def validatedValue(self, value): assert value if value not in self.choices: raise FieldValueError( u"value is %r but must be one of: %s" % (value, _tools.humanReadableList(self.choices)) ) return value
def validatedValue(self, value): assert value if value not in self.choices: raise FieldValueError( u"value is %r but must be one of: %s" % (value, _tools.humanReadableList(self.choices))) return value
def main(arguments): assert arguments is not None _FORMAT_CSV = "csv" _FORMAT_DOCBOOK = "docbook" _FORMAT_RST = "rst" _FORMATS = [_FORMAT_CSV, _FORMAT_DOCBOOK, _FORMAT_RST] _FORMAT_TO_SUFFIX_MAP = { _FORMAT_CSV: ".csv", _FORMAT_DOCBOOK: ".xml", _FORMAT_RST: ".rst" } usage = "usage: %prog [options] ODS-FILE [OUTPUT-FILE]" parser = optparse.OptionParser(usage) parser.set_defaults(format=_FORMAT_CSV, id="insert-id", sheet=1, title="Insert Title") parser.add_option("-f", "--format", metavar="FORMAT", type="choice", choices=_FORMATS, dest="format", help="output format: %s (default: %%default)" % _tools.humanReadableList(_FORMATS)) parser.add_option("-i", "--id", metavar="ID", dest="id", help="XML ID table can be referenced with (default: %default)") parser.add_option("-1", "--heading", action="store_true", dest="firstRowIsHeading", help="render first row as heading") parser.add_option("-s", "--sheet", metavar="SHEET", type="long", dest="sheet", help="sheet to convert (default: %default)") parser.add_option("-t", "--title", metavar="TITLE", dest="title", help="title to be used for XML table (default: %default)") options, others = parser.parse_args(arguments) # TODO: If no output file is specified, derive name from input file. if options.sheet < 1: _log.error("option --sheet is %d but must be at least 1" % options.sheet) sys.exit(1) elif len(others) in [1, 2]: sourceFilePath = others[0] if len(others) == 2: targetFilePath = others[1] else: assert options.format in _FORMAT_TO_SUFFIX_MAP suffix = _FORMAT_TO_SUFFIX_MAP[options.format] targetFilePath = _tools.withSuffix(sourceFilePath, suffix) _log.info("convert %r to %r using format %r" % (sourceFilePath, targetFilePath, options.format)) try: if options.format == _FORMAT_CSV: if options.firstRowIsHeading: _log.error("option --heading can not be used with --format=csv") sys.exit(1) toCsv(sourceFilePath, targetFilePath, sheet=options.sheet) elif options.format == _FORMAT_DOCBOOK: # FIXME: Add support for --heading with DocBook. assert not options.firstRowIsHeading toDocBookXml(sourceFilePath, targetFilePath, xmlId=options.id, title=options.title, sheet=options.sheet) elif options.format == _FORMAT_RST: toRst(sourceFilePath, targetFilePath, firstRowIsHeading=options.firstRowIsHeading, sheet=options.sheet) else: # pragma: no cover raise NotImplementedError(u"format=%r" % (options.format)) except EnvironmentError, error: _log.error("cannot convert ods to csv: %s" % error) sys.exit(1) except Exception, error: _log.error("cannot convert ods to csv: %s" % error, exc_info=1) sys.exit(1)
def _validatedChoice(self, key, value, choices): """ Validate that `value` is one of the available `choices` and otherwise raise `DataFormatValueError`. Always returns `value`. To be called from `validated()`. """ assert key assert choices if value not in choices: raise DataFormatValueError(u"value for data format property %r is %r but must be one of: %s" % (key, value, _tools.humanReadableList(choices))) return value
def _validatedChoice(self, key, value, choices): """ Validate that `value` is one of the available `choices` and otherwise raise `DataFormatValueError`. Always returns `value`. To be called from `validated()`. """ assert key assert choices if value not in choices: raise DataFormatValueError( u"value for data format property %r is %r but must be one of: %s" % (key, value, _tools.humanReadableList(choices))) return value
def _normalizedKey(self, key): assert key is not None # Normalize key. keyParts = key.lower().split() result = "" for keyPart in keyParts: if result: result += " " result += keyPart # Validate key. if result not in self._allKeys: raise DataFormatSyntaxError(u"data format property is %r but must be one of: %s" % (result, _tools.humanReadableList(self._allKeys))) return result
def getFieldNameIndex(supposedFieldName, availableFieldNames): """ The index of `supposedFieldName` in `availableFieldNames`. In case it is missing, raise a `FieldLookupError`. """ assert supposedFieldName is not None assert supposedFieldName == supposedFieldName.strip() assert availableFieldNames fieldName = supposedFieldName.strip() try: fieldIndex = availableFieldNames.index(fieldName) except ValueError: raise FieldLookupError( u"unknown field name %r must be replaced by one of: %s" % (fieldName, _tools.humanReadableList(availableFieldNames))) return fieldIndex
def _normalizedKey(self, key): assert key is not None # Normalize key. keyParts = key.lower().split() result = "" for keyPart in keyParts: if result: result += " " result += keyPart # Validate key. if result not in self._allKeys: raise DataFormatSyntaxError( u"data format property is %r but must be one of: %s" % (result, _tools.humanReadableList(self._allKeys))) return result
def getFieldNameIndex(supposedFieldName, availableFieldNames): """ The index of `supposedFieldName` in `availableFieldNames`. In case it is missing, raise a `FieldLookupError`. """ assert supposedFieldName is not None assert supposedFieldName == supposedFieldName.strip() assert availableFieldNames fieldName = supposedFieldName.strip() try: fieldIndex = availableFieldNames.index(fieldName) except ValueError: raise FieldLookupError( u"unknown field name %r must be replaced by one of: %s" % (fieldName, _tools.humanReadableList(availableFieldNames)) ) return fieldIndex
def createDataFormat(name): """ Factory function to create the specified data format. """ assert name is not None _NAME_TO_FORMAT_MAP = { FORMAT_CSV: CsvDataFormat, FORMAT_DELIMITED: DelimitedDataFormat, FORMAT_EXCEL: ExcelDataFormat, FORMAT_FIXED: FixedDataFormat, FORMAT_ODS: OdsDataFormat } actualName = name.lower() formatClass = _NAME_TO_FORMAT_MAP.get(actualName) if formatClass is None: dataFormatNames = _NAME_TO_FORMAT_MAP.keys() dataFormatNames.sort() raise DataFormatSyntaxError(u"data format is %r but must be one of: %r" % (actualName, _tools.humanReadableList(dataFormatNames))) result = formatClass() return result
def createDataFormat(name): """ Factory function to create the specified data format. """ assert name is not None _NAME_TO_FORMAT_MAP = { FORMAT_CSV: CsvDataFormat, FORMAT_DELIMITED: DelimitedDataFormat, FORMAT_EXCEL: ExcelDataFormat, FORMAT_FIXED: FixedDataFormat, FORMAT_ODS: OdsDataFormat } actualName = name.lower() formatClass = _NAME_TO_FORMAT_MAP.get(actualName) if formatClass is None: dataFormatNames = _NAME_TO_FORMAT_MAP.keys() dataFormatNames.sort() raise DataFormatSyntaxError( u"data format is %r but must be one of: %r" % (actualName, _tools.humanReadableList(dataFormatNames))) result = formatClass() return result
def testCanBuildHumanReadableList(self): self.assertEqual(_tools.humanReadableList([]), "") self.assertEqual(_tools.humanReadableList(["a"]), "'a'") self.assertEqual(_tools.humanReadableList(["a", "b"]), "'a' or 'b'") self.assertEqual(_tools.humanReadableList(["a", "b", "c"]), "'a', 'b' or 'c'")
def _validatedCharacter(self, key, value): r""" A single character intended as value for data format property ``key`` derived from ``value``, which can be: * a decimal or hex number (prefixed with "0x") referring to the ASCII/Unicode of the character * a string containing a single character such as "\t". * a symbolic name such as "Tab". Anything else yields a `DataFormatSyntaxError`. >>> format = DelimitedDataFormat() >>> format._validatedCharacter("x", "34") '"' >>> format._validatedCharacter("x", "9") '\t' >>> format._validatedCharacter("x", "0x9") '\t' >>> format._validatedCharacter("x", "Tab") '\t' >>> format._validatedCharacter("x", "\t") '\t' >>> format._validatedCharacter("x", "") Traceback (most recent call last): ... DataFormatSyntaxError: value for data format property 'x' must be specified >>> format._validatedCharacter("x", "Tab Tab") Traceback (most recent call last): ... DataFormatSyntaxError: value for data format property 'x' must describe a single character but is: 'Tab Tab' >>> format._validatedCharacter("x", "17.23") Traceback (most recent call last): ... DataFormatSyntaxError: numeric value for data format property 'x' must be an integer but is: '17.23' >>> format._validatedCharacter("x", "Hugo") Traceback (most recent call last): ... DataFormatSyntaxError: symbolic name 'Hugo' for data format property 'x' must be one of: 'cr', 'ff', 'lf', 'tab' or 'vt' >>> format._validatedCharacter("x", "( ") Traceback (most recent call last): ... DataFormatSyntaxError: value for data format property 'x' must a number, a single character or a symbolic name but is: '( ' >>> format._validatedCharacter("x", "\"\\") Traceback (most recent call last): ... DataFormatSyntaxError: value for data format property 'x' must a number, a single character or a symbolic name but is: '"\\' >>> format._validatedCharacter("x", "\"abc\"") Traceback (most recent call last): ... DataFormatSyntaxError: text for data format property 'x' must be a single character but is: '"abc"' """ # TODO: Consolidate code with `ranges.__init__()`. assert key assert value is not None if len(value) == 1 and (value < "0" or value > "9"): result = value else: result = None tokens = tokenize.generate_tokens(StringIO.StringIO(value).readline) nextToken = tokens.next() if _tools.isEofToken(nextToken): raise DataFormatSyntaxError(u"value for data format property %r must be specified" % key) nextType = nextToken[0] nextValue = nextToken[1] if nextType == token.NUMBER: try: if nextValue[:2].lower() == "0x": nextValue = nextValue[2:] base = 16 else: base = 10 longValue = long(nextValue, base) except ValueError: raise DataFormatSyntaxError(u"numeric value for data format property %r must be an integer but is: %r" % (key, value)) elif nextType == token.NAME: try: longValue = tools.SYMBOLIC_NAMES_MAP[nextValue.lower()] except KeyError: validSymbols = _tools.humanReadableList(sorted(tools.SYMBOLIC_NAMES_MAP.keys())) raise DataFormatSyntaxError(u"symbolic name %r for data format property %r must be one of: %s" % (value, key, validSymbols)) elif nextType == token.STRING: if len(nextValue) != 3: raise DataFormatSyntaxError(u"text for data format property %r must be a single character but is: %r" % (key, value)) leftQuote = nextValue[0] rightQuote = nextValue[2] assert leftQuote in "\"\'", u"leftQuote=%r" % leftQuote assert rightQuote in "\"\'", u"rightQuote=%r" % rightQuote longValue = ord(nextValue[1]) else: raise DataFormatSyntaxError(u"value for data format property %r must a number, a single character or a symbolic name but is: %r" % (key, value)) # Ensure there are no further tokens. nextToken = tokens.next() if not _tools.isEofToken(nextToken): raise DataFormatSyntaxError(u"value for data format property %r must describe a single character but is: %r" % (key, value)) assert longValue is not None assert longValue >= 0 result = chr(longValue) assert result is not None return result
def _validatedCharacter(self, key, value): r""" A single character intended as value for data format property ``key`` derived from ``value``, which can be: * a decimal or hex number (prefixed with "0x") referring to the ASCII/Unicode of the character * a string containing a single character such as "\t". * a symbolic name such as "Tab". Anything else yields a `DataFormatSyntaxError`. >>> format = DelimitedDataFormat() >>> format._validatedCharacter("x", "34") '"' >>> format._validatedCharacter("x", "9") '\t' >>> format._validatedCharacter("x", "0x9") '\t' >>> format._validatedCharacter("x", "Tab") '\t' >>> format._validatedCharacter("x", "\t") '\t' >>> format._validatedCharacter("x", "") Traceback (most recent call last): ... DataFormatSyntaxError: value for data format property 'x' must be specified >>> format._validatedCharacter("x", "Tab Tab") Traceback (most recent call last): ... DataFormatSyntaxError: value for data format property 'x' must describe a single character but is: 'Tab Tab' >>> format._validatedCharacter("x", "17.23") Traceback (most recent call last): ... DataFormatSyntaxError: numeric value for data format property 'x' must be an integer but is: '17.23' >>> format._validatedCharacter("x", "Hugo") Traceback (most recent call last): ... DataFormatSyntaxError: symbolic name 'Hugo' for data format property 'x' must be one of: 'cr', 'ff', 'lf', 'tab' or 'vt' >>> format._validatedCharacter("x", "( ") Traceback (most recent call last): ... DataFormatSyntaxError: value for data format property 'x' must a number, a single character or a symbolic name but is: '( ' >>> format._validatedCharacter("x", "\"\\") Traceback (most recent call last): ... DataFormatSyntaxError: value for data format property 'x' must a number, a single character or a symbolic name but is: '"\\' >>> format._validatedCharacter("x", "\"abc\"") Traceback (most recent call last): ... DataFormatSyntaxError: text for data format property 'x' must be a single character but is: '"abc"' """ # TODO: Consolidate code with `ranges.__init__()`. assert key assert value is not None if len(value) == 1 and (value < "0" or value > "9"): result = value else: result = None tokens = tokenize.generate_tokens( StringIO.StringIO(value).readline) nextToken = tokens.next() if _tools.isEofToken(nextToken): raise DataFormatSyntaxError( u"value for data format property %r must be specified" % key) nextType = nextToken[0] nextValue = nextToken[1] if nextType == token.NUMBER: try: if nextValue[:2].lower() == "0x": nextValue = nextValue[2:] base = 16 else: base = 10 longValue = long(nextValue, base) except ValueError: raise DataFormatSyntaxError( u"numeric value for data format property %r must be an integer but is: %r" % (key, value)) elif nextType == token.NAME: try: longValue = tools.SYMBOLIC_NAMES_MAP[nextValue.lower()] except KeyError: validSymbols = _tools.humanReadableList( sorted(tools.SYMBOLIC_NAMES_MAP.keys())) raise DataFormatSyntaxError( u"symbolic name %r for data format property %r must be one of: %s" % (value, key, validSymbols)) elif nextType == token.STRING: if len(nextValue) != 3: raise DataFormatSyntaxError( u"text for data format property %r must be a single character but is: %r" % (key, value)) leftQuote = nextValue[0] rightQuote = nextValue[2] assert leftQuote in "\"\'", u"leftQuote=%r" % leftQuote assert rightQuote in "\"\'", u"rightQuote=%r" % rightQuote longValue = ord(nextValue[1]) else: raise DataFormatSyntaxError( u"value for data format property %r must a number, a single character or a symbolic name but is: %r" % (key, value)) # Ensure there are no further tokens. nextToken = tokens.next() if not _tools.isEofToken(nextToken): raise DataFormatSyntaxError( u"value for data format property %r must describe a single character but is: %r" % (key, value)) assert longValue is not None assert longValue >= 0 result = chr(longValue) assert result is not None return result
def __init__(self, text, default=None): """ Setup a range as specified by ``text``. ``text`` must be of the form "lower:upper" or "limit". In case ``text`` is empty (""), any value will be accepted by `validate()`. For example, "1:40" accepts values between 1 and 40. ``default`` is an alternative text to use in case ``text`` is ``None`` or empty. """ assert default is None or default.strip(), u"default=%r" % default # Find out if a `text` has been specified and if not, use optional `default` instead. hasText = (text is not None) and text.strip() if not hasText and default is not None: text = default hasText = True if not hasText: # Use empty ranges. self._description = None self._items = None else: self._description = text self._items = [] # TODO: Consolidate code with `DelimitedDataFormat._validatedCharacter()`. tokens = tokenize.generate_tokens(StringIO.StringIO(text).readline) endReached = False while not endReached: lower = None upper = None colonFound = False afterHyphen = False nextToken = tokens.next() while not _tools.isEofToken( nextToken) and not _tools.isCommaToken(nextToken): nextType = nextToken[0] nextValue = nextToken[1] if nextType in (token.NAME, token.NUMBER, token.STRING): if nextType == token.NUMBER: try: if nextValue[:2].lower() == "0x": nextValue = nextValue[2:] base = 16 else: base = 10 longValue = long(nextValue, base) except ValueError: raise RangeSyntaxError( u"number must be an integer but is: %r" % nextValue) if afterHyphen: longValue = -1 * longValue afterHyphen = False elif nextType == token.NAME: try: longValue = tools.SYMBOLIC_NAMES_MAP[ nextValue.lower()] except KeyError: validSymbols = _tools.humanReadableList( sorted(tools.SYMBOLIC_NAMES_MAP.keys())) raise RangeSyntaxError( u"symbolic name %r must be one of: %s" % (nextValue, validSymbols)) elif nextType == token.STRING: if len(nextValue) != 3: raise RangeSyntaxError( u"text for range must contain a single character but is: %r" % nextValue) leftQuote = nextValue[0] rightQuote = nextValue[2] assert leftQuote in "\"\'", u"leftQuote=%r" % leftQuote assert rightQuote in "\"\'", u"rightQuote=%r" % rightQuote longValue = ord(nextValue[1]) if colonFound: if upper is None: upper = longValue else: raise RangeSyntaxError( "range must have at most lower and upper limit but found another number: %r" % nextValue) elif lower is None: lower = longValue else: raise RangeSyntaxError( u"number must be followed by colon (:) but found: %r" % nextValue) elif afterHyphen: raise RangeSyntaxError( u"hyphen (-) must be followed by number but found: %r" % nextValue) elif (nextType == token.OP) and (nextValue == "-"): afterHyphen = True elif (nextType == token.OP) and (nextValue == ":"): if colonFound: raise RangeSyntaxError( u"range item must contain at most one colon (:)" ) colonFound = True else: message = u"range must be specified using integer numbers, text, symbols and colon (:) but found: %r [token type: %r]" % ( nextValue, nextType) raise RangeSyntaxError(message) nextToken = tokens.next() if afterHyphen: raise RangeSyntaxError( u"hyphen (-) at end must be followed by number") # Decide upon the result. if (lower is None): if (upper is None): if colonFound: # Handle ":". # TODO: Handle ":" same as ""? raise RangeSyntaxError( u"colon (:) must be preceded and/or succeeded by number" ) else: # Handle "". result = None else: assert colonFound # Handle ":y". result = (None, upper) elif colonFound: # Handle "x:" and "x:y". if (upper is not None) and (lower > upper): raise RangeSyntaxError( u"lower range %d must be greater or equal to upper range %d" % (lower, upper)) result = (lower, upper) else: # Handle "x". result = (lower, lower) if result is not None: for item in self._items: if self._itemsOverlap(item, result): # TODO: use _repr_item() or something to display item in error message. raise RangeSyntaxError( u"range items must not overlap: %r and %r" % (self._repr_item(item), self._repr_item(result))) self._items.append(result) if _tools.isEofToken(nextToken): endReached = True
def __init__(self, text, default=None): """ Setup a range as specified by ``text``. ``text`` must be of the form "lower:upper" or "limit". In case ``text`` is empty (""), any value will be accepted by `validate()`. For example, "1:40" accepts values between 1 and 40. ``default`` is an alternative text to use in case ``text`` is ``None`` or empty. """ assert default is None or default.strip(), u"default=%r" % default # Find out if a `text` has been specified and if not, use optional `default` instead. hasText = (text is not None) and text.strip() if not hasText and default is not None: text = default hasText = True if not hasText: # Use empty ranges. self._description = None self._items = None else: self._description = text self._items = [] # TODO: Consolidate code with `DelimitedDataFormat._validatedCharacter()`. tokens = tokenize.generate_tokens(StringIO.StringIO(text).readline) endReached = False while not endReached: lower = None upper = None colonFound = False afterHyphen = False nextToken = tokens.next() while not _tools.isEofToken(nextToken) and not _tools.isCommaToken(nextToken): nextType = nextToken[0] nextValue = nextToken[1] if nextType in (token.NAME, token.NUMBER, token.STRING): if nextType == token.NUMBER: try: if nextValue[:2].lower() == "0x": nextValue = nextValue[2:] base = 16 else: base = 10 longValue = long(nextValue, base) except ValueError: raise RangeSyntaxError(u"number must be an integer but is: %r" % nextValue) if afterHyphen: longValue = - 1 * longValue afterHyphen = False elif nextType == token.NAME: try: longValue = tools.SYMBOLIC_NAMES_MAP[nextValue.lower()] except KeyError: validSymbols = _tools.humanReadableList(sorted(tools.SYMBOLIC_NAMES_MAP.keys())) raise RangeSyntaxError(u"symbolic name %r must be one of: %s" % (nextValue, validSymbols)) elif nextType == token.STRING: if len(nextValue) != 3: raise RangeSyntaxError(u"text for range must contain a single character but is: %r" % nextValue) leftQuote = nextValue[0] rightQuote = nextValue[2] assert leftQuote in "\"\'", u"leftQuote=%r" % leftQuote assert rightQuote in "\"\'", u"rightQuote=%r" % rightQuote longValue = ord(nextValue[1]) if colonFound: if upper is None: upper = longValue else: raise RangeSyntaxError("range must have at most lower and upper limit but found another number: %r" % nextValue) elif lower is None: lower = longValue else: raise RangeSyntaxError(u"number must be followed by colon (:) but found: %r" % nextValue) elif afterHyphen: raise RangeSyntaxError(u"hyphen (-) must be followed by number but found: %r" % nextValue) elif (nextType == token.OP) and (nextValue == "-"): afterHyphen = True elif (nextType == token.OP) and (nextValue == ":"): if colonFound: raise RangeSyntaxError(u"range item must contain at most one colon (:)") colonFound = True else: message = u"range must be specified using integer numbers, text, symbols and colon (:) but found: %r [token type: %r]" % (nextValue, nextType) raise RangeSyntaxError(message) nextToken = tokens.next() if afterHyphen: raise RangeSyntaxError(u"hyphen (-) at end must be followed by number") # Decide upon the result. if (lower is None): if (upper is None): if colonFound: # Handle ":". # TODO: Handle ":" same as ""? raise RangeSyntaxError(u"colon (:) must be preceded and/or succeeded by number") else: # Handle "". result = None else: assert colonFound # Handle ":y". result = (None, upper) elif colonFound: # Handle "x:" and "x:y". if (upper is not None) and (lower > upper): raise RangeSyntaxError(u"lower range %d must be greater or equal to upper range %d" % (lower, upper)) result = (lower, upper) else: # Handle "x". result = (lower, lower) if result is not None: for item in self._items: if self._itemsOverlap(item, result): # TODO: use _repr_item() or something to display item in error message. raise RangeSyntaxError(u"range items must not overlap: %r and %r" % (self._repr_item(item), self._repr_item(result))) self._items.append(result) if _tools.isEofToken(nextToken): endReached = True