Skip to content

Commit

Permalink
As extensions, additional modules (#40)
Browse files Browse the repository at this point in the history
* feat: implmentation

* docs: update examples

* feat: --list-info adjustment

* fix: for py3.5

* refactor: rename, extension class

* feat: support cookiecutter

* feat: add logger

* chore: adjustment examples
  • Loading branch information
podhmo committed Jun 11, 2019
1 parent 2574874 commit bd3cdec
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 12 deletions.
13 changes: 13 additions & 0 deletions examples/extensions/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
OPTS ?= --logging=DEBUG
TEE ?= | tee

default: 00 01

dst:
mkdir -p dst

# use extension applyging additional modules
00: dst
python src/00*.py ${TEE} dst/00.output
01: dst
kamidana ${OPTS} src/01use-naming.j2 -e "kamidana.extensions:NamingModuleExtension" ${TEE} dst/01.output
1 change: 1 addition & 0 deletions examples/extensions/dst/00.output
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fooBarBoo|snakecase -> foo_bar_boo
1 change: 1 addition & 0 deletions examples/extensions/dst/01.output
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fooBarBoo|snakecase -> foo_bar_boo
8 changes: 8 additions & 0 deletions examples/extensions/src/00use-additionals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from jinja2 import Template

tmpl = """\
{{word}}|snakecase -> {{word|snakecase}}
"""

t = Template(tmpl, extensions=["kamidana.extensions.NamingModuleExtension"])
print(t.render(word="fooBarBoo"))
1 change: 1 addition & 0 deletions examples/extensions/src/01use-naming.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fooBarBoo|snakecase -> {{"fooBarBoo"|snakecase}}
71 changes: 71 additions & 0 deletions kamidana/extensions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import logging
from jinja2.ext import Extension
from jinja2.environment import Environment
from jinja2.utils import import_string
from dictknife import deepmerge
from .. import collect_marked_items

logger = logging.Logger(__name__)


def _build_additionals(modules) -> dict:
additionals = {}
for name in modules:
logger.info("activate additional module %s", name)
m = import_string(name) # xxx: use magicalimport.import_module()?
additionals = deepmerge(additionals, collect_marked_items(m))
return additionals


def create_apply_additonal_modules_extension_class(name: str, *, doc, get_modules):
def __init__(self, environment: Environment) -> None:
super(cls, self).__init__(environment)

modules = get_modules(environment)
additionals = _build_additionals(modules)
for name, defs in additionals.items():
getattr(environment, name).update(defs)

attrs = {"__doc__": doc, "__init__": __init__}
cls = type(name, (Extension,), attrs)
return cls


NamingModuleExtension = create_apply_additonal_modules_extension_class(
"NamingModuleExtension",
get_modules=lambda env: ["kamidana.additionals.naming"],
doc="extension create from kamidana.additionals.naming",
)
ReaderModuleExtension = create_apply_additonal_modules_extension_class(
"ReaderModuleExtension",
get_modules=lambda env: ["kamidana.additionals.reader"],
doc="extension create from kamidana.additionals.reader",
)

## for cookiecutter
def _extract_module_from_cookiecutter_cotext(env, *, exception_cls=ImportError):
import inspect

# black magic (collect context arguments, from stackframes)
_context = None
f = inspect.currentframe()
while f.f_back:
if "context" in f.f_locals:
_context = f.f_locals["context"]
break
f = f.f_back
if _context is None:
raise exception_cls("cookiecutter's context is not found, something wrong?")
if "cookiecutter" not in _context:
raise exception_cls("'cookiecutter' is not found in context, something wrong??")

if "_additional_modules" not in _context["cookiecutter"]:
raise exception_cls("we needs '_additional_modules' in your cookiecutter.json")
return _context["cookiecutter"]["_additional_modules"]


CookiecutterAdditionalModulesExtension = create_apply_additonal_modules_extension_class(
"CookiecutterAdditionalModulesExtension",
get_modules=_extract_module_from_cookiecutter_cotext,
doc="activate additional modules, see context['cookiecutter']['_additional_modules'], created from your cookiecutter.json",
)
27 changes: 15 additions & 12 deletions kamidana/listinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,24 @@
Description = t.NewType("Description", str)


def collect_extensions_info() -> t.Dict[str, Description]:
def collect_extensions_info(
*, _candidates=["jinja2.ext", "kamidana.extensions"]
) -> t.Dict[str, Description]:
extensions = defaultdict(list)
for name, v in jinja2.ext.__dict__.items():
if not inspect.isclass(v):
continue
if not issubclass(v, jinja2.ext.Extension):
continue
if v == jinja2.ext.Extension:
continue
extensions[v].append(name)
for modname in _candidates:
m = import_module(modname)
for name, v in m.__dict__.items():
if not inspect.isclass(v):
continue
if not issubclass(v, jinja2.ext.Extension):
continue
if v == jinja2.ext.Extension:
continue
extensions[v].append(f"{modname}.{name}")

info = OrderedDict()
for cls, names in extensions.items():
name = sorted(names, key=lambda x: len(x))[0]
fullname = f"{cls.__module__}.{name}"
for cls, fullnames in extensions.items():
fullname = sorted(fullnames, key=lambda x: len(x))[0]
oneline_doc = inspect.getdoc(cls).strip().split("\n", 1)[0]
info[fullname] = oneline_doc
return info
Expand Down

0 comments on commit bd3cdec

Please sign in to comment.