def parse_line(self, line):
        """Parse a line of text.

        A format contains fields, within curly brackets, and free text.
        Maximum one 'variable width' field is allowed per line.
        You cannot use the '{' or '}' characters in the various text/... fields.

        Format:
            '''{<field_kind>[ <field_option>,<field_option]} text {...}'''

        Example:
            '''{song text="%(artist)s",speed=4} {elapsed}'''
            '''{song text="%(title)s",speed=2} {mode}'''

        Args:
            line (str): the text to parse

        Returns:
            PatternLine: the parsed line pattern
        """
        logger.debug('Parsing line %s', line)

        OUT_FIELD = 0
        IN_FIELD_KIND = 1
        IN_FIELD_OPTION_NAME = 2
        IN_FIELD_OPTION_VALUE = 3

        class ParserState(object):
            """Holds the current state of the parser.

            Attributes:
                quote (str): the current quote character, or None
                escaping (bool): whether the next character should be escaped
                block (char list): the content of the current 'block'
                kind (str): the kind of the current field, or ''
                option_name (str): the name of the current option, or ''
                options (dict(str => str)): maps option name to option value for
                    the current field
                state (int): state of the parser,one of OUT_FIELD/IN_FIELD_*
                fields ((str, dict(str => str)) list): list of fields, as
                    (kind, options) tuples.
            """

            def __init__(self, logger=None):
                self.quote = None
                self.escaping = False
                self.block = []
                self.kind = ''
                self.option_name = ''
                self.options = {}
                self.state = OUT_FIELD
                self.fields = []
                if not logger:
                    logger = logging.getLogger('%s.parser' % __name__)
                self.logger = logger

            def _reset(self):
                """Reset buffered state (quote/escape/block)."""
                self.quote = None
                self.escaping = False
                self.block = []

            def _register_field(self, kind, options):
                """Register a completed field."""
                self.fields.append((kind, dict(options)))

            def debug(self, msg, *args, **kwargs):
                """Print a debug message."""
                self.logger.debug(msg, *args, **kwargs)

            def save_fixed_text(self):
                """Register a completed, fixed text, field."""
                assert self.state == OUT_FIELD
                self._register_field(FIXED_TEXT_FIELD, {'text': ''.join(self.block)})

            def enter_field(self):
                """Enter a new field."""
                self.debug('Entering new field')
                self.state = IN_FIELD_KIND
                self.kind = ''
                self.options = {}
                self.option_name = ''
                self._reset()

            def leave_kind(self):
                """Leave the field kind."""
                self.state = IN_FIELD_OPTION_NAME
                self.kind = ''.join(self.block)
                self.debug("Got widget kind '%s'", self.kind)
                self._reset()

            def leave_option_name(self):
                """Leave an option name."""
                self.state = IN_FIELD_OPTION_VALUE
                self.option_name = ''.join(self.block)
                self.debug("Got option name '%s' for '%s'", self.option_name, self.kind)
                self._reset()

            def leave_option_value(self):
                """Leave an option value."""
                self.state = IN_FIELD_OPTION_NAME
                option_value = ''.join(self.block)
                self.options[self.option_name] = option_value
                self.debug("Got option '%s=%s' for '%s'", self.option_name, option_value, self.kind)
                self._reset()

            def leave_field(self):
                """Leave a field definition."""
                self.state = OUT_FIELD
                self._register_field(self.kind, self.options)
                self.debug(
                    "Got widget '%s(%s)'", self.kind,
                    ', '.join('%s=%r' % it for it in self.options.items()),
                )
                self._reset()

        st = ParserState()

        for pos, char in enumerate(line):

            # Escaping
            if st.escaping:
                st.escaping = False
                st.block.append(char)

            elif char == '\\':
                st.escaping = True

            # Quoting
            elif char in ['"', "'"]:
                if st.state == IN_FIELD_OPTION_VALUE:
                    if st.quote:  # Already in a quoted block
                        if char == st.quote:
                            st.leave_option_value()
                        else:
                            st.block.append(char)

                    elif not st.block:  # First char of the block
                        st.quote = char
                        continue

                    else:
                        raise FormatError("Unexpected '%s' at %d in %s" % (char, pos, line))

                elif st.state == OUT_FIELD:
                    st.block.append(char)

                else:
                    raise FormatError("Unexpected '%s' at %d in %s" % (char, pos, line))

            # Entering a field
            elif char == '{':
                if st.state == OUT_FIELD:
                    if st.block:
                        st.save_fixed_text()
                    st.enter_field()

                elif st.state == IN_FIELD_OPTION_VALUE and st.quote:
                    st.block.append(char)

                else:
                    raise FormatError("Unexpected '{' at %d in %s" % (pos, line))

            # Leaving a field
            elif char == '}':
                if st.state == IN_FIELD_KIND:
                    st.leave_kind()
                    st.leave_field()

                elif st.state == IN_FIELD_OPTION_NAME:
                    raise FormatError("Missing option value for %s at %d in %s" % (''.join(st.block), pos, line))

                elif st.state == IN_FIELD_OPTION_VALUE:
                    if st.quote:
                        st.block.append(char)
                    else:
                        st.leave_option_value()
                        st.leave_field()

                elif st.state == OUT_FIELD:
                    raise FormatError("Unexpected '}' at %d in %s" % (pos, line))

            # Between kind and option name
            elif char == ' ':
                if st.state == IN_FIELD_KIND:
                    if not st.block:
                        raise FormatError("Missing field kind at %s in %s" % (pos, line))

                    st.leave_kind()

                elif st.state == IN_FIELD_OPTION_VALUE and st.quote:
                    st.block.append(char)

                elif st.state == OUT_FIELD:
                    st.block.append(char)

                else:
                    raise FormatError("Unexpected ' ' at %d in %s" % (pos, line))

            # Between options
            elif char == ',':
                if st.state == IN_FIELD_OPTION_NAME:
                    if st.block:
                        raise FormatError("Missing option value for %s at %d in %s" % (''.join(st.block), pos, line))
                    else:
                        # At the beginning of a new option
                        continue

                elif st.state == IN_FIELD_KIND:
                    raise FormatError("Unexpected ',' in field definition %s at %d in %s" % (st.kind, pos, line))

                elif st.state == IN_FIELD_OPTION_VALUE:
                    if st.quote:
                        st.block.append(char)
                    elif st.block:
                        st.leave_option_value()
                    else:
                        raise FormatError("Missing option value for %s at %d in %s" % (st.option_name, pos, line))

                else:  # OUT_FIELD
                    st.block.append(char)

            # Between option name and option value
            elif char == '=':
                if st.state == IN_FIELD_OPTION_NAME:
                    if st.block:
                        st.leave_option_name()

                    else:
                        raise FormatError("Missing option name at %d in %s" % (pos, line))

                elif st.state == OUT_FIELD:
                    st.block.append(char)

                elif st.state == IN_FIELD_OPTION_VALUE:
                    if st.quote:
                        st.block.append(char)

                    elif not st.block:
                        # At the beginning of an option
                        continue

                    else:
                        raise FormatError(
                            "Unexpected '=' in option value for %s at %d in %s"
                            % (st.option_name, pos, line))

                else:
                    raise FormatError("Unexpected '=' at %d in %s" % (pos, line))

            # Everything else
            else:
                st.block.append(char)

        # All input parsed
        if st.state != OUT_FIELD:
            raise FormatError("Unclosed field at %d in '%s'; block: '%s'" % (pos, line, ''.join(st.block)))

        if st.block:
            st.save_fixed_text()

        return st.fields
def parse_line ( self , line ) : logger . debug ( 'Parsing line %s' , line ) OUT_FIELD = 0 IN_FIELD_KIND = 1 IN_FIELD_OPTION_NAME = 2 IN_FIELD_OPTION_VALUE = 3 class ParserState ( object ) : def __init__ ( self , logger = None ) : self . quote = None self . escaping = False self . block = [ ] self . kind = '' self . option_name = '' self . options = { } self . state = OUT_FIELD self . fields = [ ] if not logger : logger = logging . getLogger ( '%s.parser' % __name__ ) self . logger = logger def _reset ( self ) : self . quote = None self . escaping = False self . block = [ ] def _register_field ( self , kind , options ) : self . fields . append ( ( kind , dict ( options ) ) ) def debug ( self , msg , * args , ** kwargs ) : self . logger . debug ( msg , * args , ** kwargs ) def save_fixed_text ( self ) : assert self . state == OUT_FIELD self . _register_field ( FIXED_TEXT_FIELD , { 'text' : '' . join ( self . block ) } ) def enter_field ( self ) : self . debug ( 'Entering new field' ) self . state = IN_FIELD_KIND self . kind = '' self . options = { } self . option_name = '' self . _reset ( ) def leave_kind ( self ) : self . state = IN_FIELD_OPTION_NAME self . kind = '' . join ( self . block ) self . debug ( "Got widget kind '%s'" , self . kind ) self . _reset ( ) def leave_option_name ( self ) : self . state = IN_FIELD_OPTION_VALUE self . option_name = '' . join ( self . block ) self . debug ( "Got option name '%s' for '%s'" , self . option_name , self . kind ) self . _reset ( ) def leave_option_value ( self ) : self . state = IN_FIELD_OPTION_NAME option_value = '' . join ( self . block ) self . options [ self . option_name ] = option_value self . debug ( "Got option '%s=%s' for '%s'" , self . option_name , option_value , self . kind ) self . _reset ( ) def leave_field ( self ) : self . state = OUT_FIELD self . _register_field ( self . kind , self . options ) self . debug ( "Got widget '%s(%s)'" , self . kind , ', ' . join ( '%s=%r' % it for it in self . options . items ( ) ) , ) self . _reset ( ) st = ParserState ( ) for pos , char in enumerate ( line ) : if st . escaping : st . escaping = False st . block . append ( char ) elif char == '\\' : st . escaping = True elif char in [ '"' , "'" ] : if st . state == IN_FIELD_OPTION_VALUE : if st . quote : if char == st . quote : st . leave_option_value ( ) else : st . block . append ( char ) elif not st . block : st . quote = char continue else : raise FormatError ( "Unexpected '%s' at %d in %s" % ( char , pos , line ) ) elif st . state == OUT_FIELD : st . block . append ( char ) else : raise FormatError ( "Unexpected '%s' at %d in %s" % ( char , pos , line ) ) elif char == '{' : if st . state == OUT_FIELD : if st . block : st . save_fixed_text ( ) st . enter_field ( ) elif st . state == IN_FIELD_OPTION_VALUE and st . quote : st . block . append ( char ) else : raise FormatError ( "Unexpected '{' at %d in %s" % ( pos , line ) ) elif char == '}' : if st . state == IN_FIELD_KIND : st . leave_kind ( ) st . leave_field ( ) elif st . state == IN_FIELD_OPTION_NAME : raise FormatError ( "Missing option value for %s at %d in %s" % ( '' . join ( st . block ) , pos , line ) ) elif st . state == IN_FIELD_OPTION_VALUE : if st . quote : st . block . append ( char ) else : st . leave_option_value ( ) st . leave_field ( ) elif st . state == OUT_FIELD : raise FormatError ( "Unexpected '}' at %d in %s" % ( pos , line ) ) elif char == ' ' : if st . state == IN_FIELD_KIND : if not st . block : raise FormatError ( "Missing field kind at %s in %s" % ( pos , line ) ) st . leave_kind ( ) elif st . state == IN_FIELD_OPTION_VALUE and st . quote : st . block . append ( char ) elif st . state == OUT_FIELD : st . block . append ( char ) else : raise FormatError ( "Unexpected ' ' at %d in %s" % ( pos , line ) ) elif char == ',' : if st . state == IN_FIELD_OPTION_NAME : if st . block : raise FormatError ( "Missing option value for %s at %d in %s" % ( '' . join ( st . block ) , pos , line ) ) else : continue elif st . state == IN_FIELD_KIND : raise FormatError ( "Unexpected ',' in field definition %s at %d in %s" % ( st . kind , pos , line ) ) elif st . state == IN_FIELD_OPTION_VALUE : if st . quote : st . block . append ( char ) elif st . block : st . leave_option_value ( ) else : raise FormatError ( "Missing option value for %s at %d in %s" % ( st . option_name , pos , line ) ) else : st . block . append ( char ) elif char == '=' : if st . state == IN_FIELD_OPTION_NAME : if st . block : st . leave_option_name ( ) else : raise FormatError ( "Missing option name at %d in %s" % ( pos , line ) ) elif st . state == OUT_FIELD : st . block . append ( char ) elif st . state == IN_FIELD_OPTION_VALUE : if st . quote : st . block . append ( char ) elif not st . block : continue else : raise FormatError ( "Unexpected '=' in option value for %s at %d in %s" % ( st . option_name , pos , line ) ) else : raise FormatError ( "Unexpected '=' at %d in %s" % ( pos , line ) ) else : st . block . append ( char ) if st . state != OUT_FIELD : raise FormatError ( "Unclosed field at %d in '%s'; block: '%s'" % ( pos , line , '' . join ( st . block ) ) ) if st . block : st . save_fixed_text ( ) return st . fields
Parse a line of text.

        A format contains fields, within curly brackets, and free text.
        Maximum one 'variable width' field is allowed per line.
        You cannot use the '{' or '}' characters in the various text/... fields.

        Format:
            '''{<field_kind>[ <field_option>,<field_option]} text {...}'''

        Example:
            '''{song text="%(artist)s",speed=4} {elapsed}'''
            '''{song text="%(title)s",speed=2} {mode}'''

        Args:
            line (str): the text to parse

        Returns:
            PatternLine: the parsed line pattern