Source code for knittingpattern.Parser

"""In this module you can find the parsing of knitting pattern structures."""
# attributes

ID = "id"  #: the id of a row, an instruction or a pattern
NAME = "name"  #: the name of a row
TYPE = "type"  #: the type of an instruction or the knitting pattern set
VERSION = "version"  #: the version of a knitting pattern set
INSTRUCTIONS = "instructions"  #: the instructions in a row
SAME_AS = "same as"  #: pointer to a inherit from
PATTERNS = "patterns"  #: the patterns in the knitting pattern set
ROWS = "rows"  #: the rows inside a pattern
CONNECTIONS = "connections"  #: the connections in a pattern
FROM = "from"  #: the position and row a connection comes from
TO = "to"  #: the position and row a connection goes to
START = "start"  #: the mesh index the connection starts at
#: the default mesh index the connection starts at if none is given
DEFAULT_START = 0
MESHES = "meshes"  #: the number of meshes of a connection
COMMENT = "comment"  #: a comment of a row, an instruction, anything

# constants

#: the default type of the knitting pattern set
KNITTING_PATTERN_TYPE = "knitting pattern"


[docs]class ParsingError(ValueError): """Mistake in the provided object to parse. This Error is raised if there is an error during the parsing for :class:`~knittingpattern.Parser.Parser`. """
[docs]class Parser(object): """Parses a knitting pattern set and anything in it."""
[docs] def __init__(self, specification): """Create a parser with a specification. :param specification: the types and classes to use for the resulting object structure, preferably a :class:`knittingpattern.ParsingSpecification.ParsingSpecification` """ self._spec = specification self._start()
def _start(self): """Initialize the parsing process.""" self._instruction_library = self._spec.new_default_instructions() self._as_instruction = self._instruction_library.as_instruction self._id_cache = {} self._pattern_set = None self._inheritance_todos = [] self._instruction_todos = [] @staticmethod def _to_id(id_): """Converts the argument to a object suitable as an identifier. :return: a hashable object """ return tuple(id_) if isinstance(id_, list) else id_ def _error(self, text): """Raise an error. :raises: a specified ParsingError :param str text: the text to include in the error message """ raise self._spec.new_parsing_error(text)
[docs] def knitting_pattern_set(self, values): """Parse a knitting pattern set. :param dict value: the specification of the knitting pattern set :rtype: knittingpattern.KnittingPatternSet.KnittingPatternSet :raises knittingpattern.KnittingPatternSet.ParsingError: if :paramref:`value` does not fulfill the :ref:`specification <FileFormatSpecification>`. """ self._start() pattern_collection = self._new_pattern_collection() self._fill_pattern_collection(pattern_collection, values) self._create_pattern_set(pattern_collection, values) return self._pattern_set
def _finish_inheritance(self): """Finish those who still need to inherit.""" while self._inheritance_todos: prototype, parent_id = self._inheritance_todos.pop() parent = self._id_cache[parent_id] prototype.inherit_from(parent) def _delay_inheritance(self, prototype, parent_id): """Add a deleyed inheritance that is ti be resolved later. When calling :meth:`_finish_inheritance` this inheritance chain shall be resolved. """ self._inheritance_todos.append((prototype, parent_id)) def _finish_instructions(self): """Finish those who still need to inherit.""" while self._instruction_todos: row = self._instruction_todos.pop() instructions = row.get(INSTRUCTIONS, []) row.instructions.extend(instructions) def _delay_instructions(self, row): """Add a deleyed inheritance that is ti be resolved later. When calling :meth:`_finish_instructions` this inheritance chain shall be resolved. """ self._instruction_todos.append(row) def _new_pattern_collection(self): """Create a new pattern collection. :return: a new specified pattern collection for :meth:`knitting_pattern_set` """ return self._spec.new_pattern_collection()
[docs] def new_row_collection(self): """Create a new row collection. :return: a new specified row collection for the :meth:`knitting pattern <new_pattern>` """ return self._spec.new_row_collection()
def _fill_pattern_collection(self, pattern_collection, values): """Fill a pattern collection.""" pattern = values.get(PATTERNS, []) for pattern_to_parse in pattern: parsed_pattern = self._pattern(pattern_to_parse) pattern_collection.append(parsed_pattern) def _row(self, values): """Parse a row.""" row_id = self._to_id(values[ID]) row = self._spec.new_row(row_id, values, self) if SAME_AS in values: self._delay_inheritance(row, self._to_id(values[SAME_AS])) self._delay_instructions(row) self._id_cache[row_id] = row return row
[docs] def new_row(self, id_): """Create a new row with an id. :param id_: the id of the row :return: a row :rtype: knittingpattern.Row.Row """ return self._spec.new_row(id_, {}, self)
[docs] def instruction_in_row(self, row, specification): """Parse an instruction. :param row: the row of the instruction :param specification: the specification of the instruction :return: the instruction in the row """ whole_instruction_ = self._as_instruction(specification) return self._spec.new_instruction_in_row(row, whole_instruction_)
def _pattern(self, base): """Parse a pattern.""" rows = self._rows(base.get(ROWS, [])) self._finish_inheritance() self._finish_instructions() self._connect_rows(base.get(CONNECTIONS, [])) id_ = self._to_id(base[ID]) name = base[NAME] return self.new_pattern(id_, name, rows)
[docs] def new_pattern(self, id_, name, rows=None): """Create a new knitting pattern. If rows is :obj:`None` it is replaced with the :meth:`new_row_collection`. """ if rows is None: rows = self.new_row_collection() return self._spec.new_pattern(id_, name, rows, self)
def _rows(self, spec): """Parse a collection of rows.""" rows = self.new_row_collection() for row in spec: rows.append(self._row(row)) return rows def _connect_rows(self, connections): """Connect the parsed rows.""" for connection in connections: from_row_id = self._to_id(connection[FROM][ID]) from_row = self._id_cache[from_row_id] from_row_start_index = connection[FROM].get(START, DEFAULT_START) from_row_number_of_possible_meshes = \ from_row.number_of_produced_meshes - from_row_start_index to_row_id = self._to_id(connection[TO][ID]) to_row = self._id_cache[to_row_id] to_row_start_index = connection[TO].get(START, DEFAULT_START) to_row_number_of_possible_meshes = \ to_row.number_of_consumed_meshes - to_row_start_index meshes = min(from_row_number_of_possible_meshes, to_row_number_of_possible_meshes) # TODO: test all kinds of connections number_of_meshes = connection.get(MESHES, meshes) from_row_stop_index = from_row_start_index + number_of_meshes to_row_stop_index = to_row_start_index + number_of_meshes assert 0 <= from_row_start_index <= from_row_stop_index produced_meshes = from_row.produced_meshes[ from_row_start_index:from_row_stop_index] assert 0 <= to_row_start_index <= to_row_stop_index consumed_meshes = to_row.consumed_meshes[ to_row_start_index:to_row_stop_index] assert len(produced_meshes) == len(consumed_meshes) mesh_pairs = zip(produced_meshes, consumed_meshes) for produced_mesh, consumed_mesh in mesh_pairs: produced_mesh.connect_to(consumed_mesh) def _get_type(self, values): """:return: the type of a knitting pattern set.""" if TYPE not in values: self._error("No pattern type given but should be " "\"{}\"".format(KNITTING_PATTERN_TYPE)) type_ = values[TYPE] if type_ != KNITTING_PATTERN_TYPE: self._error("Wrong pattern type. Type is \"{}\" " "but should be \"{}\"" "".format(type_, KNITTING_PATTERN_TYPE)) return type_ def _get_version(self, values): """:return: the version of :paramref:`values`.""" return values[VERSION] def _create_pattern_set(self, pattern, values): """Create a new pattern set.""" type_ = self._get_type(values) version = self._get_version(values) comment = values.get(COMMENT) self._pattern_set = self._spec.new_pattern_set( type_, version, pattern, self, comment )
[docs]def default_parser(): """The parser with a default specification. :return: a parser using a :class:`knittingpattern.ParsingSpecification.DefaultSpecification` :rtype: knittingpattern.Parser.Parser """ from .ParsingSpecification import DefaultSpecification specification = DefaultSpecification() return Parser(specification)
__all__ = ["Parser", "ID", "NAME", "TYPE", "VERSION", "INSTRUCTIONS", "SAME_AS", "PATTERNS", "ROWS", "CONNECTIONS", "FROM", "TO", "START", "DEFAULT_START", "MESHES", "COMMENT", "ParsingError", "default_parser"]