Source code for c4dtools.resource.menuparser

# coding: utf-8
#
# Copyright (c) 2012-2013, Niklas Rosenstein
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be interpreted
# as representing official policies,  either expressed or implied, of
# the FreeBSD Project.
r"""
c4dtools.resource.menuparser
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

New in 1.2.0.

This module implements parsing a Menu-resource in order to attach the
resource to a dialog.
"""

import c4d
import scan
import string

from c4dtools.resource import Resource

try:
    import cStringIO as StringIO
except ImportError:
    import StringIO

class MenuNode(object):

    # Always a MenuContainer instance or None.
    parent = None

    def _assert_symbol(self, symbol, res):
        if not res.has_symbol(symbol):
            raise AttributeError('Resource does not have required symbol %r' %
                                 symbol)

    def _compare_symbol(self, node_id, res):
        r"""
        Sub-procedure for sub-classes implementing a ``symbol`` attribute.
        """

        if self.symbol:
            if node_id == self.symbol:
                return True

            self._assert_symbol(self.symbol, res)
            if res.get(self.symbol) == node_id:
                return True

        return False

    def render(self, dialog, res):
        pass

    def find_node(self, node_id, res):
        r"""
        New in 1.2.7. Find a node by it's identifier.
        """

        return None

    def remove(self):
        r"""
        New in 1.2.8. Remove the node from the tree.
        """

        if self.parent:
            self.parent.children.remove(self)
            self.parent = None

    def copy(self):
        r"""
        New in 1.2.8. Return a copy of the Menu tree.
        """
        raise NotImplementedError


class MenuSeperator(MenuNode):

    def render(self, dialog, res):
        dialog.MenuAddSeparator()

    def copy(self):
        return MenuSeperator()

class MenuCommand(MenuNode):

    def __init__(self, command_id=None, symbol=None):
        super(MenuCommand, self).__init__()
        assert command_id or symbol
        self.command_id = command_id
        self.symbol = symbol

    def render(self, dialog, res):
        command_id = self.command_id
        if not command_id:
            self._assert_symbol(self.symbol, res)
            command_id = res.get(self.symbol)

        dialog.MenuAddCommand(command_id)

    def find_node(self, node_id, res):
        if self.command_id and self.command_id == node_id:
            return self
        elif self._compare_symbol(node_id, res):
            return self

        return None

    def copy(self):
        return MenuCommand(self.command_id, self.symbol)

class MenuString(MenuNode):

    def __init__(self, symbol):
        super(MenuString, self).__init__()
        self.symbol = symbol

    def render(self, dialog, res):
        self._assert_symbol(self.symbol, res)
        dialog.MenuAddString(*res.string.get(self.symbol).both)

    def find_node(self, node_id, res):
        if self._compare_symbol(node_id, res):
            return self
            
    def copy(self):
        return MenuString(self.symbol)


class MenuSet(scan.TokenSet):

    def on_init(self):
        digits = string.digits
        letters = string.letters + '_'

        self.add('comment', 2, scan.HashComment(skip=True))
        self.add('menu',    1, scan.Keyword('MENU'))
        self.add('command', 1, scan.Keyword('COMMAND'))
        self.add('bopen',   1, scan.Keyword('{'))
        self.add('bclose',  1, scan.Keyword('}'))
        self.add('end',     1, scan.Keyword(';'))
        self.add('sep',     0, scan.CharacterSet('-'))
        self.add('symbol',  0, scan.CharacterSet(letters, letters + digits))
        self.add('number',  0, scan.CharacterSet(digits))

class MenuParser(object):

    def __init__(self, **options):
        super(MenuParser, self).__init__()
        self.options = options

    def __getitem__(self, name):
        return self.options[name]

    def _assert_type(self, token, *tokentypes):
        for tokentype in tokentypes:
            if not token or token.type != tokentype:
                raise scan.UnexpectedTokenError(token, tokentypes)

    def _command(self, lexer):
        self._assert_type(lexer.token, lexer.t_command)
        lexer.read_token()

        command_id = None
        symbol_name = None
        if lexer.token.type == lexer.t_number:
            command_id = int(lexer.token.value)
        elif lexer.token.type == lexer.t_symbol:
            symbol_name = lexer.token.value
        else:
            raise scan.UnexpectedTokenError(lexer.token, [lexer.t_number,
                    lexer.t_symbol])

        return MenuCommand(command_id, symbol_name)

    def _menu(self, lexer):
        self._assert_type(lexer.token, lexer.t_menu)
        lexer.read_token()
        self._assert_type(lexer.token, lexer.t_symbol)
        items = MenuContainer(lexer.token.value)
        lexer.read_token()
        self._assert_type(lexer.token, lexer.t_bopen)
        lexer.read_token()

        while lexer.token and lexer.token.type != lexer.t_bclose:

            require_endstmt = True
            if lexer.token.type == lexer.t_menu:
                item = self._menu(lexer)
                require_endstmt = False
            elif lexer.token.type == lexer.t_command:
                item = self._command(lexer)
            elif lexer.token.type == lexer.t_sep:
                item = MenuSeperator()
            elif lexer.token.type == lexer.t_symbol:
                item = MenuString(lexer.token.value)
            else:
                raise scan.UnexpectedTokenError(lexer.token, [lexer.t_menu,
                        lexer.t_command, lexer.t_sep, lexer.t_symbol])

            items.add(item)

            if require_endstmt:
                lexer.read_token()
                self._assert_type(lexer.token, lexer.t_end)
                lexer.read_token()

        self._assert_type(lexer.token, lexer.t_bclose)
        lexer.read_token()
        return items

    def parse(self, lexer):
        menus = MenuContainer(None)
        while lexer.token:
            menu = self._menu(lexer)
            menus.add(menu)
        return menus


[docs]def parse_file(filename): r""" Parse a ``*.menu`` file from the local file-system. Returns a list of :class:`MenuContainer` objects. """ return parse_fileobject(open(filename, 'rb'))
[docs]def parse_string(data): r""" Parse a ``*.menu`` formatted string. Returns a list of of :class:`MenuContainer` objects. """ fl = StringIO.StringIO(data) fl.seek(0) return parse_fileobject(fl)
[docs]def parse_fileobject(fl): r""" Parse a file-like object. Returns a list of :class:`MenuContainer` objects. """ scanner = scan.Scanner(fl) scanner.read() lexer = scan.Lexer(scanner, MenuSet()) lexer.read_token() parser = MenuParser() return parser.parse(lexer)
[docs]def parse_and_prepare(filename, dialog, res): r""" Like :func:`parse_file`, but renders the parsed menus to the dialog. """ if not isinstance(dialog, c4d.GeDialog): raise TypeError('Expected c4d.gui.GeDialog as 2nd argument.') if not isinstance(res, Resource): raise TypeError('Expected c4dtools.resource.Resource as 3rd argument.') menu = parse_file(filename) menu.render(dialog, res)