Skip to content

Commit

Permalink
added panlf CLI, made ContextImport x-platform
Browse files Browse the repository at this point in the history
  • Loading branch information
kiwi0fruit committed Dec 16, 2018
1 parent 8786ffe commit 3e6d4b9
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 64 deletions.
2 changes: 1 addition & 1 deletion panflute/__init__.py
Expand Up @@ -32,6 +32,6 @@
from .tools import (
stringify, yaml_filter, shell, run_pandoc, convert_text, debug, get_option)

from .autofilter import main
from .autofilter import main, panfl

from .version import __version__
167 changes: 117 additions & 50 deletions panflute/autofilter.py
@@ -1,99 +1,166 @@
"""
Allow Panflute to be run as a command line script
(so it can be used as a Pandoc filter)
main():
to be used as a Pandoc filter
panfl():
to be used in Pandoctools shell scripts as
Pandoc filter with arguments
"""

import os
import os.path as p
import sys
from collections import OrderedDict
import click

from .io import load, dump
from .tools import debug, run_pandoc
from .tools import debug
from .utils import ContextImport


def main():
def get_filter_dir(hardcoded=False):
if hardcoded:
if os.name == 'nt':
return p.join(os.environ["APPDATA"], "pandoc", "filters")
else:
return p.join(os.environ["HOME"], ".pandoc", "filters")
else:
from .tools import run_pandoc
# Extract $DATADIR
info = run_pandoc(args=['--version']).splitlines()
prefix = "Default user data directory: "
info = [row for row in info if row.startswith(prefix)]
assert len(info) == 1
data_dir = info[0][len(prefix):]
return p.join(data_dir, 'filters')


def _main(filters=None, extra_dirs=None, data_dir=False, sys_path=True):
"""
:param filters: Union[List[str], None]
if not None then it's panfl
instead of panflute
:param extra_dirs: Union[List[str], None]
if not None then it's panfl
instead of panflute
:param data_dir: bool
:param sys_path: bool
:return: json doc
"""
doc = load()
meta = doc.metadata

# meta = doc.metadata # Local variable 'meta' value is not used
verbose = doc.get_metadata('panflute-verbose', False)

# extra_path can be a list, a string, or missing
extra_path = doc.get_metadata('panflute-path', [])
if type(extra_path) != list:
extra_path = [extra_path]
if extra_dirs is None:
# metadata 'panflute-path' can be a list, a string, or missing
# `extra_dirs` should be a list of str
extra_dirs = doc.get_metadata('panflute-path', [])
if type(extra_dirs) != list:
extra_dirs = [extra_dirs]
extra_dirs.append('.')
if data_dir:
extra_dirs.append(get_filter_dir())
elif data_dir:
# panfl case:
extra_dirs.append(get_filter_dir(hardcoded=True))

# Display message (tests that everything is working ok)
msg = doc.get_metadata('panflute-echo', False)
if msg:
debug(msg)

# Run filters sequentially
filters = doc.get_metadata('panflute-filters', [])

# Allow for a single filter instead of a list
if type(filters) != list:
filters = [filters]
if filters is None:
# metadata 'panflute-filters' can be a list, a string, or missing
# `filters` should be a list of str
filters = doc.get_metadata('panflute-filters', [])
if type(filters) != list:
filters = [filters]

if filters:
if verbose:
msg = "panflute: will run the following filters:"
debug(msg, ' '.join(filters))
doc = autorun_filters(filters, doc, extra_path, verbose)
doc = autorun_filters(filters, doc, extra_dirs, verbose, sys_path)
elif verbose:
debug("panflute: no filters found in metadata")
debug("panflute: no filters were provided")

dump(doc)


def autorun_filters(filters, doc, searchpath, verbose):
# Extract $DATADIR
info = run_pandoc(args=['--version']).splitlines()
prefix = "Default user data directory: "
info = [row for row in info if row.startswith(prefix)]
assert len(info) == 1
datadir = info[0][len(prefix):]
filterdir = os.path.join(datadir, 'filters')

searchpath = searchpath + ['.', filterdir] + sys.path
filenames = OrderedDict()

for ff in filters:
for p in searchpath:

def main():
_main(data_dir=True)


@click.command(help="Filters should have basename only (may be with or without .py extension). " +
"Search preserves directories order (except for --data-dir and `sys.path`).")
@click.argument('filters', nargs=-1)
@click.option('-w', '-t', '--write', '--to', 'to', type=str, default='html',
help='Pandoc writer option.')
@click.option('--dir', '-d', 'extra_dirs', multiple=True,
help="Search filters in provided directories: `-d dir1 -d dir2`.")
@click.option('--data-dir', is_flag=True, default=False,
help="Search filters in default user data directory listed in `pandoc --version` " +
"(in it's `filters` subfolder actually). It's appended to the search list.")
@click.option('--no-sys-path', 'not_sys_path', is_flag=True, default=False,
help="Disable search filters in python's `sys.path` " +
"(I tried to remove current working directory either way) " +
"that is appended to the search list.")
def panfl(filters, to, extra_dirs, data_dir, not_sys_path):
# `load()` in `_main()` needs `to` in the 2nd arg
if len(sys.argv) > 1:
sys.argv[1] = to
elif len(sys.argv) == 1:
sys.argv.append(to)
_main(list(filters), list(extra_dirs), data_dir, sys_path=not not_sys_path)


def autorun_filters(filters, doc, extra_dirs, verbose, sys_path=True):
"""
:param filters: list of str
:param doc: panflute.Doc
:param extra_dirs: list of str
:param verbose: bool
:param sys_path: bool
:return: panflute.Doc
"""
search_path = extra_dirs
if sys_path:
search_path += [dir_ for dir_ in sys.path if (dir_ != '') and (dir_ != '.') and p.isdir(dir_)]

file_names = OrderedDict()

for file_ in filters:
for path in search_path:
# Allow with and without .py ending
if ff.endswith('.py'):
fn = os.path.join(p, ff)
else:
fn = os.path.join(p, ff + '.py')
filter_path = p.join(path, file_ + ('' if file_.endswith('.py') else '.py'))

if os.path.isfile(fn):
if p.isfile(filter_path):
if verbose:
debug("panflute: filter <{}> found in {}".format(ff, fn))
filenames[ff] = fn
debug("panflute: filter <{}> found in {}".format(file_, filter_path))
file_names[file_] = filter_path
break
elif verbose:
debug(" filter <{}> NOT found in {}".format(ff, fn))
debug(" filter <{}> NOT found in {}".format(file_, filter_path))
else:
raise Exception("filter not found: " + ff)
raise Exception("filter not found: " + file_)

for ff, fn in filenames.items():
_ = dict()
for file_, filter_path in file_names.items():
if verbose:
debug("panflute: running filter <{}>".format(ff))
with ContextImport(fn) as module:
debug("panflute: running filter <{}>".format(file_))
with ContextImport(filter_path) as module:
try:
module.main(doc)
except:
debug("Failed to run filter: " + ff)
except Exception as e:
debug("Failed to run filter: " + file_)
if not hasattr(module, 'main'):
debug(' - Possible cause: filter lacks a main() function')
debug('Filter code:')
debug('-' * 64)
debug(code)
with open(filter_path) as fp:
debug(fp.read())
debug('-' * 64)
raise
raise Exception(e)
if verbose:
debug("panflute: filter <{}> completed".format(ff))
debug("panflute: filter <{}> completed".format(file_))

return doc
29 changes: 17 additions & 12 deletions panflute/utils.py
Expand Up @@ -8,6 +8,7 @@

from collections import OrderedDict
import sys
import os.path as p

# ---------------------------
# Functions
Expand Down Expand Up @@ -43,26 +44,30 @@ def encode_dict(tag, content):


class ContextImport():
"""Import File Context Manager
Adds temporarly the director of zed file to the path, and puts in context
the file.
:params file: Full path to file with extension
:type file: `str`
"""
Import File Context Manager
Adds temporarly the director of zed file to the path,
and puts in context the file.
:Example:
>>> filename = 'foo.py'[:-3] # With out py extension
Example:
>>> filename = 'foo' # without .py extension
>>> with add_path('/path/to/dir'):
modules = __import__(filename)
module = __import__(filename)
bar = module.bar
baz = module.baz
>>> baz()
>>> print(bar)
"""
def __init__(self, file):
def __init__(self, file_):
"""
:param file_: str
Full path to file with extension
"""
# Get the directory of the file
self.path = r"/".join(file.split(r"/")[:-1])
# Get filename without extension
self.file = file.split(r"/")[-1].replace(".py", "")
self.path = p.dirname(file_)
# Get filename without .py extension:
name, ext = p.splitext(p.basename(file_))
self.file = name + ext.replace('.py', '')

def __enter__(self):
sys.path.insert(0, self.path)
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Expand Up @@ -96,7 +96,7 @@
# your project is installed. For an analysis of "install_requires" vs pip's
# requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=['pyyaml', 'future', 'shutilwhich'],
install_requires=['pyyaml', 'future', 'shutilwhich', 'click'],

# List additional groups of dependencies here (e.g. development
# dependencies). You can install these using the following syntax,
Expand Down Expand Up @@ -127,6 +127,7 @@
entry_points={
'console_scripts': [
'panflute=panflute:main',
'panfl=panflute:panfl',
],
},
)

0 comments on commit 3e6d4b9

Please sign in to comment.