From a1b3d443333ea4b50053501bc12f31c51f039b52 Mon Sep 17 00:00:00 2001 From: BJ Dierkes Date: Fri, 27 Jul 2018 15:50:35 -0500 Subject: [PATCH] Minimalize Project Template --- Dockerfile | 2 + MANIFEST.in | 1 + Makefile | 4 +- cement/cli/contrib/markupsafe/__init__.py | 305 ++++++++++++++++++ cement/cli/contrib/markupsafe/_compat.py | 26 ++ cement/cli/contrib/markupsafe/_constants.py | 267 +++++++++++++++ cement/cli/contrib/markupsafe/_native.py | 46 +++ cement/cli/contrib/markupsafe/_speedups.c | 239 ++++++++++++++ cement/cli/controllers/base.py | 14 +- .../generate/extension/.generate.yml | 8 + .../templates/generate/plugin/.generate.yml | 5 + .../generate/project-loaded/.generate.yml | 39 +++ .../generate/project-loaded/.gitignore | 105 ++++++ .../generate/project-loaded/CHANGELOG.md | 0 .../generate/project-loaded/Dockerfile | 15 + .../generate/project-loaded/LICENSE.md | 1 + .../generate/project-loaded/MANIFEST.in | 4 + .../{project => project-loaded}/Makefile | 0 .../generate/project-loaded/README.md | 85 +++++ .../config/{{ label }}.yml.example | 0 .../project-loaded/docker-compose.yml | 12 + .../docker/Dockerfile.dev | 0 .../requirements-dev.txt | 0 .../generate/project-loaded/requirements.txt | 6 + .../generate/project-loaded/setup.cfg | 0 .../generate/project-loaded/setup.py | 28 ++ .../generate/project-loaded/tests/conftest.py | 27 ++ .../project-loaded/tests/test_main.py | 13 + .../project-loaded/{{ label }}/__init__.py | 0 .../project-loaded/{{ label }}/bootstrap.py | 5 + .../{{ label }}/controllers/__init__.py | 0 .../{{ label }}/controllers/base.py | 61 ++++ .../{{ label }}/core/__init__.py | 0 .../project-loaded/{{ label }}/core/exc.py | 13 + .../{{ label }}/core/version.py | 7 + .../{{ label }}/ext/__init__.py | 0 .../project-loaded/{{ label }}/main.py | 90 ++++++ .../{{ label }}/plugins/__init__.py | 0 .../{{ label }}/templates/__init__.py | 0 .../{{ label }}/templates/command1.jinja2 | 0 .../templates/generate/project/.generate.yml | 5 + .../cli/templates/generate/project/Dockerfile | 11 +- .../templates/generate/project/MANIFEST.in | 1 + .../project/config/{{ label }}.conf.example | 36 +++ .../generate/project/docker-compose.yml | 8 +- .../generate/project/requirements.txt | 4 - .../generate/project/tests/conftest.py | 4 +- .../project/{{ label }}/controllers/base.py | 15 +- .../generate/project/{{ label }}/main.py | 32 +- .../templates/generate/script/.generate.yml | 9 + cement/core/template.py | 108 ++++--- cement/ext/ext_generate.py | 9 + cement/utils/version.py | 15 +- docker/Dockerfile.dev | 1 + docker/Dockerfile.docs | 8 - setup.py | 4 +- tests/cli/test_main.py | 2 +- tests/conftest.py | 4 +- .../templates/generate/test4/.generate.yml | 8 + .../generate/test4/exclude-me/take-me | 0 .../generate/test4/ignore-me/take-me | 0 .../generate/test4/take-me/exclude-me | 0 .../generate/test4/take-me/ignore-me | 0 .../templates/generate/test4/take-me/take-me | 0 tests/ext/test_ext_generate.py | 17 + 65 files changed, 1615 insertions(+), 114 deletions(-) create mode 100644 cement/cli/contrib/markupsafe/__init__.py create mode 100644 cement/cli/contrib/markupsafe/_compat.py create mode 100644 cement/cli/contrib/markupsafe/_constants.py create mode 100644 cement/cli/contrib/markupsafe/_native.py create mode 100644 cement/cli/contrib/markupsafe/_speedups.c create mode 100644 cement/cli/templates/generate/project-loaded/.generate.yml create mode 100644 cement/cli/templates/generate/project-loaded/.gitignore create mode 100644 cement/cli/templates/generate/project-loaded/CHANGELOG.md create mode 100644 cement/cli/templates/generate/project-loaded/Dockerfile create mode 100644 cement/cli/templates/generate/project-loaded/LICENSE.md create mode 100644 cement/cli/templates/generate/project-loaded/MANIFEST.in rename cement/cli/templates/generate/{project => project-loaded}/Makefile (100%) create mode 100644 cement/cli/templates/generate/project-loaded/README.md rename cement/cli/templates/generate/{project => project-loaded}/config/{{ label }}.yml.example (100%) create mode 100644 cement/cli/templates/generate/project-loaded/docker-compose.yml rename cement/cli/templates/generate/{project => project-loaded}/docker/Dockerfile.dev (100%) rename cement/cli/templates/generate/{project => project-loaded}/requirements-dev.txt (100%) create mode 100644 cement/cli/templates/generate/project-loaded/requirements.txt create mode 100644 cement/cli/templates/generate/project-loaded/setup.cfg create mode 100644 cement/cli/templates/generate/project-loaded/setup.py create mode 100644 cement/cli/templates/generate/project-loaded/tests/conftest.py create mode 100644 cement/cli/templates/generate/project-loaded/tests/test_main.py create mode 100644 cement/cli/templates/generate/project-loaded/{{ label }}/__init__.py create mode 100644 cement/cli/templates/generate/project-loaded/{{ label }}/bootstrap.py create mode 100644 cement/cli/templates/generate/project-loaded/{{ label }}/controllers/__init__.py create mode 100644 cement/cli/templates/generate/project-loaded/{{ label }}/controllers/base.py create mode 100644 cement/cli/templates/generate/project-loaded/{{ label }}/core/__init__.py create mode 100644 cement/cli/templates/generate/project-loaded/{{ label }}/core/exc.py create mode 100644 cement/cli/templates/generate/project-loaded/{{ label }}/core/version.py create mode 100644 cement/cli/templates/generate/project-loaded/{{ label }}/ext/__init__.py create mode 100644 cement/cli/templates/generate/project-loaded/{{ label }}/main.py create mode 100644 cement/cli/templates/generate/project-loaded/{{ label }}/plugins/__init__.py create mode 100644 cement/cli/templates/generate/project-loaded/{{ label }}/templates/__init__.py rename cement/cli/templates/generate/{project => project-loaded}/{{ label }}/templates/command1.jinja2 (100%) create mode 100644 cement/cli/templates/generate/project/config/{{ label }}.conf.example delete mode 100644 docker/Dockerfile.docs create mode 100644 tests/data/templates/generate/test4/.generate.yml create mode 100644 tests/data/templates/generate/test4/exclude-me/take-me create mode 100644 tests/data/templates/generate/test4/ignore-me/take-me create mode 100644 tests/data/templates/generate/test4/take-me/exclude-me create mode 100644 tests/data/templates/generate/test4/take-me/ignore-me create mode 100644 tests/data/templates/generate/test4/take-me/take-me diff --git a/Dockerfile b/Dockerfile index 9102fd81..82166e8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,3 +4,5 @@ WORKDIR /app COPY . /app RUN python setup.py install \ && rm -rf /app +WORKDIR / +ENTRYPOINT ["/usr/local/bin/cement"] diff --git a/MANIFEST.in b/MANIFEST.in index 78bf8b03..af13e088 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,4 @@ recursive-include *.py include setup.cfg include README.md CHANGELOG.md LICENSE.md CONTRIBUTORS.md include *.txt +recursive-include cement/cli/templates/generate * diff --git a/Makefile b/Makefile index d5e57446..0968d703 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,4 @@ -.PHONY: all dev test comply docs clean dist dist-upload - -all: test comply comply-test api-docs clean +.PHONY: dev test test-core comply-fix docs clean dist dist-upload dev: docker-compose up -d diff --git a/cement/cli/contrib/markupsafe/__init__.py b/cement/cli/contrib/markupsafe/__init__.py new file mode 100644 index 00000000..68dc85f6 --- /dev/null +++ b/cement/cli/contrib/markupsafe/__init__.py @@ -0,0 +1,305 @@ +# -*- coding: utf-8 -*- +""" + markupsafe + ~~~~~~~~~~ + + Implements a Markup string. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import re +import string +from collections import Mapping +from markupsafe._compat import text_type, string_types, int_types, \ + unichr, iteritems, PY2 + +__version__ = "1.0" + +__all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent'] + + +_striptags_re = re.compile(r'(|<[^>]*>)') +_entity_re = re.compile(r'&([^& ;]+);') + + +class Markup(text_type): + r"""Marks a string as being safe for inclusion in HTML/XML output without + needing to be escaped. This implements the `__html__` interface a couple + of frameworks and web applications use. :class:`Markup` is a direct + subclass of `unicode` and provides all the methods of `unicode` just that + it escapes arguments passed and always returns `Markup`. + + The `escape` function returns markup objects so that double escaping can't + happen. + + The constructor of the :class:`Markup` class can be used for three + different things: When passed an unicode object it's assumed to be safe, + when passed an object with an HTML representation (has an `__html__` + method) that representation is used, otherwise the object passed is + converted into a unicode string and then assumed to be safe: + + >>> Markup("Hello World!") + Markup(u'Hello World!') + >>> class Foo(object): + ... def __html__(self): + ... return 'foo' + ... + >>> Markup(Foo()) + Markup(u'foo') + + If you want object passed being always treated as unsafe you can use the + :meth:`escape` classmethod to create a :class:`Markup` object: + + >>> Markup.escape("Hello World!") + Markup(u'Hello <em>World</em>!') + + Operations on a markup string are markup aware which means that all + arguments are passed through the :func:`escape` function: + + >>> em = Markup("%s") + >>> em % "foo & bar" + Markup(u'foo & bar') + >>> strong = Markup("%(text)s") + >>> strong % {'text': 'hacker here'} + Markup(u'<blink>hacker here</blink>') + >>> Markup("Hello ") + "" + Markup(u'Hello <foo>') + """ + __slots__ = () + + def __new__(cls, base=u'', encoding=None, errors='strict'): + if hasattr(base, '__html__'): + base = base.__html__() + if encoding is None: + return text_type.__new__(cls, base) + return text_type.__new__(cls, base, encoding, errors) + + def __html__(self): + return self + + def __add__(self, other): + if isinstance(other, string_types) or hasattr(other, '__html__'): + return self.__class__(super(Markup, self).__add__(self.escape(other))) + return NotImplemented + + def __radd__(self, other): + if hasattr(other, '__html__') or isinstance(other, string_types): + return self.escape(other).__add__(self) + return NotImplemented + + def __mul__(self, num): + if isinstance(num, int_types): + return self.__class__(text_type.__mul__(self, num)) + return NotImplemented + __rmul__ = __mul__ + + def __mod__(self, arg): + if isinstance(arg, tuple): + arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg) + else: + arg = _MarkupEscapeHelper(arg, self.escape) + return self.__class__(text_type.__mod__(self, arg)) + + def __repr__(self): + return '%s(%s)' % ( + self.__class__.__name__, + text_type.__repr__(self) + ) + + def join(self, seq): + return self.__class__(text_type.join(self, map(self.escape, seq))) + join.__doc__ = text_type.join.__doc__ + + def split(self, *args, **kwargs): + return list(map(self.__class__, text_type.split(self, *args, **kwargs))) + split.__doc__ = text_type.split.__doc__ + + def rsplit(self, *args, **kwargs): + return list(map(self.__class__, text_type.rsplit(self, *args, **kwargs))) + rsplit.__doc__ = text_type.rsplit.__doc__ + + def splitlines(self, *args, **kwargs): + return list(map(self.__class__, text_type.splitlines( + self, *args, **kwargs))) + splitlines.__doc__ = text_type.splitlines.__doc__ + + def unescape(self): + r"""Unescape markup again into an text_type string. This also resolves + known HTML4 and XHTML entities: + + >>> Markup("Main » About").unescape() + u'Main \xbb About' + """ + from markupsafe._constants import HTML_ENTITIES + def handle_match(m): + name = m.group(1) + if name in HTML_ENTITIES: + return unichr(HTML_ENTITIES[name]) + try: + if name[:2] in ('#x', '#X'): + return unichr(int(name[2:], 16)) + elif name.startswith('#'): + return unichr(int(name[1:])) + except ValueError: + pass + # Don't modify unexpected input. + return m.group() + return _entity_re.sub(handle_match, text_type(self)) + + def striptags(self): + r"""Unescape markup into an text_type string and strip all tags. This + also resolves known HTML4 and XHTML entities. Whitespace is + normalized to one: + + >>> Markup("Main » About").striptags() + u'Main \xbb About' + """ + stripped = u' '.join(_striptags_re.sub('', self).split()) + return Markup(stripped).unescape() + + @classmethod + def escape(cls, s): + """Escape the string. Works like :func:`escape` with the difference + that for subclasses of :class:`Markup` this function would return the + correct subclass. + """ + rv = escape(s) + if rv.__class__ is not cls: + return cls(rv) + return rv + + def make_simple_escaping_wrapper(name): + orig = getattr(text_type, name) + def func(self, *args, **kwargs): + args = _escape_argspec(list(args), enumerate(args), self.escape) + _escape_argspec(kwargs, iteritems(kwargs), self.escape) + return self.__class__(orig(self, *args, **kwargs)) + func.__name__ = orig.__name__ + func.__doc__ = orig.__doc__ + return func + + for method in '__getitem__', 'capitalize', \ + 'title', 'lower', 'upper', 'replace', 'ljust', \ + 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \ + 'translate', 'expandtabs', 'swapcase', 'zfill': + locals()[method] = make_simple_escaping_wrapper(method) + + # new in python 2.5 + if hasattr(text_type, 'partition'): + def partition(self, sep): + return tuple(map(self.__class__, + text_type.partition(self, self.escape(sep)))) + def rpartition(self, sep): + return tuple(map(self.__class__, + text_type.rpartition(self, self.escape(sep)))) + + # new in python 2.6 + if hasattr(text_type, 'format'): + def format(*args, **kwargs): + self, args = args[0], args[1:] + formatter = EscapeFormatter(self.escape) + kwargs = _MagicFormatMapping(args, kwargs) + return self.__class__(formatter.vformat(self, args, kwargs)) + + def __html_format__(self, format_spec): + if format_spec: + raise ValueError('Unsupported format specification ' + 'for Markup.') + return self + + # not in python 3 + if hasattr(text_type, '__getslice__'): + __getslice__ = make_simple_escaping_wrapper('__getslice__') + + del method, make_simple_escaping_wrapper + + +class _MagicFormatMapping(Mapping): + """This class implements a dummy wrapper to fix a bug in the Python + standard library for string formatting. + + See http://bugs.python.org/issue13598 for information about why + this is necessary. + """ + + def __init__(self, args, kwargs): + self._args = args + self._kwargs = kwargs + self._last_index = 0 + + def __getitem__(self, key): + if key == '': + idx = self._last_index + self._last_index += 1 + try: + return self._args[idx] + except LookupError: + pass + key = str(idx) + return self._kwargs[key] + + def __iter__(self): + return iter(self._kwargs) + + def __len__(self): + return len(self._kwargs) + + +if hasattr(text_type, 'format'): + class EscapeFormatter(string.Formatter): + + def __init__(self, escape): + self.escape = escape + + def format_field(self, value, format_spec): + if hasattr(value, '__html_format__'): + rv = value.__html_format__(format_spec) + elif hasattr(value, '__html__'): + if format_spec: + raise ValueError('No format specification allowed ' + 'when formatting an object with ' + 'its __html__ method.') + rv = value.__html__() + else: + # We need to make sure the format spec is unicode here as + # otherwise the wrong callback methods are invoked. For + # instance a byte string there would invoke __str__ and + # not __unicode__. + rv = string.Formatter.format_field( + self, value, text_type(format_spec)) + return text_type(self.escape(rv)) + + +def _escape_argspec(obj, iterable, escape): + """Helper for various string-wrapped functions.""" + for key, value in iterable: + if hasattr(value, '__html__') or isinstance(value, string_types): + obj[key] = escape(value) + return obj + + +class _MarkupEscapeHelper(object): + """Helper for Markup.__mod__""" + + def __init__(self, obj, escape): + self.obj = obj + self.escape = escape + + __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x], s.escape) + __unicode__ = __str__ = lambda s: text_type(s.escape(s.obj)) + __repr__ = lambda s: str(s.escape(repr(s.obj))) + __int__ = lambda s: int(s.obj) + __float__ = lambda s: float(s.obj) + + +# we have to import it down here as the speedups and native +# modules imports the markup type which is define above. +try: + from markupsafe._speedups import escape, escape_silent, soft_unicode +except ImportError: + from markupsafe._native import escape, escape_silent, soft_unicode + +if not PY2: + soft_str = soft_unicode + __all__.append('soft_str') diff --git a/cement/cli/contrib/markupsafe/_compat.py b/cement/cli/contrib/markupsafe/_compat.py new file mode 100644 index 00000000..62e5632a --- /dev/null +++ b/cement/cli/contrib/markupsafe/_compat.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" + markupsafe._compat + ~~~~~~~~~~~~~~~~~~ + + Compatibility module for different Python versions. + + :copyright: (c) 2013 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +import sys + +PY2 = sys.version_info[0] == 2 + +if not PY2: + text_type = str + string_types = (str,) + unichr = chr + int_types = (int,) + iteritems = lambda x: iter(x.items()) +else: + text_type = unicode + string_types = (str, unicode) + unichr = unichr + int_types = (int, long) + iteritems = lambda x: x.iteritems() diff --git a/cement/cli/contrib/markupsafe/_constants.py b/cement/cli/contrib/markupsafe/_constants.py new file mode 100644 index 00000000..919bf03c --- /dev/null +++ b/cement/cli/contrib/markupsafe/_constants.py @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +""" + markupsafe._constants + ~~~~~~~~~~~~~~~~~~~~~ + + Highlevel implementation of the Markup string. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + + +HTML_ENTITIES = { + 'AElig': 198, + 'Aacute': 193, + 'Acirc': 194, + 'Agrave': 192, + 'Alpha': 913, + 'Aring': 197, + 'Atilde': 195, + 'Auml': 196, + 'Beta': 914, + 'Ccedil': 199, + 'Chi': 935, + 'Dagger': 8225, + 'Delta': 916, + 'ETH': 208, + 'Eacute': 201, + 'Ecirc': 202, + 'Egrave': 200, + 'Epsilon': 917, + 'Eta': 919, + 'Euml': 203, + 'Gamma': 915, + 'Iacute': 205, + 'Icirc': 206, + 'Igrave': 204, + 'Iota': 921, + 'Iuml': 207, + 'Kappa': 922, + 'Lambda': 923, + 'Mu': 924, + 'Ntilde': 209, + 'Nu': 925, + 'OElig': 338, + 'Oacute': 211, + 'Ocirc': 212, + 'Ograve': 210, + 'Omega': 937, + 'Omicron': 927, + 'Oslash': 216, + 'Otilde': 213, + 'Ouml': 214, + 'Phi': 934, + 'Pi': 928, + 'Prime': 8243, + 'Psi': 936, + 'Rho': 929, + 'Scaron': 352, + 'Sigma': 931, + 'THORN': 222, + 'Tau': 932, + 'Theta': 920, + 'Uacute': 218, + 'Ucirc': 219, + 'Ugrave': 217, + 'Upsilon': 933, + 'Uuml': 220, + 'Xi': 926, + 'Yacute': 221, + 'Yuml': 376, + 'Zeta': 918, + 'aacute': 225, + 'acirc': 226, + 'acute': 180, + 'aelig': 230, + 'agrave': 224, + 'alefsym': 8501, + 'alpha': 945, + 'amp': 38, + 'and': 8743, + 'ang': 8736, + 'apos': 39, + 'aring': 229, + 'asymp': 8776, + 'atilde': 227, + 'auml': 228, + 'bdquo': 8222, + 'beta': 946, + 'brvbar': 166, + 'bull': 8226, + 'cap': 8745, + 'ccedil': 231, + 'cedil': 184, + 'cent': 162, + 'chi': 967, + 'circ': 710, + 'clubs': 9827, + 'cong': 8773, + 'copy': 169, + 'crarr': 8629, + 'cup': 8746, + 'curren': 164, + 'dArr': 8659, + 'dagger': 8224, + 'darr': 8595, + 'deg': 176, + 'delta': 948, + 'diams': 9830, + 'divide': 247, + 'eacute': 233, + 'ecirc': 234, + 'egrave': 232, + 'empty': 8709, + 'emsp': 8195, + 'ensp': 8194, + 'epsilon': 949, + 'equiv': 8801, + 'eta': 951, + 'eth': 240, + 'euml': 235, + 'euro': 8364, + 'exist': 8707, + 'fnof': 402, + 'forall': 8704, + 'frac12': 189, + 'frac14': 188, + 'frac34': 190, + 'frasl': 8260, + 'gamma': 947, + 'ge': 8805, + 'gt': 62, + 'hArr': 8660, + 'harr': 8596, + 'hearts': 9829, + 'hellip': 8230, + 'iacute': 237, + 'icirc': 238, + 'iexcl': 161, + 'igrave': 236, + 'image': 8465, + 'infin': 8734, + 'int': 8747, + 'iota': 953, + 'iquest': 191, + 'isin': 8712, + 'iuml': 239, + 'kappa': 954, + 'lArr': 8656, + 'lambda': 955, + 'lang': 9001, + 'laquo': 171, + 'larr': 8592, + 'lceil': 8968, + 'ldquo': 8220, + 'le': 8804, + 'lfloor': 8970, + 'lowast': 8727, + 'loz': 9674, + 'lrm': 8206, + 'lsaquo': 8249, + 'lsquo': 8216, + 'lt': 60, + 'macr': 175, + 'mdash': 8212, + 'micro': 181, + 'middot': 183, + 'minus': 8722, + 'mu': 956, + 'nabla': 8711, + 'nbsp': 160, + 'ndash': 8211, + 'ne': 8800, + 'ni': 8715, + 'not': 172, + 'notin': 8713, + 'nsub': 8836, + 'ntilde': 241, + 'nu': 957, + 'oacute': 243, + 'ocirc': 244, + 'oelig': 339, + 'ograve': 242, + 'oline': 8254, + 'omega': 969, + 'omicron': 959, + 'oplus': 8853, + 'or': 8744, + 'ordf': 170, + 'ordm': 186, + 'oslash': 248, + 'otilde': 245, + 'otimes': 8855, + 'ouml': 246, + 'para': 182, + 'part': 8706, + 'permil': 8240, + 'perp': 8869, + 'phi': 966, + 'pi': 960, + 'piv': 982, + 'plusmn': 177, + 'pound': 163, + 'prime': 8242, + 'prod': 8719, + 'prop': 8733, + 'psi': 968, + 'quot': 34, + 'rArr': 8658, + 'radic': 8730, + 'rang': 9002, + 'raquo': 187, + 'rarr': 8594, + 'rceil': 8969, + 'rdquo': 8221, + 'real': 8476, + 'reg': 174, + 'rfloor': 8971, + 'rho': 961, + 'rlm': 8207, + 'rsaquo': 8250, + 'rsquo': 8217, + 'sbquo': 8218, + 'scaron': 353, + 'sdot': 8901, + 'sect': 167, + 'shy': 173, + 'sigma': 963, + 'sigmaf': 962, + 'sim': 8764, + 'spades': 9824, + 'sub': 8834, + 'sube': 8838, + 'sum': 8721, + 'sup': 8835, + 'sup1': 185, + 'sup2': 178, + 'sup3': 179, + 'supe': 8839, + 'szlig': 223, + 'tau': 964, + 'there4': 8756, + 'theta': 952, + 'thetasym': 977, + 'thinsp': 8201, + 'thorn': 254, + 'tilde': 732, + 'times': 215, + 'trade': 8482, + 'uArr': 8657, + 'uacute': 250, + 'uarr': 8593, + 'ucirc': 251, + 'ugrave': 249, + 'uml': 168, + 'upsih': 978, + 'upsilon': 965, + 'uuml': 252, + 'weierp': 8472, + 'xi': 958, + 'yacute': 253, + 'yen': 165, + 'yuml': 255, + 'zeta': 950, + 'zwj': 8205, + 'zwnj': 8204 +} diff --git a/cement/cli/contrib/markupsafe/_native.py b/cement/cli/contrib/markupsafe/_native.py new file mode 100644 index 00000000..5e83f10a --- /dev/null +++ b/cement/cli/contrib/markupsafe/_native.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +""" + markupsafe._native + ~~~~~~~~~~~~~~~~~~ + + Native Python implementation the C module is not compiled. + + :copyright: (c) 2010 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +from markupsafe import Markup +from markupsafe._compat import text_type + + +def escape(s): + """Convert the characters &, <, >, ' and " in string s to HTML-safe + sequences. Use this if you need to display text that might contain + such characters in HTML. Marks return value as markup string. + """ + if hasattr(s, '__html__'): + return s.__html__() + return Markup(text_type(s) + .replace('&', '&') + .replace('>', '>') + .replace('<', '<') + .replace("'", ''') + .replace('"', '"') + ) + + +def escape_silent(s): + """Like :func:`escape` but converts `None` into an empty + markup string. + """ + if s is None: + return Markup() + return escape(s) + + +def soft_unicode(s): + """Make a string unicode if it isn't already. That way a markup + string is not converted back to unicode. + """ + if not isinstance(s, text_type): + s = text_type(s) + return s diff --git a/cement/cli/contrib/markupsafe/_speedups.c b/cement/cli/contrib/markupsafe/_speedups.c new file mode 100644 index 00000000..d779a68c --- /dev/null +++ b/cement/cli/contrib/markupsafe/_speedups.c @@ -0,0 +1,239 @@ +/** + * markupsafe._speedups + * ~~~~~~~~~~~~~~~~~~~~ + * + * This module implements functions for automatic escaping in C for better + * performance. + * + * :copyright: (c) 2010 by Armin Ronacher. + * :license: BSD. + */ + +#include + +#define ESCAPED_CHARS_TABLE_SIZE 63 +#define UNICHR(x) (PyUnicode_AS_UNICODE((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL))); + +#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN) +typedef int Py_ssize_t; +#define PY_SSIZE_T_MAX INT_MAX +#define PY_SSIZE_T_MIN INT_MIN +#endif + + +static PyObject* markup; +static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE]; +static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE]; + +static int +init_constants(void) +{ + PyObject *module; + /* mapping of characters to replace */ + escaped_chars_repl['"'] = UNICHR("""); + escaped_chars_repl['\''] = UNICHR("'"); + escaped_chars_repl['&'] = UNICHR("&"); + escaped_chars_repl['<'] = UNICHR("<"); + escaped_chars_repl['>'] = UNICHR(">"); + + /* lengths of those characters when replaced - 1 */ + memset(escaped_chars_delta_len, 0, sizeof (escaped_chars_delta_len)); + escaped_chars_delta_len['"'] = escaped_chars_delta_len['\''] = \ + escaped_chars_delta_len['&'] = 4; + escaped_chars_delta_len['<'] = escaped_chars_delta_len['>'] = 3; + + /* import markup type so that we can mark the return value */ + module = PyImport_ImportModule("markupsafe"); + if (!module) + return 0; + markup = PyObject_GetAttrString(module, "Markup"); + Py_DECREF(module); + + return 1; +} + +static PyObject* +escape_unicode(PyUnicodeObject *in) +{ + PyUnicodeObject *out; + Py_UNICODE *inp = PyUnicode_AS_UNICODE(in); + const Py_UNICODE *inp_end = PyUnicode_AS_UNICODE(in) + PyUnicode_GET_SIZE(in); + Py_UNICODE *next_escp; + Py_UNICODE *outp; + Py_ssize_t delta=0, erepl=0, delta_len=0; + + /* First we need to figure out how long the escaped string will be */ + while (*(inp) || inp < inp_end) { + if (*inp < ESCAPED_CHARS_TABLE_SIZE) { + delta += escaped_chars_delta_len[*inp]; + erepl += !!escaped_chars_delta_len[*inp]; + } + ++inp; + } + + /* Do we need to escape anything at all? */ + if (!erepl) { + Py_INCREF(in); + return (PyObject*)in; + } + + out = (PyUnicodeObject*)PyUnicode_FromUnicode(NULL, PyUnicode_GET_SIZE(in) + delta); + if (!out) + return NULL; + + outp = PyUnicode_AS_UNICODE(out); + inp = PyUnicode_AS_UNICODE(in); + while (erepl-- > 0) { + /* look for the next substitution */ + next_escp = inp; + while (next_escp < inp_end) { + if (*next_escp < ESCAPED_CHARS_TABLE_SIZE && + (delta_len = escaped_chars_delta_len[*next_escp])) { + ++delta_len; + break; + } + ++next_escp; + } + + if (next_escp > inp) { + /* copy unescaped chars between inp and next_escp */ + Py_UNICODE_COPY(outp, inp, next_escp-inp); + outp += next_escp - inp; + } + + /* escape 'next_escp' */ + Py_UNICODE_COPY(outp, escaped_chars_repl[*next_escp], delta_len); + outp += delta_len; + + inp = next_escp + 1; + } + if (inp < inp_end) + Py_UNICODE_COPY(outp, inp, PyUnicode_GET_SIZE(in) - (inp - PyUnicode_AS_UNICODE(in))); + + return (PyObject*)out; +} + + +static PyObject* +escape(PyObject *self, PyObject *text) +{ + PyObject *s = NULL, *rv = NULL, *html; + + /* we don't have to escape integers, bools or floats */ + if (PyLong_CheckExact(text) || +#if PY_MAJOR_VERSION < 3 + PyInt_CheckExact(text) || +#endif + PyFloat_CheckExact(text) || PyBool_Check(text) || + text == Py_None) + return PyObject_CallFunctionObjArgs(markup, text, NULL); + + /* if the object has an __html__ method that performs the escaping */ + html = PyObject_GetAttrString(text, "__html__"); + if (html) { + rv = PyObject_CallObject(html, NULL); + Py_DECREF(html); + return rv; + } + + /* otherwise make the object unicode if it isn't, then escape */ + PyErr_Clear(); + if (!PyUnicode_Check(text)) { +#if PY_MAJOR_VERSION < 3 + PyObject *unicode = PyObject_Unicode(text); +#else + PyObject *unicode = PyObject_Str(text); +#endif + if (!unicode) + return NULL; + s = escape_unicode((PyUnicodeObject*)unicode); + Py_DECREF(unicode); + } + else + s = escape_unicode((PyUnicodeObject*)text); + + /* convert the unicode string into a markup object. */ + rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL); + Py_DECREF(s); + return rv; +} + + +static PyObject* +escape_silent(PyObject *self, PyObject *text) +{ + if (text != Py_None) + return escape(self, text); + return PyObject_CallFunctionObjArgs(markup, NULL); +} + + +static PyObject* +soft_unicode(PyObject *self, PyObject *s) +{ + if (!PyUnicode_Check(s)) +#if PY_MAJOR_VERSION < 3 + return PyObject_Unicode(s); +#else + return PyObject_Str(s); +#endif + Py_INCREF(s); + return s; +} + + +static PyMethodDef module_methods[] = { + {"escape", (PyCFunction)escape, METH_O, + "escape(s) -> markup\n\n" + "Convert the characters &, <, >, ', and \" in string s to HTML-safe\n" + "sequences. Use this if you need to display text that might contain\n" + "such characters in HTML. Marks return value as markup string."}, + {"escape_silent", (PyCFunction)escape_silent, METH_O, + "escape_silent(s) -> markup\n\n" + "Like escape but converts None to an empty string."}, + {"soft_unicode", (PyCFunction)soft_unicode, METH_O, + "soft_unicode(object) -> string\n\n" + "Make a string unicode if it isn't already. That way a markup\n" + "string is not converted back to unicode."}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + + +#if PY_MAJOR_VERSION < 3 + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif +PyMODINIT_FUNC +init_speedups(void) +{ + if (!init_constants()) + return; + + Py_InitModule3("markupsafe._speedups", module_methods, ""); +} + +#else /* Python 3.x module initialization */ + +static struct PyModuleDef module_definition = { + PyModuleDef_HEAD_INIT, + "markupsafe._speedups", + NULL, + -1, + module_methods, + NULL, + NULL, + NULL, + NULL +}; + +PyMODINIT_FUNC +PyInit__speedups(void) +{ + if (!init_constants()) + return NULL; + + return PyModule_Create(&module_definition); +} + +#endif diff --git a/cement/cli/controllers/base.py b/cement/cli/controllers/base.py index 27b26a6a..772b3c1e 100644 --- a/cement/cli/controllers/base.py +++ b/cement/cli/controllers/base.py @@ -1,18 +1,8 @@ -import sys -import platform from cement import Controller -from cement.utils.version import get_version +from cement.utils.version import get_version_banner -VERSION = get_version() -PYTHON_VERSION = '.'.join([str(x) for x in sys.version_info[0:3]]) -PLATFORM = platform.platform() - -BANNER = """ -Cement Framework %s -Python %s -Platform %s -""" % (VERSION, PYTHON_VERSION, PLATFORM) +BANNER = get_version_banner() class Base(Controller): diff --git a/cement/cli/templates/generate/extension/.generate.yml b/cement/cli/templates/generate/extension/.generate.yml index b5aedae5..03de1207 100644 --- a/cement/cli/templates/generate/extension/.generate.yml +++ b/cement/cli/templates/generate/extension/.generate.yml @@ -1,5 +1,13 @@ --- +exclude: + - '^(.*)[\/\\\\]extension[\/\\\\]{{ label }}[\/\\\\]templates[\/\\\\](.*)$' + +ignore: + - '^(.*)pyc(.*)$' + - '^(.*)pyo(.*)$' + - '^(.*)__pycache__(.*)$' + variables: - name: label prompt: "Extension Label" diff --git a/cement/cli/templates/generate/plugin/.generate.yml b/cement/cli/templates/generate/plugin/.generate.yml index 6ffed86c..abd6a61e 100644 --- a/cement/cli/templates/generate/plugin/.generate.yml +++ b/cement/cli/templates/generate/plugin/.generate.yml @@ -3,6 +3,11 @@ exclude: - '^(.*)[\/\\\\]plugin[\/\\\\]{{ label }}[\/\\\\]templates[\/\\\\](.*)$' +ignore: + - '^(.*)pyc(.*)$' + - '^(.*)pyo(.*)$' + - '^(.*)__pycache__(.*)$' + variables: - name: label prompt: "Plugin Label" diff --git a/cement/cli/templates/generate/project-loaded/.generate.yml b/cement/cli/templates/generate/project-loaded/.generate.yml new file mode 100644 index 00000000..ceac5be0 --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/.generate.yml @@ -0,0 +1,39 @@ +--- + +exclude: + - '^(.*)[\/\\\\]project[\/\\\\]{{ label }}[\/\\\\]templates[\/\\\\](.*)$' + +variables: + - name: label + prompt: "App Label" + case: "lower" + default: "myapp" + + - name: name + prompt: "App Name" + default: "My Application" + + - name: class_name + prompt: "App Class Name" + validate: "^[a-zA-Z0-9]+$" + default: "MyApp" + + - name: description + prompt: "App Description" + default: "MyApp Does Amazing Things!" + + - name: creator + prompt: "Creator Name" + default: "John Doe" + + - name: creator_email + prompt: "Creator Email" + default: "john.doe@example.com" + + - name: url + prompt: "Project URL" + default: "https://github.com/johndoe/myapp/" + + - name: license + prompt: "License" + default: "unlicensed" diff --git a/cement/cli/templates/generate/project-loaded/.gitignore b/cement/cli/templates/generate/project-loaded/.gitignore new file mode 100644 index 00000000..a74b246a --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/.gitignore @@ -0,0 +1,105 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +coverage-report/ +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/cement/cli/templates/generate/project-loaded/CHANGELOG.md b/cement/cli/templates/generate/project-loaded/CHANGELOG.md new file mode 100644 index 00000000..e69de29b diff --git a/cement/cli/templates/generate/project-loaded/Dockerfile b/cement/cli/templates/generate/project-loaded/Dockerfile new file mode 100644 index 00000000..91dd3343 --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.6-alpine +MAINTAINER {{ creator }} <{{ creator_email }}> +WORKDIR /app +COPY . /app +RUN apk update \ + && apk add git \ + && pip install --no-cache-dir -r requirements.txt \ + && rm -f /usr/local/lib/python3.6/site-packages/cement.egg-link \ + && cd src/cement \ + && python setup.py install \ + && cd /app \ + && python setup.py install \ + && rm -rf /app +WORKDIR / +ENTRYPOINT ["{{ label }}"] diff --git a/cement/cli/templates/generate/project-loaded/LICENSE.md b/cement/cli/templates/generate/project-loaded/LICENSE.md new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/LICENSE.md @@ -0,0 +1 @@ + diff --git a/cement/cli/templates/generate/project-loaded/MANIFEST.in b/cement/cli/templates/generate/project-loaded/MANIFEST.in new file mode 100644 index 00000000..8c2bf41d --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/MANIFEST.in @@ -0,0 +1,4 @@ +recursive-include *.py +include setup.cfg +include README.md CHANGELOG.md LICENSE.md +include *.txt diff --git a/cement/cli/templates/generate/project/Makefile b/cement/cli/templates/generate/project-loaded/Makefile similarity index 100% rename from cement/cli/templates/generate/project/Makefile rename to cement/cli/templates/generate/project-loaded/Makefile diff --git a/cement/cli/templates/generate/project-loaded/README.md b/cement/cli/templates/generate/project-loaded/README.md new file mode 100644 index 00000000..ce27e04a --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/README.md @@ -0,0 +1,85 @@ +# {{ description }} + +## Installation + +``` +$ pip install -r requirements.txt + +$ pip install setup.py +``` + +## Development + +### Environment Setup + +This project includes a basic Docker Compose configuration that will setup a local development environment with all dependencies, and services required for development and testing. + +``` +$ make dev +[...] +|> {{ label }} <| app # +``` + +The `{{ label }}` command line application is installed in `develop` mode, therefore all changes will be live and can be tested immediately as code is modified. + +``` +|> {{ label }} <| app # {{ label }} --help +``` + +### Running Tests + +Execute tests from within the development environment: + +``` +|> {{ label }} <| app # make test +``` + + +### Releasing to PyPi + +Before releasing to PyPi, you must configure your login credentials: + +**~/.pypirc**: + +``` +[pypi] +username = YOUR_USERNAME +password = YOUR_PASSWORD +``` + +Then use the included helper function via the `Makefile`: + +``` +$ make dist + +$ make dist-upload +``` + +## Deployments + +### Docker + +Included is a basic `Dockerfile` for building and distributing `{{ name }}`, +and can be built with the included `make` helper: + +``` +$ make docker + +$ docker run -it {{ label }} --help +usage: {{ label }} [-h] [--debug] [--quiet] [-o {json,yaml}] [-v] {command1} ... + +{{ description }} + +optional arguments: + -h, --help show this help message and exit + --debug toggle debug output + --quiet suppress all output + -o {json,yaml} output handler + -v, --version show program's version number and exit + +sub-commands: + {command1} + command1 example sub command1 + +Usage: {{ title }} command1 --foo bar +``` diff --git a/cement/cli/templates/generate/project/config/{{ label }}.yml.example b/cement/cli/templates/generate/project-loaded/config/{{ label }}.yml.example similarity index 100% rename from cement/cli/templates/generate/project/config/{{ label }}.yml.example rename to cement/cli/templates/generate/project-loaded/config/{{ label }}.yml.example diff --git a/cement/cli/templates/generate/project-loaded/docker-compose.yml b/cement/cli/templates/generate/project-loaded/docker-compose.yml new file mode 100644 index 00000000..777fa033 --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3" +services: + {{ label }}: + image: "{{ label }}:dev" + build: + context: . + dockerfile: docker/Dockerfile.dev + hostname: cement + stdin_open: true + tty: true + volumes: + - ".:/app" diff --git a/cement/cli/templates/generate/project/docker/Dockerfile.dev b/cement/cli/templates/generate/project-loaded/docker/Dockerfile.dev similarity index 100% rename from cement/cli/templates/generate/project/docker/Dockerfile.dev rename to cement/cli/templates/generate/project-loaded/docker/Dockerfile.dev diff --git a/cement/cli/templates/generate/project/requirements-dev.txt b/cement/cli/templates/generate/project-loaded/requirements-dev.txt similarity index 100% rename from cement/cli/templates/generate/project/requirements-dev.txt rename to cement/cli/templates/generate/project-loaded/requirements-dev.txt diff --git a/cement/cli/templates/generate/project-loaded/requirements.txt b/cement/cli/templates/generate/project-loaded/requirements.txt new file mode 100644 index 00000000..d0978892 --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/requirements.txt @@ -0,0 +1,6 @@ +### FIXME: Replace with 'cement==3.0.0' once it is stable +-e git+https://github.com/datafolklabs/cement.git@portland#egg=cement + +jinja2 +pyyaml +colorlog diff --git a/cement/cli/templates/generate/project-loaded/setup.cfg b/cement/cli/templates/generate/project-loaded/setup.cfg new file mode 100644 index 00000000..e69de29b diff --git a/cement/cli/templates/generate/project-loaded/setup.py b/cement/cli/templates/generate/project-loaded/setup.py new file mode 100644 index 00000000..e37d9877 --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/setup.py @@ -0,0 +1,28 @@ + +from setuptools import setup, find_packages +from {{ label }}.core.version import get_version + +VERSION = get_version() + +f = open('README.md', 'r') +LONG_DESCRIPTION = f.read() +f.close() + +setup( + name='{{ label }}', + version=VERSION, + description='{{ description }}', + long_description=LONG_DESCRIPTION, + long_description_content_type='text/markdown', + author='{{ creator }}', + author_email='{{ creator_email }}', + url='{{ url }}', + license='{{ license }}', + packages=find_packages(exclude=['ez_setup', 'tests*']), + package_data={'{{ label }}': ['templates/*']}, + include_package_data=True, + entry_points=""" + [console_scripts] + {{ label }} = {{ label }}.main:main + """, +) diff --git a/cement/cli/templates/generate/project-loaded/tests/conftest.py b/cement/cli/templates/generate/project-loaded/tests/conftest.py new file mode 100644 index 00000000..4e3d4d70 --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/tests/conftest.py @@ -0,0 +1,27 @@ +""" +PyTest Fixtures. +""" + +import os +import shutil +import pytest +from tempfile import mkstemp, mkdtemp + + +@pytest.fixture(scope="function") +def tmp(request): + """ + Create a `tmp` object that geneates a unique temporary directory, and file + for each test function that requires it. + """ + + class Tmp(object): + def __init__(self): + self.dir = mkdtemp() + _, self.file = mkstemp(dir=self.dir) + t = Tmp() + yield t + + # cleanup + if os.path.exists(t.dir): + shutil.rmtree(t.dir) diff --git a/cement/cli/templates/generate/project-loaded/tests/test_main.py b/cement/cli/templates/generate/project-loaded/tests/test_main.py new file mode 100644 index 00000000..5f3a3f27 --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/tests/test_main.py @@ -0,0 +1,13 @@ + +from {{ label }}.main import {{ class_name}}Test + +def test_{{ label }}(tmp): + with {{ class_name }}Test() as app: + res = app.run() + print(res) + raise Exception + +def test_command1(tmp): + argv = ['command1'] + with {{ class_name }}Test(argv=argv) as app: + app.run() diff --git a/cement/cli/templates/generate/project-loaded/{{ label }}/__init__.py b/cement/cli/templates/generate/project-loaded/{{ label }}/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cement/cli/templates/generate/project-loaded/{{ label }}/bootstrap.py b/cement/cli/templates/generate/project-loaded/{{ label }}/bootstrap.py new file mode 100644 index 00000000..acc79a39 --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/{{ label }}/bootstrap.py @@ -0,0 +1,5 @@ + +from .controllers.base import Base + +def load(app): + app.handler.register(Base) diff --git a/cement/cli/templates/generate/project-loaded/{{ label }}/controllers/__init__.py b/cement/cli/templates/generate/project-loaded/{{ label }}/controllers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cement/cli/templates/generate/project-loaded/{{ label }}/controllers/base.py b/cement/cli/templates/generate/project-loaded/{{ label }}/controllers/base.py new file mode 100644 index 00000000..5db1041b --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/{{ label }}/controllers/base.py @@ -0,0 +1,61 @@ + +from cement import Controller, ex +from ..core.version import get_version + +VERSION_BANNER = """ +{{ description }} +Version: %s +Created by: {{ creator }} +URL: {{ url }} +""" % get_version() + + +class Base(Controller): + class Meta: + label = 'base' + + # text displayed at the top of --help output + description = '{{ description }}' + + # text displayed at the bottom of --help output + epilog = 'Usage: {{ label }} command1 --foo bar' + + # controller level arguments. ex: '{{ label }} --version' + arguments = [ + ### add a version banner + ( [ '-v', '--version' ], + { 'action' : 'version', + 'version' : VERSION_BANNER } ), + ] + + + def _default(self): + """Default action if no sub-command is passed.""" + + self.app.args.print_help() + + + @ex( + help='example sub command1', + + # sub-command level arguments. ex: '{{ label }} command1 --foo bar' + arguments=[ + ### add a sample foo option under subcommand namespace + ( [ '-f', '--foo' ], + { 'help' : 'notorious foo option', + 'action' : 'store', + 'dest' : 'foo' } ), + ], + ) + def command1(self): + """Example sub-command.""" + + data = { + 'foo' : 'bar', + } + + ### do something with arguments + if self.app.pargs.foo is not None: + data['foo'] = self.app.pargs.foo + + self.app.render(data, 'command1.jinja2') diff --git a/cement/cli/templates/generate/project-loaded/{{ label }}/core/__init__.py b/cement/cli/templates/generate/project-loaded/{{ label }}/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cement/cli/templates/generate/project-loaded/{{ label }}/core/exc.py b/cement/cli/templates/generate/project-loaded/{{ label }}/core/exc.py new file mode 100644 index 00000000..c86ffb73 --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/{{ label }}/core/exc.py @@ -0,0 +1,13 @@ + +class {{ class_name }}Error(Exception): + """Generic errors.""" + + def __init__(self, msg): + Exception.__init__(self) + self.msg = msg + + def __str__(self): + return self.msg + + def __repr__(self): + return "<{{ class_name }}Error - %s>" % self.msg diff --git a/cement/cli/templates/generate/project-loaded/{{ label }}/core/version.py b/cement/cli/templates/generate/project-loaded/{{ label }}/core/version.py new file mode 100644 index 00000000..d130f858 --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/{{ label }}/core/version.py @@ -0,0 +1,7 @@ + +from cement.utils.version import get_version as cement_get_version + +VERSION = (0, 0, 1, 'alpha', 0) + +def get_version(version=VERSION): + return cement_get_version(version) diff --git a/cement/cli/templates/generate/project-loaded/{{ label }}/ext/__init__.py b/cement/cli/templates/generate/project-loaded/{{ label }}/ext/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cement/cli/templates/generate/project-loaded/{{ label }}/main.py b/cement/cli/templates/generate/project-loaded/{{ label }}/main.py new file mode 100644 index 00000000..130bed0b --- /dev/null +++ b/cement/cli/templates/generate/project-loaded/{{ label }}/main.py @@ -0,0 +1,90 @@ + +from cement import App, init_defaults +from cement.core.exc import CaughtSignal +from .core.exc import {{ class_name }}Error + + +# configuration defaults +DEFAULTS = init_defaults('{{ label }}') +DEFAULTS['{{ label }}']['{{ foo }}'] = 'bar' + + +class {{ class_name }}(App): + """{{ name }} primary application.""" + + class Meta: + label = '{{ label }}' + + # offload handler/hook registration to a separate module + bootstrap = '{{ label }}.bootstrap' + + # configuration defaults + config_defaults = DEFAULTS + + # load additional framework extensions + extensions = [ + 'json', + 'yaml', + 'colorlog', + 'jinja2', + ] + + # configuration handler + config_handler = 'yaml' + + # configuration file suffix + config_file_suffix = '.yml' + + # set the log handler + log_handler = 'colorlog' + + # set the output handler + output_handler = 'jinja2' + + # call sys.exit() on close + close_on_exit = True + + +class {{ class_name }}Test({{ class_name}}): + """A test app that is better suited for testing.""" + + class Meta: + # default argv to empty (don't use sys.argv) + argv = [] + + # don't look for config files (could break tests) + config_files = [] + + # don't call sys.exit() when app.close() is called in tests + exit_on_close = False + + +def main(): + with {{ class_name }}() as app: + try: + app.run() + + except AssertionError as e: + print('AssertionError > %s' % e.args[0]) + app.exit_code = 1 + + if app.debug is True: + import traceback + traceback.print_exc() + + except {{ class_name}}Error: + print('{{ class_name }}Error > %s' % e.args[0]) + app.exit_code = 1 + + if app.debug is True: + import traceback + traceback.print_exc() + + except CaughtSignal as e: + # Default Cement signals are SIGINT and SIGTERM, exit 0 (non-error) + print('\n%s' % e) + app.exit_code = 0 + + +if __name__ == '__main__': + main() diff --git a/cement/cli/templates/generate/project-loaded/{{ label }}/plugins/__init__.py b/cement/cli/templates/generate/project-loaded/{{ label }}/plugins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cement/cli/templates/generate/project-loaded/{{ label }}/templates/__init__.py b/cement/cli/templates/generate/project-loaded/{{ label }}/templates/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cement/cli/templates/generate/project/{{ label }}/templates/command1.jinja2 b/cement/cli/templates/generate/project-loaded/{{ label }}/templates/command1.jinja2 similarity index 100% rename from cement/cli/templates/generate/project/{{ label }}/templates/command1.jinja2 rename to cement/cli/templates/generate/project-loaded/{{ label }}/templates/command1.jinja2 diff --git a/cement/cli/templates/generate/project/.generate.yml b/cement/cli/templates/generate/project/.generate.yml index ceac5be0..9583624e 100644 --- a/cement/cli/templates/generate/project/.generate.yml +++ b/cement/cli/templates/generate/project/.generate.yml @@ -3,6 +3,11 @@ exclude: - '^(.*)[\/\\\\]project[\/\\\\]{{ label }}[\/\\\\]templates[\/\\\\](.*)$' +ignore: + - '^(.*)pyc(.*)$' + - '^(.*)pyo(.*)$' + - '^(.*)__pycache__(.*)$' + variables: - name: label prompt: "App Label" diff --git a/cement/cli/templates/generate/project/Dockerfile b/cement/cli/templates/generate/project/Dockerfile index 91dd3343..564cc38b 100644 --- a/cement/cli/templates/generate/project/Dockerfile +++ b/cement/cli/templates/generate/project/Dockerfile @@ -2,14 +2,15 @@ FROM python:3.6-alpine MAINTAINER {{ creator }} <{{ creator_email }}> WORKDIR /app COPY . /app -RUN apk update \ - && apk add git \ - && pip install --no-cache-dir -r requirements.txt \ +RUN apk update && \ + apk add git && \ + pip install --no-cache-dir -r requirements.txt \ && rm -f /usr/local/lib/python3.6/site-packages/cement.egg-link \ && cd src/cement \ && python setup.py install \ && cd /app \ - && python setup.py install \ - && rm -rf /app + && rm -rf src/cement \ + && python setup.py install +RUN rm -rf /app WORKDIR / ENTRYPOINT ["{{ label }}"] diff --git a/cement/cli/templates/generate/project/MANIFEST.in b/cement/cli/templates/generate/project/MANIFEST.in index 8c2bf41d..7bfc348b 100644 --- a/cement/cli/templates/generate/project/MANIFEST.in +++ b/cement/cli/templates/generate/project/MANIFEST.in @@ -2,3 +2,4 @@ recursive-include *.py include setup.cfg include README.md CHANGELOG.md LICENSE.md include *.txt +recursive-include {{ label }}/templates * diff --git a/cement/cli/templates/generate/project/config/{{ label }}.conf.example b/cement/cli/templates/generate/project/config/{{ label }}.conf.example new file mode 100644 index 00000000..069caee2 --- /dev/null +++ b/cement/cli/templates/generate/project/config/{{ label }}.conf.example @@ -0,0 +1,36 @@ +### {{ name }} Configuration Settings + +[{{ label }}] + +### Toggle application level debug (does not toggle framework debugging) +# debug = false + +### Where external (third-party) plugins are loaded from +# plugin_dir = /var/lib/{{ label }}/plugins/ + +### Where all plugin configurations are loaded from +# plugin_config_dir = /etc/{{ label }}/plugins.d/ + +### sample foo option +# foo = bar + + +[log.logging] + +### Where the log file lives (no log file by default) +# file = + +### The level for which to log. One of: info, warning, error, fatal, debug +# level = info + +### Whether or not to log to console +# to_console = true + +### Whether or not to rotate the log file when it reaches `max_bytes` +# rotate = false + +### Max size in bytes that a log file can grow until it is rotated. +# max_bytes = 512000 + +### The maximun number of log files to maintain when rotating +# max_files = 4 diff --git a/cement/cli/templates/generate/project/docker-compose.yml b/cement/cli/templates/generate/project/docker-compose.yml index 777fa033..972e16ef 100644 --- a/cement/cli/templates/generate/project/docker-compose.yml +++ b/cement/cli/templates/generate/project/docker-compose.yml @@ -4,9 +4,11 @@ services: image: "{{ label }}:dev" build: context: . - dockerfile: docker/Dockerfile.dev - hostname: cement + dockerfile: Dockerfile + hostname: {{ label }} stdin_open: true tty: true + working_dir: '/{{ label }}' + entrypoint: '/bin/ash' volumes: - - ".:/app" + - ".:/{{ label }}" diff --git a/cement/cli/templates/generate/project/requirements.txt b/cement/cli/templates/generate/project/requirements.txt index d0978892..e0ce94bd 100644 --- a/cement/cli/templates/generate/project/requirements.txt +++ b/cement/cli/templates/generate/project/requirements.txt @@ -1,6 +1,2 @@ ### FIXME: Replace with 'cement==3.0.0' once it is stable -e git+https://github.com/datafolklabs/cement.git@portland#egg=cement - -jinja2 -pyyaml -colorlog diff --git a/cement/cli/templates/generate/project/tests/conftest.py b/cement/cli/templates/generate/project/tests/conftest.py index 4e3d4d70..6e1fe722 100644 --- a/cement/cli/templates/generate/project/tests/conftest.py +++ b/cement/cli/templates/generate/project/tests/conftest.py @@ -16,6 +16,8 @@ def tmp(request): """ class Tmp(object): + cleanup = True + def __init__(self): self.dir = mkdtemp() _, self.file = mkstemp(dir=self.dir) @@ -23,5 +25,5 @@ def tmp(request): yield t # cleanup - if os.path.exists(t.dir): + if os.path.exists(t.dir) and cleanup is True: shutil.rmtree(t.dir) diff --git a/cement/cli/templates/generate/project/{{ label }}/controllers/base.py b/cement/cli/templates/generate/project/{{ label }}/controllers/base.py index 5db1041b..d41ebc45 100644 --- a/cement/cli/templates/generate/project/{{ label }}/controllers/base.py +++ b/cement/cli/templates/generate/project/{{ label }}/controllers/base.py @@ -1,13 +1,12 @@ from cement import Controller, ex +from cement.utils.version import get_version_banner from ..core.version import get_version VERSION_BANNER = """ -{{ description }} -Version: %s -Created by: {{ creator }} -URL: {{ url }} -""" % get_version() +{{ description }} %s +%s +""" % (get_version(), get_version_banner()) class Base(Controller): @@ -54,8 +53,8 @@ class Base(Controller): 'foo' : 'bar', } + self.app.log.info('Inside Base.command1()') + ### do something with arguments if self.app.pargs.foo is not None: - data['foo'] = self.app.pargs.foo - - self.app.render(data, 'command1.jinja2') + print('Foo => %s' % self.app.pargs.foo) diff --git a/cement/cli/templates/generate/project/{{ label }}/main.py b/cement/cli/templates/generate/project/{{ label }}/main.py index 130bed0b..5aa14b79 100644 --- a/cement/cli/templates/generate/project/{{ label }}/main.py +++ b/cement/cli/templates/generate/project/{{ label }}/main.py @@ -2,7 +2,7 @@ from cement import App, init_defaults from cement.core.exc import CaughtSignal from .core.exc import {{ class_name }}Error - +from .controllers.base import Base # configuration defaults DEFAULTS = init_defaults('{{ label }}') @@ -15,38 +15,20 @@ class {{ class_name }}(App): class Meta: label = '{{ label }}' - # offload handler/hook registration to a separate module - bootstrap = '{{ label }}.bootstrap' - # configuration defaults config_defaults = DEFAULTS - # load additional framework extensions - extensions = [ - 'json', - 'yaml', - 'colorlog', - 'jinja2', - ] - - # configuration handler - config_handler = 'yaml' - - # configuration file suffix - config_file_suffix = '.yml' - - # set the log handler - log_handler = 'colorlog' - - # set the output handler - output_handler = 'jinja2' - # call sys.exit() on close close_on_exit = True + # register handlers + handlers = [ + Base + ] + class {{ class_name }}Test({{ class_name}}): - """A test app that is better suited for testing.""" + """A sub-class of {{ class_name}} that is better suited for testing.""" class Meta: # default argv to empty (don't use sys.argv) diff --git a/cement/cli/templates/generate/script/.generate.yml b/cement/cli/templates/generate/script/.generate.yml index 7936e28a..9bf22381 100644 --- a/cement/cli/templates/generate/script/.generate.yml +++ b/cement/cli/templates/generate/script/.generate.yml @@ -1,5 +1,14 @@ --- +exclude: + - '^(.*)[\/\\\\]script[\/\\\\]{{ label }}[\/\\\\]templates[\/\\\\](.*)$' + +ignore: + - '^(.*)pyc(.*)$' + - '^(.*)pyo(.*)$' + - '^(.*)__pycache__(.*)$' + + variables: - name: label prompt: "Script Name" diff --git a/cement/core/template.py b/cement/core/template.py index b400a4dd..fd37d81f 100644 --- a/cement/core/template.py +++ b/cement/core/template.py @@ -126,6 +126,12 @@ class TemplateHandler(TemplateInterface, Handler): # must be provided by a subclass raise NotImplemented # pragma: nocover + def _match_patterns(self, item, patterns): + for pattern in patterns: + if re.match(pattern, item): + return True + return False + def copy(self, src, dest, data, force=False, exclude=None, ignore=None): """ Render ``src`` directory as template, including directory and file @@ -152,89 +158,109 @@ class TemplateHandler(TemplateInterface, Handler): dest = fs.abspath(dest) src = fs.abspath(src) + if exclude is None: exclude = [] if ignore is None: ignore = [] + ignore_patterns = self._meta.ignore + ignore + exclude_patterns = self._meta.exclude + exclude assert os.path.exists(src), "Source path %s does not exist!" % src if not os.path.exists(dest): os.makedirs(dest) - self.app.log.debug('Copying source template %s -> %s' % (src, dest)) + LOG.debug('copying source template %s -> %s' % (src, dest)) # here's the fun for cur_dir, sub_dirs, files in os.walk(src): if cur_dir == '.': continue # pragma: nocover - - # don't render the source base dir (because we are telling it - # where to go as `dest`) - if cur_dir == src: + elif cur_dir == src: + # don't render the source base dir (because we are telling it + # where to go as `dest`) cur_dir_dest = dest + elif self._match_patterns(cur_dir, ignore_patterns): + LOG.debug( + 'not copying ignored directory: %s' % cur_dir) + continue + elif self._match_patterns(cur_dir, exclude_patterns): + LOG.debug( + 'not rendering excluded directory as template: ' + + '%s' % cur_dir) + cur_dir_stub = re.sub(src, '', cur_dir) + cur_dir_stub = cur_dir_stub.lstrip('/') + cur_dir_stub = cur_dir_stub.lstrip('\\') + cur_dir_dest = os.path.join(dest, cur_dir_stub) else: # render the cur dir - self.app.log.debug('rendering template %s' % cur_dir) + LOG.debug( + 'rendering directory as template: %s' % cur_dir) cur_dir_stub = re.sub(src, '', self.render(cur_dir, data)) + cur_dir_stub = cur_dir_stub.lstrip('/') cur_dir_stub = cur_dir_stub.lstrip('\\') cur_dir_dest = os.path.join(dest, cur_dir_stub) # render sub-dirs for sub_dir in sub_dirs: - self.app.log.debug('rendering template %s' % sub_dir) - new_sub_dir = re.sub(src, - '', - self.render(sub_dir, data)) - sub_dir_dest = os.path.join(cur_dir_dest, new_sub_dir) + full_path = os.path.join(cur_dir, sub_dir) + + if self._match_patterns(full_path, ignore_patterns): + LOG.debug( + 'not copying ignored sub-directory: ' + + '%s' % full_path) + continue + elif self._match_patterns(full_path, exclude_patterns): + LOG.debug( + 'not rendering excluded sub-directory as template: ' + + '%s' % full_path) + sub_dir_dest = os.path.join(cur_dir_dest, sub_dir) + else: + LOG.debug( + 'rendering sub-directory as template: %s' % full_path) + new_sub_dir = re.sub(src, + '', + self.render(sub_dir, data)) + sub_dir_dest = os.path.join(cur_dir_dest, new_sub_dir) if not os.path.exists(sub_dir_dest): - self.app.log.debug('Creating sub-directory %s' % - sub_dir_dest) + LOG.debug('creating sub-directory %s' % sub_dir_dest) os.makedirs(sub_dir_dest) for _file in files: - self.app.log.debug('rendering template %s' % _file) new_file = re.sub(src, '', self.render(_file, data)) _file = fs.abspath(os.path.join(cur_dir, _file)) _file_dest = fs.abspath(os.path.join(cur_dir_dest, new_file)) - if force is True: - LOG.debug('Overwriting existing file: %s ' % _file_dest) - else: - assert not os.path.exists(_file_dest), \ - 'Destination file already exists: %s ' % _file_dest + # handle if destination path already exists - ignore_it = False - all_patterns = self._meta.ignore + ignore - for pattern in all_patterns: - if re.match(pattern, _file): - ignore_it = True - break + if os.path.exists(_file_dest): + if force is True: + LOG.debug( + 'overwriting existing file: %s ' % _file_dest) + else: + assert False, \ + 'Destination file already exists: %s ' % _file_dest - if ignore_it is True: - self.app.log.debug( - 'Not copying ignored file: ' + + if self._match_patterns(_file, ignore_patterns): + LOG.debug( + 'not copying ignored file: ' + '%s' % _file) continue - exclude_it = False - all_patterns = self._meta.exclude + exclude - for pattern in all_patterns: - if re.match(pattern, _file): - exclude_it = True - break - - if exclude_it is True: - self.app.log.debug( - 'Not rendering excluded file as template: ' + + elif self._match_patterns(_file, exclude_patterns): + LOG.debug( + 'not rendering excluded file: ' + '%s' % _file) shutil.copy(_file, _file_dest) + else: - f = open(os.path.join(cur_dir, _file), 'r') + LOG.debug('rendering file as template: %s' % _file) + f = open(_file, 'r') content = f.read() f.close() @@ -251,8 +277,8 @@ class TemplateHandler(TemplateInterface, Handler): template_path = template_path.lstrip('/') full_path = fs.abspath(os.path.join(template_prefix, template_path)) - LOG.debug("attemping to load output template from file %s" % - full_path) + LOG.debug( + "attemping to load output template from file %s" % full_path) if os.path.exists(full_path): content = open(full_path, 'r').read() LOG.debug("loaded output template from file %s" % diff --git a/cement/ext/ext_generate.py b/cement/ext/ext_generate.py index 0fa55965..f633bb9f 100644 --- a/cement/ext/ext_generate.py +++ b/cement/ext/ext_generate.py @@ -123,6 +123,7 @@ import os import inspect import yaml from .. import Controller, minimal_logger, shell, FrameworkError +from ..utils.version import VERSION, get_version LOG = minimal_logger(__name__) @@ -140,6 +141,14 @@ class GenerateTemplateAbstractBase(Controller): self.app.log.info(msg) data = {} + # builtin vars + maj_min = float('%s.%s' % (VERSION[0], VERSION[1])) + data['cement'] = {} + data['cement']['version'] = get_version() + data['cement']['major_version'] = VERSION[0] + data['cement']['minor_version'] = VERSION[1] + data['cement']['major_minor_version'] = maj_min + f = open(os.path.join(source, '.generate.yml')) g_config = yaml.load(f) f.close() diff --git a/cement/utils/version.py b/cement/utils/version.py index 61628028..de92da97 100644 --- a/cement/utils/version.py +++ b/cement/utils/version.py @@ -38,8 +38,9 @@ import datetime # pragma: nocover import os # pragma: nocover +import sys # pragma: nocover import subprocess # pragma: nocover - +import platform # pragma: nocover from ..core.backend import VERSION # pragma: nocover @@ -71,6 +72,18 @@ def get_version(version=VERSION): # pragma: nocover return main + sub +def get_version_banner(): + cement_ver = get_version() + python_ver = '.'.join([str(x) for x in sys.version_info[0:3]]) + plat = platform.platform() + + banner = 'Cement Framework %s\n' % cement_ver + \ + 'Python %s\n' % python_ver + \ + 'Platform %s' % plat + + return banner + + def get_git_changeset(): # pragma: nocover """Returns a numeric identifier of the latest git changeset. diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 312c5822..b075b1e1 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -13,6 +13,7 @@ RUN apk update \ make \ vim \ bash \ + git \ && ln -sf /usr/bin/vim /usr/bin/vi \ && pip install --no-cache-dir -r requirements-dev.txt COPY . /app diff --git a/docker/Dockerfile.docs b/docker/Dockerfile.docs deleted file mode 100644 index 85e2532b..00000000 --- a/docker/Dockerfile.docs +++ /dev/null @@ -1,8 +0,0 @@ -FROM alpine:3.5 -MAINTAINER BJ Dierkes -EXPOSE 8000 -WORKDIR /app -RUN apk update && apk add hugo py-pygments -COPY doc-new/ /app/ -COPY docker/bin/run-docs.sh /usr/bin/run-docs.sh -CMD ["/usr/bin/run-docs.sh"] diff --git a/setup.py b/setup.py index 26091d68..e437f55d 100644 --- a/setup.py +++ b/setup.py @@ -11,16 +11,18 @@ f.close() setup(name='cement', version=VERSION, - description='CLI Application Framework for Python', + description='CLI Framework for Python', long_description=LONG, long_description_content_type='text/markdown', classifiers=[], + install_requires=[], keywords='cli framework', author='Data Folk Labs, LLC', author_email='derks@datafolklabs.com', url='http://builtoncement.org', license='BSD', packages=find_packages(exclude=['ez_setup', 'tests*']), + package_data={'cement': ['cement/cli/templates/generate/*']}, include_package_data=True, zip_safe=False, test_suite='nose.collector', diff --git a/tests/cli/test_main.py b/tests/cli/test_main.py index 2ad8f03d..bb3e6048 100644 --- a/tests/cli/test_main.py +++ b/tests/cli/test_main.py @@ -19,7 +19,7 @@ def test_app(): def test_generate(tmp): - argv = ['generate', 'app', tmp.dir, '--defaults'] + argv = ['generate', 'project', tmp.dir, '--defaults'] with App(argv=argv) as app: app.run() diff --git a/tests/conftest.py b/tests/conftest.py index cb85739b..d166a73d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,6 +9,8 @@ from tempfile import mkstemp, mkdtemp @pytest.fixture(scope="function") def tmp(request): class Tmp(object): + cleanup = True + def __init__(self): self.dir = mkdtemp() _, self.file = mkstemp(dir=self.dir) @@ -16,7 +18,7 @@ def tmp(request): yield t # cleanup - if os.path.exists(t.dir): + if os.path.exists(t.dir) and t.cleanup is True: shutil.rmtree(t.dir) diff --git a/tests/data/templates/generate/test4/.generate.yml b/tests/data/templates/generate/test4/.generate.yml new file mode 100644 index 00000000..ad74b68d --- /dev/null +++ b/tests/data/templates/generate/test4/.generate.yml @@ -0,0 +1,8 @@ +--- + +ignore: +- '.*ignore-me.*' + +exclude: +- '.*exclude-me.*' + diff --git a/tests/data/templates/generate/test4/exclude-me/take-me b/tests/data/templates/generate/test4/exclude-me/take-me new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/templates/generate/test4/ignore-me/take-me b/tests/data/templates/generate/test4/ignore-me/take-me new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/templates/generate/test4/take-me/exclude-me b/tests/data/templates/generate/test4/take-me/exclude-me new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/templates/generate/test4/take-me/ignore-me b/tests/data/templates/generate/test4/take-me/ignore-me new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/templates/generate/test4/take-me/take-me b/tests/data/templates/generate/test4/take-me/take-me new file mode 100644 index 00000000..e69de29b diff --git a/tests/ext/test_ext_generate.py b/tests/ext/test_ext_generate.py index 1dbd9f1d..73c94e48 100644 --- a/tests/ext/test_ext_generate.py +++ b/tests/ext/test_ext_generate.py @@ -108,3 +108,20 @@ def test_generate_default_command(tmp): argv = ['generate'] with GenerateApp(argv=argv) as app: app.run() + + +def test_filtered_sub_dirs(tmp): + tmp.cleanup = False + argv = ['generate', 'test4', tmp.dir, '--defaults'] + + with GenerateApp(argv=argv) as app: + app.run() + + assert exists_join(tmp.dir, 'take-me') + assert exists_join(tmp.dir, 'take-me', 'take-me') + assert exists_join(tmp.dir, 'take-me', 'exclude-me') + assert not exists_join(tmp.dir, 'take-me', 'ignore-me') + assert exists_join(tmp.dir, 'exclude-me') + assert exists_join(tmp.dir, 'exclude-me', 'take-me') + assert not exists_join(tmp.dir, 'ignore-me') + assert not exists_join(tmp.dir, 'ignore-me', 'take-me')