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