diff --git a/calibre-plugin/config.py b/calibre-plugin/config.py
index 792e66a..7ced2c9 100644
--- a/calibre-plugin/config.py
+++ b/calibre-plugin/config.py
@@ -13,6 +13,8 @@ logger = logging.getLogger(__name__)
import traceback, copy, threading
from collections import OrderedDict
+from ConfigParser import ParsingError
+
try:
from PyQt5.Qt import (QDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QLineEdit, QFont, QWidget, QTextEdit, QComboBox,
@@ -40,7 +42,7 @@ else:
return x.toPyObject()
from calibre.gui2.ui import get_gui
-from calibre.gui2 import dynamic, info_dialog
+from calibre.gui2 import dynamic, info_dialog, question_dialog
from calibre.constants import numeric_version as calibre_version
# pulls in translation files for _() strings
@@ -73,7 +75,7 @@ no_trans = { 'pini':'personal.ini',
from calibre_plugins.fanfictiondownloader_plugin.prefs import prefs, PREFS_NAMESPACE
from calibre_plugins.fanfictiondownloader_plugin.dialogs \
import (UPDATE, UPDATEALWAYS, collision_order, save_collisions, RejectListDialog,
- EditTextDialog, RejectUrlEntry)
+ EditTextDialog, RejectUrlEntry, errors_dialog)
from calibre_plugins.fanfictiondownloader_plugin.fanficdownloader.adapters \
import getConfigSections
@@ -81,6 +83,8 @@ from calibre_plugins.fanfictiondownloader_plugin.fanficdownloader.adapters \
from calibre_plugins.fanfictiondownloader_plugin.common_utils \
import ( KeyboardConfigDialog, PrefsViewerDialog )
+from calibre_plugins.fanfictiondownloader_plugin.ffdl_util import (get_ffdl_config)
+
from calibre.gui2.complete2 import EditWithComplete #MultiCompleteLineEdit
class RejectURLList:
@@ -255,7 +259,7 @@ class ConfigWidget(QWidget):
prefs['addtolistsonread'] = self.readinglist_tab.addtolistsonread.isChecked()
# personal.ini
- ini = unicode(self.personalini_tab.ini.toPlainText())
+ ini = self.personalini_tab.personalini
if ini:
prefs['personal.ini'] = ini
else:
@@ -541,10 +545,6 @@ class BasicTab(QWidget):
if i > -1:
self.collision.setCurrentIndex(i)
- def show_defaults(self):
- text = get_resources('plugin-defaults.ini')
- ShowDefaultsIniDialog(self.windowIcon(),text,self).exec_()
-
def show_rejectlist(self):
d = RejectListDialog(self,
rejecturllist.get_list(),
@@ -565,7 +565,9 @@ class BasicTab(QWidget):
icon=self.windowIcon(),
title=_("Reject Reasons"),
label=_("Customize Reject List Reasons"),
- tooltip=_("Customize the Reasons presented when Rejecting URLs"))
+ tooltip=_("Customize the Reasons presented when Rejecting URLs"),
+ save_size_name='ffdl:Reject List Reasons',
+ use_find=True)
d.exec_()
if d.result() == d.Accepted:
prefs['rejectreasons'] = d.get_plain_text()
@@ -578,7 +580,8 @@ class BasicTab(QWidget):
label=_("Add Reject URLs. Use: http://...,note or http://...,title by author - note
Invalid story URLs will be ignored."),
tooltip=_("One URL per line:\nhttp://...,note\nhttp://...,title by author - note"),
rejectreasons=rejecturllist.get_reject_reasons(),
- reasonslabel=_('Add this reason to all URLs added:'))
+ reasonslabel=_('Add this reason to all URLs added:'),
+ save_size_name='ffdl:Add Reject List')
d.exec_()
if d.result() == d.Accepted:
rejecturllist.add_text(d.get_plain_text(),d.get_reason_text())
@@ -601,16 +604,25 @@ class PersonalIniTab(QWidget):
self.label = QLabel('personal.ini:')
self.l.addWidget(self.label)
- self.ini = QTextEdit(self)
- try:
- self.ini.setFont(QFont("Courier",
- self.plugin_action.gui.font().pointSize()+1))
- except Exception as e:
- logger.error("Couldn't get font: %s"%e)
- self.ini.setLineWrapMode(QTextEdit.NoWrap)
- self.ini.setText(prefs['personal.ini'])
- self.l.addWidget(self.ini)
+ # self.ini = QTextEdit(self)
+ # try:
+ # self.ini.setFont(QFont("Courier",
+ # self.plugin_action.gui.font().pointSize()+1))
+ # except Exception as e:
+ # logger.error("Couldn't get font: %s"%e)
+ # self.ini.setLineWrapMode(QTextEdit.NoWrap)
+ # self.ini.setText(prefs['personal.ini'])
+ # self.l.addWidget(self.ini)
+ self.personalini = prefs['personal.ini']
+
+ self.ini_button = QPushButton(_('Edit personal.ini'), self)
+ self.ini_button.setToolTip(_("Edit personal.ini file."))
+ self.ini_button.clicked.connect(self.add_ini_button)
+ self.l.addWidget(self.ini_button)
+
+
+
self.defaults = QPushButton(_('View Defaults')+' (plugin-defaults.ini)', self)
self.defaults.setToolTip(_("View all of the plugin's configurable settings\nand their default settings."))
self.defaults.clicked.connect(self.show_defaults)
@@ -620,38 +632,45 @@ class PersonalIniTab(QWidget):
# let edit box fill the space.
def show_defaults(self):
- text = get_resources('plugin-defaults.ini')
- ShowDefaultsIniDialog(self.windowIcon(),text,self).exec_()
+ EditTextDialog(self,
+ get_resources('plugin-defaults.ini'),
+ icon=self.windowIcon(),
+ title=_('Plugin Defaults'),
+ label=_("Plugin Defaults (%s) (Read-Only)")%'plugin-defaults.ini',
+ tooltip=_("These are all of the plugin's configurable options\nand their default settings."),
+ use_find=True,
+ read_only=True,
+ save_size_name='ffdl:defaults.ini').exec_()
-class ShowDefaultsIniDialog(QDialog):
+ def add_ini_button(self):
+ d = EditTextDialog(self,
+ self.personalini,
+ icon=self.windowIcon(),
+ title=_("Edit personal.ini"),
+ label=_("Edit personal.ini"),
+ tooltip=_("Edit personal.ini"),
+ use_find=True,
+ save_size_name='ffdl:personal.ini')
+ error=False
+ while not error:
+ error=True
+ d.exec_()
+ if d.result() == d.Accepted:
+ self.personalini = unicode(d.get_plain_text())
- def __init__(self, icon, text, parent=None):
- QDialog.__init__(self, parent)
- self.resize(600, 500)
- self.l = QVBoxLayout()
- self.setLayout(self.l)
- self.label = QLabel(_("Plugin Defaults (%s) (Read-Only)")%'plugin-defaults.ini')
- self.label.setToolTip(_("These are all of the plugin's configurable options\nand their default settings."))
- self.setWindowTitle(_('Plugin Defaults'))
- self.setWindowIcon(icon)
- self.l.addWidget(self.label)
-
- self.ini = QTextEdit(self)
- self.ini.setToolTip(_("These are all of the plugin's configurable options\nand their default settings."))
- try:
- self.ini.setFont(QFont("Courier",
- get_gui().font().pointSize()+1))
- except Exception as e:
- logger.error("Couldn't get font: %s"%e)
- self.ini.setLineWrapMode(QTextEdit.NoWrap)
- self.ini.setText(text)
- self.ini.setReadOnly(True)
- self.l.addWidget(self.ini)
-
- self.ok_button = QPushButton(_('OK'), self)
- self.ok_button.clicked.connect(self.hide)
- self.l.addWidget(self.ok_button)
-
+ try:
+ configini = get_ffdl_config("test1.com?sid=555",
+ personalini=self.personalini)
+
+ errors = configini.test_config()
+ except ParsingError as pe:
+ errors = pe.errors
+
+ if errors:
+ error = not errors_dialog(self.plugin_action.gui,
+ _('Go back to fix errors?'),
+ '
'+'
'.join([ '%s %s'%e for e in errors ])+'
') + class ReadingListTab(QWidget): def __init__(self, parent_dialog, plugin_action): diff --git a/calibre-plugin/dialogs.py b/calibre-plugin/dialogs.py index 218a780..a204632 100644 --- a/calibre-plugin/dialogs.py +++ b/calibre-plugin/dialogs.py @@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division, print_function) __license__ = 'GPL v3' -__copyright__ = '2011, Jim Miller' +__copyright__ = '2014, Jim Miller' __docformat__ = 'restructuredtext en' import logging @@ -23,18 +23,22 @@ from datetime import datetime try: from PyQt5 import QtWidgets as QtGui + from PyQt5 import QtCore from PyQt5.Qt import (QDialog, QTableWidget, QVBoxLayout, QHBoxLayout, QGridLayout, - QPushButton, QLabel, QCheckBox, QIcon, QLineEdit, + QPushButton, QFont, QLabel, QCheckBox, QIcon, QLineEdit, QComboBox, QProgressDialog, QTimer, QDialogButtonBox, QPixmap, Qt, QAbstractItemView, QTextEdit, pyqtSignal, - QGroupBox, QFrame) + QGroupBox, QFrame, QTextBrowser, QSize, QAction, + QSyntaxHighlighter, QTextCharFormat, QBrush ) except ImportError as e: from PyQt4 import QtGui + from PyQt4 import QtCore from PyQt4.Qt import (QDialog, QTableWidget, QVBoxLayout, QHBoxLayout, QGridLayout, - QPushButton, QLabel, QCheckBox, QIcon, QLineEdit, + QPushButton, QFont, QLabel, QCheckBox, QIcon, QLineEdit, QComboBox, QProgressDialog, QTimer, QDialogButtonBox, QPixmap, Qt, QAbstractItemView, QTextEdit, pyqtSignal, - QGroupBox, QFrame) + QGroupBox, QFrame, QTextBrowser, QSize, QAction, QtCore, + QSyntaxHighlighter, QTextCharFormat, QBrush ) try: from calibre.gui2 import QVariant @@ -1111,14 +1115,20 @@ class RejectListDialog(SizePersistedDialog): def get_deletebooks(self): return self.deletebooks.isChecked() -class EditTextDialog(QDialog): +class EditTextDialog(SizePersistedDialog): def __init__(self, parent, text, icon=None, title=None, label=None, tooltip=None, - rejectreasons=[],reasonslabel=None + rejectreasons=[],reasonslabel=None, + use_find=False, + read_only=False, + save_size_name='ffdl:edit text dialog', ): - QDialog.__init__(self, parent) - self.resize(600, 500) + SizePersistedDialog.__init__(self, parent, save_size_name) + + self.keys=dict() + + #self.resize(600, 500) self.l = QVBoxLayout() self.setLayout(self.l) self.label = QLabel(label) @@ -1129,10 +1139,56 @@ class EditTextDialog(QDialog): self.l.addWidget(self.label) self.textedit = QTextEdit(self) + + highlighter = IniHighlighter(self.textedit, "Classic") + self.textedit.setLineWrapMode(QTextEdit.NoWrap) + try: + self.textedit.setFont(QFont("Courier", + parent.font().pointSize()+1)) + except Exception as e: + logger.error("Couldn't get font: %s"%e) + + self.textedit.setReadOnly(read_only) + self.textedit.setText(text) self.l.addWidget(self.textedit) + self.lastStart = 0 + + if use_find: + + findtooltip=_('Search for string in edit box.') + + horz = QHBoxLayout() + label = QLabel(_('Find:')) + + label.setToolTip(findtooltip) + + # Button to search the document for something + self.findButton = QtGui.QPushButton(_('Find'),self) + self.findButton.clicked.connect(self.find) + self.findButton.setToolTip(findtooltip) + + # The field into which to type the query + self.findField = QLineEdit(self) + self.findField.setToolTip(findtooltip) + self.findField.returnPressed.connect(self.findButton.setFocus) + + # Case Sensitivity option + self.caseSens = QtGui.QCheckBox(_('Case sensitive'),self) + self.caseSens.setToolTip(_("Search for case sensitive string; don't treat Harry, HARRY and harry all the same.")) + + horz.addWidget(label) + horz.addWidget(self.findField) + horz.addWidget(self.findButton) + horz.addWidget(self.caseSens) + + self.l.addLayout(horz) + + self.addCtrlKeyPress(QtCore.Qt.Key_F,self.findFocus) + self.addCtrlKeyPress(QtCore.Qt.Key_G,self.find) + if tooltip: self.label.setToolTip(tooltip) self.textedit.setToolTip(tooltip) @@ -1159,11 +1215,230 @@ class EditTextDialog(QDialog): button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) + # button_box.button(QDialogButtonBox.Ok).setDefault(False) + # button_box.button(QDialogButtonBox.Cancel).setDefault(False) self.l.addWidget(button_box) + + # Cause our dialog size to be restored from prefs or created on first usage + self.resize_dialog() + def addCtrlKeyPress(self,key,func): + # print("addKeyPress: key(0x%x)"%key) + # print("control: 0x%x"%QtCore.Qt.ControlModifier) + self.keys[key]=func + + def keyPressEvent(self, event): + # print("event: key(0x%x) modifiers(0x%x)"%(event.key(),event.modifiers())) + if (event.modifiers() & QtCore.Qt.ControlModifier) and event.key() in self.keys: + func = self.keys[event.key()] + return func() + else: + return SizePersistedDialog.keyPressEvent(self, event) + def get_plain_text(self): return unicode(self.textedit.toPlainText()) def get_reason_text(self): return unicode(self.reason_edit.currentText()).strip() + + def findFocus(self): + # print("findFocus called") + self.findField.setFocus() + self.findField.selectAll() + + def find(self): + + print("find self.lastStart:%s"%self.lastStart) + + # Grab the parent's text + text = self.textedit.toPlainText() + + # And the text to find + query = self.findField.text() + + if not self.caseSens.isChecked(): + text = text.lower() + query = query.lower() + + # Use normal string search to find the query from the + # last starting position + self.lastStart = text.find(query,self.lastStart + 1) + # If the find() method didn't return -1 (not found) + + if self.lastStart >= 0: + end = self.lastStart + len(query) + self.moveCursor(self.lastStart,end) + else: + # Make the next search start from the begining again + self.lastStart = 0 + self.textedit.moveCursor(self.textedit.textCursor().Start) + + def moveCursor(self,start,end): + + # We retrieve the QTextCursor object from the parent's QTextEdit + cursor = self.textedit.textCursor() + + # Then we set the position to the beginning of the last match + cursor.setPosition(start) + + # Next we move the Cursor by over the match and pass the KeepAnchor parameter + # which will make the cursor select the the match's text + cursor.movePosition(cursor.Right,cursor.KeepAnchor,end - start) + + # And finally we set this new cursor as the parent's + self.textedit.setTextCursor(cursor) + +def errors_dialog(parent, + title, + html): + + d = ViewLog(title,html,parent) + + return d.exec_() == d.Accepted + + +class ViewLog(QDialog): + + def __init__(self, title, html, parent=None): + QDialog.__init__(self, parent) + self.l = l = QVBoxLayout() + self.setLayout(l) + + self.tb = QTextBrowser(self) + self.tb.setFont(QFont("Courier", + parent.font().pointSize()+1)) + self.tb.setHtml(html) + l.addWidget(self.tb) + + self.bb = QDialogButtonBox(QDialogButtonBox.Yes | QDialogButtonBox.No) + self.bb.accepted.connect(self.accept) + self.bb.rejected.connect(self.reject) + # self.copy_button = self.bb.addButton(_('Copy to clipboard'), + # self.bb.ActionRole) + # self.copy_button.setIcon(QIcon(I('edit-copy.png'))) + # self.copy_button.clicked.connect(self.copy_to_clipboard) + l.addWidget(self.bb) + self.setModal(False) + self.resize(700, 500) + self.setWindowTitle(title) + self.setWindowIcon(QIcon(I('debug.png'))) + #self.show() + + def copy_to_clipboard(self): + txt = self.tb.toPlainText() + QApplication.clipboard().setText(txt) + +class IniHighlighter(QSyntaxHighlighter): + + def __init__( self, parent, theme ): + QSyntaxHighlighter.__init__( self, parent ) + self.parent = parent + keyword = QTextCharFormat() + reservedClasses = QTextCharFormat() + assignmentOperator = QTextCharFormat() + delimiter = QTextCharFormat() + specialConstant = QTextCharFormat() + boolean = QTextCharFormat() + number = QTextCharFormat() + comment = QTextCharFormat() + string = QTextCharFormat() + singleQuotedString = QTextCharFormat() + + self.highlightingRules = [] + + # # keyword + # brush = QBrush( Qt.darkBlue, Qt.SolidPattern ) + # keyword.setForeground( brush ) + # keyword.setFontWeight( QFont.Bold ) + # keywords = [ "break", "else", "for", "if", "in", + # "next", "repeat", "return", "switch", + # "try", "while" ] + # for word in keywords: + # pattern = "\\b" + word + "\\b" + # rule = HighlightingRule( pattern, keyword ) + # self.highlightingRules.append( rule ) + + # # reservedClasses + # reservedClasses.setForeground( brush ) + # reservedClasses.setFontWeight( QFont.Bold ) + # keywords = [ "array", "character", "complex", + # "data.frame", "double", "factor", + # "function", "integer", "list", + # "logical", "matrix", "numeric", + # "vector" ] + # for word in keywords: + # pattern = "\\b" + word + "\\b" + # rule = HighlightingRule( pattern, reservedClasses ) + # self.highlightingRules.append( rule ) + + # # assignmentOperator + # brush = QBrush( Qt.yellow, Qt.SolidPattern ) + # pattern = "(<){1,2}-" + # assignmentOperator.setForeground( brush ) + # assignmentOperator.setFontWeight( QFont.Bold ) + # rule = HighlightingRule( pattern, assignmentOperator ) + # self.highlightingRules.append( rule ) + + # section + pattern = r"^\[[^\]]+\]" + brush = QBrush( Qt.darkBlue, Qt.SolidPattern ) + delimiter.setForeground( brush ) + delimiter.setFontWeight( QFont.Bold ) + rule = HighlightingRule( pattern, delimiter ) + self.highlightingRules.append( rule ) + + # # specialConstant + # brush = QBrush( Qt.green, Qt.SolidPattern ) + # specialConstant.setForeground( brush ) + # keywords = [ "Inf", "NA", "NaN", "NULL" ] + # for word in keywords: + # pattern = "\\b" + word + "\\b" + # rule = HighlightingRule( pattern, specialConstant ) + # self.highlightingRules.append( rule ) + + # boolean, case insensitive + boolean.setForeground( brush ) + pattern = r"\b(true|false)\b" + rule = HighlightingRule( pattern, boolean ) + self.highlightingRules.append( rule ) + + # # number + # pattern = "[-+]?[0-9]*\.?[0-9]+?([eE][-+]?[0-9]+?)?" + # number.setForeground( brush ) + # rule = HighlightingRule( pattern, number ) + # self.highlightingRules.append( rule ) + + # comment + brush = QBrush( Qt.darkGray, Qt.SolidPattern ) + pattern = "#[^\n]*" + comment.setForeground( brush ) + rule = HighlightingRule( pattern, comment ) + self.highlightingRules.append( rule ) + + # string + brush = QBrush( Qt.red, Qt.SolidPattern ) + pattern = "\".*?\"" + string.setForeground( brush ) + rule = HighlightingRule( pattern, string ) + self.highlightingRules.append( rule ) + + # singleQuotedString + pattern = "\'.*?\'" + singleQuotedString.setForeground( brush ) + rule = HighlightingRule( pattern, singleQuotedString ) + self.highlightingRules.append( rule ) + + def highlightBlock( self, text ): + for rule in self.highlightingRules: + for match in rule.pattern.finditer(text): + self.setFormat( match.start(), match.end()-match.start(), rule.format ) + self.setCurrentBlockState( 0 ) + +class HighlightingRule(): + def __init__( self, pattern, format ): + if isinstance(pattern,basestring): + self.pattern = re.compile(pattern) + else: + self.pattern=pattern + self.format = format diff --git a/downloader.py b/downloader.py index 62a92de..f0a994a 100644 --- a/downloader.py +++ b/downloader.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright 2011 Fanficdownloader team +# Copyright 2014 Fanficdownloader team # # Licensed under the Apache License, Version 2.0 (the 'License'); # you may not use this file except in compliance with the License. diff --git a/fanficdownloader/configurable.py b/fanficdownloader/configurable.py index 9b6d35f..bbe8c1d 100644 --- a/fanficdownloader/configurable.py +++ b/fanficdownloader/configurable.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright 2011 Fanficdownloader team +# Copyright 2014 Fanficdownloader team # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ # import ConfigParser, re +from ConfigParser import DEFAULTSECT, MissingSectionHeaderError, ParsingError # All of the writers(epub,html,txt) and adapters(ffnet,twlt,etc) # inherit from Configurable. The config file(s) uses ini format: @@ -32,10 +33,15 @@ import ConfigParser, re # [overrides] # titlepage_entries: category +import adapters + class Configuration(ConfigParser.SafeConfigParser): def __init__(self, site, fileform): ConfigParser.SafeConfigParser.__init__(self) + + self.linenos=dict() # key by section or section,key -> lineno + self.sectionslist = ['defaults'] if site.startswith("www."): @@ -162,6 +168,132 @@ class Configuration(ConfigParser.SafeConfigParser): def getConfigList(self, key): return self.get_config_list(self.sectionslist, key) + + def test_config(self): + errors=[] + + sites = adapters.getConfigSections() + sitesections = ['defaults','overrides'] + for section in sites: + sitesections.append(section) + if section.startswith('www.'): + # add w/o www if has www + sitesections.append(section[4:]) + else: + # add w/ www if doesn't www + sitesections.append('www.%s'%section) + + allowedsections = [] + forms=['html','txt','epub','mobi'] + allowedsections.extend(forms) + + for section in sitesections: + allowedsections.append(section) + for f in forms: + allowedsections.append('%s:%s'%(section,f)) + + for section in self.sections(): + if section not in allowedsections and 'teststory:' not in section: + errors.append((self.get_lineno(section),"Bad Section Name: %s"%section)) + + return errors + + def get_lineno(self,section,key=None): + if key: + return self.linenos.get(section+','+key,None) + else: + return self.linenos.get(section,None) + + ## Copied from Python library so as to make it save linenos too. + # + # Regular expressions for parsing section headers and options. + # + def _read(self, fp, fpname): + """Parse a sectioned setup file. + + The sections in setup file contains a title line at the top, + indicated by a name in square brackets (`[]'), plus key/value + options lines, indicated by `name: value' format lines. + Continuations are represented by an embedded newline then + leading whitespace. Blank lines, lines beginning with a '#', + and just about everything else are ignored. + """ + cursect = None # None, or a dictionary + optname = None + lineno = 0 + e = None # None, or an exception + while True: + line = fp.readline() + if not line: + break + lineno = lineno + 1 + # comment or blank line? + if line.strip() == '' or line[0] in '#;': + continue + if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR": + # no leading whitespace + continue + # continuation line? + if line[0].isspace() and cursect is not None and optname: + value = line.strip() + if value: + cursect[optname] = "%s\n%s" % (cursect[optname], value) + # a section header or option header? + else: + # is it a section header? + mo = self.SECTCRE.match(line) + if mo: + sectname = mo.group('header') + if sectname in self._sections: + cursect = self._sections[sectname] + elif sectname == DEFAULTSECT: + cursect = self._defaults + else: + cursect = self._dict() + cursect['__name__'] = sectname + self._sections[sectname] = cursect + self.linenos[sectname]=lineno + # So sections can't start with a continuation line + optname = None + # no section header in the file? + elif cursect is None: + if not e: + e = ParsingError(fpname) + e.append(lineno, u'(Line outside section) '+line) + #raise MissingSectionHeaderError(fpname, lineno, line) + # an option line? + else: + mo = self._optcre.match(line) + if mo: + optname, vi, optval = mo.group('option', 'vi', 'value') + # This check is fine because the OPTCRE cannot + # match if it would set optval to None + if optval is not None: + if vi in ('=', ':') and ';' in optval: + # ';' is a comment delimiter only if it follows + # a spacing character + pos = optval.find(';') + if pos != -1 and optval[pos-1].isspace(): + optval = optval[:pos] + optval = optval.strip() + # allow empty values + if optval == '""': + optval = '' + optname = self.optionxform(optname.rstrip()) + cursect[optname] = optval + self.linenos[cursect['__name__']+','+optname]=lineno + else: + # a non-fatal parsing error occurred. set up the + # exception but keep going. the exception will be + # raised at the end of the file and will contain a + # list of all bogus lines + if not e: + e = ParsingError(fpname) + e.append(lineno, line) + # if any parsing errors occurred, raise an exception + if e: + raise e + # extended by adapter, writer and story for ease of calling configuration. class Configurable(object):