Source code for simudo.pyaml.pyaml1

import ast
import re
import string

import yaml
import yamlordereddictloader
from cached_property import cached_property

from .astglobals import find_globals
from .multidict import multidict_items


[docs]class BaseCustomTag(object): yaml_classes = () yaml_tag = None
[docs] @classmethod def register_in_loader(cls, loader): yt = cls.yaml_tag if yt is not None: loader.add_constructor(yt, cls.from_yaml)
[docs] @classmethod def register_in_dumper(cls, dumper): for klass in cls.yaml_classes: dumper.add_multi_representer(klass, cls.to_yaml)
[docs] @classmethod def from_yaml(cls, loader, node): raise NotImplementedError()
[docs] @classmethod def to_yaml(cls, dumper, data): raise NotImplementedError()
[docs]class InitKwSetattr(object): def __init__(self, dict): for k, v in dict.items(): setattr(self, k, v)
[docs]class CodeTag(InitKwSetattr): auto_cache = True env = 'default' code = None
[docs]class YCodeTag(BaseCustomTag): yaml_tag = '!c'
[docs] @classmethod def from_yaml(cls, loader, node): return CodeTag(dict(code=loader.construct_scalar(node)))
[docs]class YCode2Tag(BaseCustomTag): yaml_tag = '!c2'
[docs] @classmethod def from_yaml(cls, loader, node): return CodeTag(loader.construct_mapping(node))
[docs]class XLoader(yamlordereddictloader.Loader): pass
for kl in [YCodeTag, YCode2Tag]: kl.register_in_loader(XLoader) LINE_START_RE = re.compile('^', re.M)
[docs]class CodeGenerator(): YAMLLoader = XLoader depth = 0 indent_spaces = 2
[docs] @classmethod def pyaml_to_python(cls, tree=None, stream=None): ''' use this method! ''' cg = cls() if stream: tree = yaml.load(stream, Loader=cg.YAMLLoader) cg.process(tree) # you probably have a syntax error return ''.join(cg.result)
@cached_property def result(self): return []
[docs] def emit_str(self, text): self.result.append(text)
[docs] def emit(self, line): for line_ in line.split('\n'): self.emit_str(self.get_current_indent()) self.emit_str(line_) self.emit_str('\n')
[docs] def get_current_indent(self): return ' '*(self.indent_spaces*self.depth)
[docs] def process(self, yaml_tree): self.emit_str("""\ # WARNING: This file was autogenerated from a YAML source. # All modifications to this file WILL be lost.\n\n""") preamble = yaml_tree.get('preamble', '') toplevel = __name__.rpartition('.')[0] preamble = string.Template(preamble).substitute( pyaml=toplevel, helper=toplevel+'.helper') self.python_preamble = preamble self.emit(preamble) self.emit_str('\n') for cls, clsname in yaml_tree.get('classes', {}).items(): self.process_class(cls, clsname)
[docs] def as_isolated_method_in_module(self, preamble, code): # why is the preamble needed? in case the user makes changes # to the syntax, e.g. via `from __future__ import braces`, and # thus the `code` wouldn't parse without said changes indented_code = LINE_START_RE.sub(' ', code) modcode = ''.join((preamble, '\ndef f(self):\n', indented_code)) return modcode
[docs] def process_code(self, name, codetag, prefix=''): self.emit('def {}(self):'.format(name)) self.depth += 1 code = codetag.code.rstrip() if code.startswith("%"): code = code[1:] else: code = 'return (\n{}\n)'.format(LINE_START_RE.sub(' ', code)) # FIXME: preamble might contain functions and therefore global refs globals_ = find_globals(ast.parse( self.as_isolated_method_in_module(self.python_preamble, code))) globals_ -= {'_', 'AttrPrefixProxy', 'globals', '___globals'} self.emit('_ = AttrPrefixProxy(self, {!r})\n' '___globals = globals()'.format(prefix)) for g in globals_: self.emit("{} = self.PYAML_get_env({!r}, {!r}, ___globals)".format( g, codetag.env, g)) self.emit(code) self.depth -= 1 self.emit_str('\n') self.emit_eval_auto_call(name, codetag)
[docs] def emit_eval_auto_call(self, name, codetag): # FIXME: cleaner way to do this before, sep, after = name.partition('EVAL_') if not before and sep: self.emit("@property\ndef {}(self):".format(after)) self.depth += 1 self.emit("return self.{}()".format(name)) self.depth -= 1 self.emit_str('\n')
[docs] def process_eval(self, destination_prefix, lookup_prefix, tree): for origkey, value in multidict_items(tree): key = lookup_prefix + origkey if isinstance(value, dict): self.process_eval( destination_prefix=destination_prefix, lookup_prefix=key, tree=value) elif isinstance(value, CodeTag): self.process_code(name=destination_prefix+key, codetag=value, prefix=lookup_prefix) else: raise TypeError("unknown type {!r} for node {!r} key {!r}" .format(type(value), value, origkey))
[docs] def process_class(self, name, tree): inherit = tree.get('inherit', ()) self.emit('class {}({}):'.format(name, ', '.join(inherit))) self.depth += 1 for key, value in multidict_items(tree): if key == 'eval': self.process_eval( destination_prefix='EVAL_', lookup_prefix='', tree=value) elif key == 'env': self.process_eval( destination_prefix='ENV_', lookup_prefix='', tree=value) self.depth -= 1 self.emit_str('\n')
# s = open('simudo/test/pyaml1_test1.yaml', 'rt') # print(CodeGenerator.pyaml_to_python(stream=s))