# Scripting with literate programs. Since `pidgy` is based on [Python], derived pidgy documents can be used as scripts. A `pidgy` program executed as the **main** program has similar state to the running notebook and it introduces the file object. `pidgy` is based on [Python], a scripting language, therefore it should be possible execute markdown as scripts. import types, pidgy, ast, runpy, importlib __all__ = 'run', 'render', 'Runner' def run(object: str, **globals) -> dict: `run` executes a literate document as a script. return Runner(object).run(**globals) def render(object: str, **globals) -> dict: `render` executes a templated document. return Runner(object).render(**globals) ... class Runner(pidgy.pidgyLoader): A script `Runner` for `pidgy` documents based off the `importnb` machinery. def __init__(self, name, path=None, *args, **kwargs): if path is None: path = name super().__init__(name, path, *args, **kwargs) def visit(self, node): node = super().visit(node) body, annotations = ast.Module([]), ast.Module([]) while node.body: element = node.body.pop(0) if isinstance(element, ast.AnnAssign) and element.target.id[0].islower(): try: if element.value: ast.literal_eval(element.value) annotations.body.append(element) continue except: ... if isinstance(element, (ast.Import, ast.ImportFrom)): annotations.body.append(element) body.body.append(element) self.arg_code = compile(annotations, self.path, 'exec') return body def create_module(loader, spec=None): When the module is created. Compile the source to code to discover arguments in the code. if spec is None: spec = importlib.util.spec_from_loader(loader.name, loader) module = super().create_module(spec) loader.main_code = loader.get_code(loader.name) runpy._run_code(loader.arg_code, vars(module), {}, '__main__', spec, None, None) return module def exec_module(loader, module=None, **globals): module = module or loader.create_module() vars(module).update(globals) runpy._run_code(loader.main_code, vars(module), {}, '__main__', module.__spec__, None, None) return module def run(loader, **globals): return loader.exec_module(**globals) def render(loader, **globals): return loader.format(loader.run(**globals)) def cli(loader): import pidgy.autocli, click module = loader.create_module() def main(verbose: bool=True, **globals): nonlocal module try: loader.exec_module(module, **globals) verbose and click.echo(pidgy.util.ansify(loader.format(module))) except SystemExit: ... pidgy.autocli.command_from_decorators(main, click.option('--verbose/--silent', default=True), *pidgy.autocli.decorators_from_module(module)).main() def format(loader, module): import nbconvert, operator, builtins if loader.path.endswith(('.py', '.md', '.markdown')): return nbconvert.TemplateExporter().environment.from_string( pidgy.util.strip_front_matter( pidgy.util.strip_html_comment( pidgy.util.strip_shebang( loader.decode()))) ).render({ **vars(operator), **vars(builtins), **vars(module)}).rstrip() + '\n' ... ## shebang statements in literate programs. A feature of `pidgy` markdown files, not notebook files, is that a shebang statement can be included at the beginning to indicate how a document is executed. Some useful shebang lines to being pidgy documents with. #!/usr/bin/env pidgy run #!/usr/bin/env python -m pidgy run #!/usr/bin/env python -m pidgy render #!/usr/bin/env pidgy render