from __future__ import print_function import ast import os import os.path import sys import pycommand __version__ = '0.1.0' def get_setting_specs(module): sys.path.insert(0, os.getcwd()) return __import__(module) def write_settings(specs, out, values=None): values = values or {} print('# {file} file generated by topple v{version}' .format(file=specs.OUTPUT_FILE, version=__version__), file=out) for key, spec in specs.SETTINGS: value = values.get(key, spec[1]) print(key, repr(value), sep=' = ', file=out) def get_node_value(node): if isinstance(node, ast.Name): return ast.literal_eval(node.id) elif isinstance(node, ast.Str): return node.s elif isinstance(node, ast.Num): return node.n else: return node def get_settings_from(filename): res = {} with open(filename, 'r') as f: tree = ast.parse('\n'.join(f.readlines()), filename) for node in ast.iter_child_nodes(tree): if isinstance(node, ast.Assign): for target in node.targets: res[target.id] = get_node_value(node.value) else: print(node, node._fields) return res class PreviewMixin(object): optionList = ( ('preview', ('p', None, 'Print generated file to stdout')), ) def __new__(cls, *args, **kwargs): cls.optionList = (cls.optionList or tuple()) + PreviewMixin.optionList instance = super(PreviewMixin, cls).__new__(cls, *args, **kwargs) return instance def write(self, output, specs, values=None): preview = self.flags['preview'] if not preview: with open(output, 'w') as f: write_settings(specs, f, values) else: write_settings(specs, sys.stdout, values) class Generate(pycommand.CommandBase, PreviewMixin): usagestr = 'usage: topple generate [--preview|-p]' def run(self): sys.path.insert(0, os.getcwd()) specs = get_setting_specs(self.parentFlags['module'] or 'toppler') self.write(specs.OUTPUT_FILE, specs) class Update(pycommand.CommandBase, PreviewMixin): usagestr = 'usage: topple update [--preview|-p]' def run(self): sys.path.insert(0, os.getcwd()) filename = 'settings_local.py' res = get_settings_from(filename) specs = get_setting_specs(self.parentFlags['module'] or 'toppler') self.write(specs.OUTPUT_FILE, specs, res) class Check(pycommand.CommandBase): usagestr = 'usage: topple check' def run(self): sys.path.insert(0, os.getcwd()) filename = 'settings_local.py' res = get_settings_from(filename) specs = get_setting_specs(self.parentFlags['module'] or 'toppler') for key, spec in specs.SETTINGS: if key not in res: print('Missing setting:', key) elif not spec[0](res[key]): print('Value "%s" for %s does not pass predicate `%s\'' % (res[key], key, spec[0].func_name)) class Topple(pycommand.CommandBase): usagestr = 'usage: topple [--module=modulename|-m modulename] generate' description = ( 'Commands:\n' ' generate Generate a new settings file.\n' ' update Update an existing settings file.\n' ' check Check the validity of an existing settings file.' ) optionList = (('module', ('m', '', 'Use specified python module.')),) def run(self): module = self.flags['module'] or 'toppler' if not os.path.exists(module + '.py'): print('No {module}.py module found!'.format(module=module)) return 1 if not self.args: print(self.usage) return 2 if self.args[0] == 'generate': cmd = Generate(argv=self.args[1:]) elif self.args[0] == 'update': cmd = Update(argv=self.args[1:]) elif self.args[0] == 'check': cmd = Check(argv=self.args[1:]) else: print('Undefined command: {cmd}'.format(cmd=self.args[0])) return 1 cmd.registerParentFlag('module', self.flags['module']) if cmd.error: print('Error during {cmd}: {err}'.format(cmd=self.args[0], err=cmd.error)) return 1 else: return cmd.run()