properties.py

'''\
Properties Classes.

classes:
    Properties              - Properties base class
    ApplicationProperties   - name=value pairs (values are all strings)
    ObjectProperties        - name=value pairs (values are tuples of tokens)
    QueryProperties         - name(value) specifications

Developer@Sonnack.com
February 2014
'''
####################################################################################################
from sys import stdoutstderrargv
from lexer import SourceFileLexerLexerLine
####################################################################################################

ListToString = lambda lst: reduce(lambda acc,s: acc+slst'')


##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
class Properties (object):
    '''\
Properties base class.

properties:
    .props          - properties collection
    .filename       - properties file name

methods:
    [N]             - Nth argument
    .len()          - number of arguments
    .str()          - string representation
    .repr()         - JSON-like representation
'''
    def keys_sorted_by_name (self):
        '''All items sorted by name.'''
        return sorted(self.props.keys(), key=lambda k: str(k).lower())

    def items_sorted_by_name (self):
        '''All items sorted by name.'''
        return sorted(self.props.items(), key=lambda (k,v): str(k).lower())

    def _get_tok (self):
        '''Get Character.'''
        tok = self.lex.get_token()
        #print 'token:',tok
        return tok

    def _unget_tok (selftok):
        '''Unget Character.'''
        self.lex.unget_token(tok)

    def __getitem__ (selfk):
        '''Get Properties Property.'''
        return self.props[k]

    def __iter__ (self):
        '''Get Properties Property iterator.'''
        return iter(self.props)

    def __cmp__ (selfother):
        '''Compare two Properties objects (using their Source).'''
        return cmp(self.srcother.src)

    def __len__ (self):
        '''Length of an Properties object is number of Property objects.'''
        return len(self.props)

    def __str__ (self):
        '''String version.'''
        ss = map(lambda (k,v): '%s: "%s"\n' % (k,str(v)), self.items_sorted_by_name())
        return reduce(lambda acc,s: acc+sss'')

    def __repr__ (self):
        '''JSON-ish version.'''
        k = self.keys_sorted_by_name()
        s = '{Properties:{len:%d, keys:%s, src:%s, id:%s}}'
        t = (len(self), krepr(self.src), hex(id(self)))
        return s % t

    def __init__ (selffilenameLexClass=Lexer):
        '''Create a new Properties instance.'''
        self.props = {}
        self.filename = filename
        self.src = SourceFile(self.filename)
        self.lex = LexClass(self.src)

    E1 = "Parser: ERROR *** Oh, No!! There's a Syntax Error! ***"
    E2 = "error: %s"
    E3 = "file: %s"
    E4 = "line: %d, char: %d  (chars read: %d)"

##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
class ApplicationProperties (Properties):
    '''\
Application Properties Class.

properties:

methods:
    [N]             - Nth argument
    .len()          - number of arguments
    .str()          - string representation
    .repr()         - JSON-like representation
'''
    def _parse_file (self):
        tok = self._get_tok()
        while tok:
            # Skip blank lines (and comments)...
            if tok[0] == Lexer.NewlineToken:
                tok = self._get_tok()
                continue
            # Property name...
            if tok[0not in [Lexer.NameTokenLexer.StringToken]:
                raise SyntaxError, ('Expected Property Name. [%s]' % str(tok))
            prop_name = tok[1]
            # Separator...
            tok = self._get_tok()
            if tok[0] != Lexer.SymbolToken:
                raise SyntaxError, ('Expected delimiter. [%s]' % str(tok))
            if tok[1not in [':''=']:
                raise SyntaxError, ('Unexpected delimiter. [%s]' % str(tok))
            # Value...
            ch = self.lex.skip_whitespace()
            if ch in self.lex.sdq_chars:
                prop_value = self._get_tok()
            else:
                prop_value = self.lex.get_rest_of_line()
            # Add Property to collection...
            self.props[prop_name] = prop_value[1]
            # Continue...
            tok = self._get_tok()
            ## Loop!! -->>

    def __repr__ (self):
        '''JSON-ish version.'''
        r = super(ApplicationProperties,self).__repr__()
        s = '{ApplicationProperties:{parent:%s, id:%s}}'
        t = (rhex(id(self)))
        return s % t

    def __init__ (selffilename):
        '''Create a new Application Properties instance.'''
        super(ApplicationProperties,self).__init__(filenameLexerLine)
        # Parse file...
        try:
            self._parse_file()
        except IndexError:
            pass
        except SyntaxError as e:
            print >> stderrProperties.E1
            print >> stderrProperties.E2 % str(e)
            print >> stderrProperties.E3 % self.src.filename
            print >> stderrProperties.E4 % (self.src.linesself.src.cpself.src.chars)


##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
class ObjectProperties (Properties):
    '''\
Object Properties Class.

properties:

methods:
    [N]             - Nth argument
    .len()          - number of arguments
    .str()          - string representation
    .repr()         - JSON-like representation
'''
    def _parse_file (self):
        tok = self._get_tok()
        while tok:
            # Skip blank lines (and comments)...
            if tok[0] == Lexer.NewlineToken:
                tok = self._get_tok()
                continue
            # Property name...
            if tok[0not in [Lexer.NameTokenLexer.StringToken]:
                raise SyntaxError, ('Expected Property Name. [%s]' % str(tok))
            prop_name = tok[1]
            # Separator...
            tok = self._get_tok()
            if tok[0] != Lexer.SymbolToken:
                raise SyntaxError, ('Expected delimiter. [%s]' % str(tok))
            if tok[1not in [':''=']:
                raise SyntaxError, ('Unexpected delimiter. [%s]' % str(tok))
            # Value...
            prop_values = []
            tok = self._get_tok()
            while tok[0] != Lexer.NewlineToken:
                if tok[0in [Lexer.NumberTokenLexer.DateToken]:
                    val = tok[2]
                else:
                    val = tok[1]
                prop_values.append(val)
                tok = self._get_tok()
                ## Loop!! -->>
            # Add Property to collection...
            self.props[prop_name] = tuple([len(prop_values)]+prop_values)
            # Continue...
            tok = self._get_tok()
            ## Loop!! -->>

    def __repr__ (self):
        '''JSON-ish version.'''
        r = super(ObjectProperties,self).__repr__()
        s = '{ObjectProperties:{parent:%s, id:%s}}'
        t = (rhex(id(self)))
        return s % t

    def __init__ (selffilename):
        '''Create a new Properties instance.'''
        super(ObjectProperties,self).__init__(filename)
        # Parse file...
        try:
            self._parse_file()
        except IndexError:
            pass
        except SyntaxError as e:
            print >> stderrProperties.E1
            print >> stderrProperties.E2 % str(e)
            print >> stderrProperties.E3 % self.src.filename
            print >> stderrProperties.E4 % (self.src.linesself.src.cpself.src.chars)


##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
class QueryProperties (Properties):
    '''\
Query Properties Class.

properties:
    .text
    .args

methods:
    [N]             - Nth argument
    .len()          - number of arguments
    .str()          - string representation
    .repr()         - JSON-like representation
'''
    def _parse_file (self):
        '''| <file> := [<property-name> <property-value>]* |'''
        #print 'parse_file:'
        tok = self._get_tok()
        while tok:
            # Expecting a Name Token...
            if tok[0] == Lexer.NameToken:
                prop_name = tok[1]
                prop_value = self._parse_prop_value()
                if prop_name not in self.props:
                    self.props[prop_name] = [prop_value]
                else:
                    self.props[prop_name].append(prop_value)
                # Get next Token and continue...
                tok = self._get_tok()
                continue
            # If we get here, the token wasn't recognized...
            raise SyntaxError, ('Unexpected token. [%s]' % str(tok))
        # protection!
        raise RuntimeError'Unexpected Token loop exit!'

    def _parse_prop_value (self):
        '''| <property-value> := "(" <item> [, <item>]* ")" |'''
        #print 'parse_prop_value:'
        tok = self._get_tok()
        while tok:
            # Expecting a '(' Token...
            if tok[1] == '(':
                # Get Property values (might be empty list)...
                return self._parse_value_list()
            #
            # If we get here, the token wasn't recognized...
            raise SyntaxError, ('Expected "(" to begin property value! [%s]' % str(tok))
        # protection!
        raise RuntimeError'Unexpected Token loop exit!'

    def _parse_value_list (self):
        '''| <item> ")" or <item> "," |'''
        #print 'parse_value_list:'
        # Get next Token...
        tok = self._get_tok()
        # Could be a ')' Token to end the value list...
        if tok[1] == ')':
            return []
        # Got a value, so create value-list...
        prop_values = []
        while tok:
            # Parse the value and add to list...
            val = self._parse_value_item(tok)
            prop_values.append(val)
            # Get next Token...
            tok = self._get_tok()
            # A comma (',') Token indicates more values...
            if tok[1] == ',':
                tok = self._get_tok()
                continue
            # A ')' Token indicates end of value(s)...
            if tok[1] == ')':
                return prop_values
            # If we get here, the token wasn't recognized...
            raise SyntaxError, ('Expected ")" to end property value! [%s]' % str(tok))
        # protection!
        raise RuntimeError'Unexpected Token loop exit!'

    def _parse_value_item (selfval_tok):
        '''| <item> := <constant> or <name> or <sub-property> |'''
        #print 'parse_value_item:', val_tok
        # Token is Number or Date; add to value list..
        if val_tok[0in [Lexer.NumberTokenLexer.DateToken]:
            return (val_tok[3], val_tok[2])
        # Token is String (a little weird, but whatever); add to value list..
        if val_tok[0] == Lexer.StringToken:
            return ('string'val_tok[1])
        # Token is Name; could be a constant or a sub-property..
        if val_tok[0] == Lexer.NameToken:
            prop_name = val_tok[1]
            # Get next Token for sub-property test...
            tok = self._get_tok()
            # If it has an opening parenthesis, it's a property value...
            if tok[1] == '(':
                # Handle sub-property...
                prop_value = self._parse_value_list()
                return ('f:'+prop_nameprop_value)
            # Wasn't a sub-property, so UNGET the  Token and return the name...
            self._unget_tok(tok)
            return ('name'prop_name)
        # If we get here, the token wasn't recognized...
        raise SyntaxError, ('Expected value token! [%s]' % str(val_tok))

    def __str__ (self):
        '''String version.'''
        s1 = '%s:\n%s'
        s2 = '    %s\n'
        ss = map(lambda (k,v): s1 % (kListToString(map(lambda m: s2 % str(m), v))), self.items_sorted_by_name())
        return ListToString(ss)

    def __repr__ (self):
        '''JSON-ish version.'''
        r = super(QueryProperties,self).__repr__()
        s = '{QueryProperties:{parent:%s, id:%s}}'
        t = (rhex(id(self)))
        return s % t

    def __init__ (selffilename):
        '''Create a new Properties instance.'''
        super(QueryProperties,self).__init__(filename)
        # Parse file...
        self.lex.nl_flag = False
        try:
            self._parse_file()
        except IndexError:
            pass
        except SyntaxError as e:
            print >> stderrProperties.E1
            print >> stderrProperties.E2 % str(e)
            print >> stderrProperties.E3 % self.src.filename
            print >> stderrProperties.E4 % (self.src.linesself.src.cpself.src.chars)



####################################################################################################
'''eof'''