Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions cement/cli/templates/generate/project/.generate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,48 @@ exclude:
- '^(.*)[\/\\\\]project[\/\\\\]{{ label }}[\/\\\\]templates[\/\\\\](.*)$'

ignore:
- '^(.*)pyc(.*)$'
- '^(.*)pyo(.*)$'
- '^(.*)[.]pyc$'
- '^(.*)[.]pyo$'
- '^(.*)__pycache__(.*)$'
- '^(.*)[\/\\\\][.]git(.*)$'

variables:
- name: label
prompt: "App Label"
case: "lower"
default: "myapp"
default: "{{ dest_label }}"

- name: name
prompt: "App Name"
default: "My Application"
default: "{{ dest_tail|title|catif(' Application') }}"

- name: class_name
prompt: "App Class Name"
validate: "^[a-zA-Z0-9]+$"
default: "MyApp"
default: "{{ dest_tail|pyid(case='title')|catif('App') }}"

- name: description
prompt: "App Description"
default: "MyApp Does Amazing Things!"
default: "{{ class_name }} Does Amazing Things!"

- name: creator
prompt: "Creator Name"
default: "John Doe"

- name: _creator_mail_id
default: "{{ creator|pyid(sep='.') }}"

- name: creator_email
prompt: "Creator Email"
default: "john.doe@example.com"
default: "{{ _creator_mail_id }}@gmail.com"

- name: github_user
prompt: "GitHub User ID"
default: "{{ creator|pyid(sep='-') }}"

- name: url
prompt: "Project URL"
default: "https://github.com/johndoe/myapp/"
default: "https://github.com/{{ github_user }}/{{ label }}/"

- name: license
prompt: "License"
Expand Down
104 changes: 60 additions & 44 deletions cement/ext/ext_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,29 @@ class Meta:
pass

def _generate(self, source, dest):
from datetime import datetime
from cement.utils.misc import pyid
msg = 'Generating %s %s in %s' % (
self.app._meta.label, self._meta.label, dest
)
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
today = datetime.today()
dest_tail = os.path.split(dest.strip(os.path.sep))[-1].strip()
data = dict(
cement=dict(
version=get_version(),
major_version=VERSION[0],
minor_version=VERSION[1],
major_minor_version=maj_min,
),
today=today,
iso_today=today.date().isoformat(),
dest_tail=dest_tail,
dest_label=pyid(dest_tail, sep='_'),
)

f = open(os.path.join(source, '.generate.yml'))
yaml_load = yaml.full_load if hasattr(yaml, 'full_load') else yaml.load
Expand All @@ -46,54 +56,38 @@ def _generate(self, source, dest):
self._meta.label
ignore_list.append(g_config_yml)

var_defaults = {
'name': None,
'prompt': None,
'validate': None,
'case': None,
'default': None,
}

for defined_var in vars:
var = var_defaults.copy()
var.update(defined_var)
for key in ['name', 'prompt']:
assert var[key] is not None, \
"Required generate config key missing: %s" % key

for var in vars:
var_name = var.get('name', None)
assert var_name is not None, \
"Required generate config key missing: name"
var_prompt = var.get('prompt', None)
var_default = var.get('default', None)
val = None
if var['default'] is not None and self.app.pargs.defaults:
val = var['default']

elif var['default'] is not None:
default_text = ' [%s]' % var['default']

else:
default_text = '' # pragma: nocover
if var_default is not None:
var_default = self.__render(var_default, data)
if self.app.pargs.defaults or var_prompt is None:
val = var_default

if val is None:
class MyPrompt(shell.Prompt):
class Meta:
text = "%s%s:" % (var['prompt'], default_text)
default = var.get('default', None)

p = MyPrompt()
val = p.prompt() # pragma: nocover
val = self.__prompt(var_prompt, var_default)
val = self.__render(val, data)

if var['case'] in ['lower', 'upper', 'title']:
val = getattr(val, var['case'])()
elif var['case'] is not None:
var_case = var.get('case', None)
if var_case in ['lower', 'upper', 'title']:
val = getattr(val, var_case)()
elif var_case is not None:
self.app.log.warning(
"Invalid configuration for variable " +
"'%s': " % var['name'] +
"'%s': " % var_name +
"case must be one of lower, upper, or title."
)

if var['validate'] is not None:
assert re.match(var['validate'], val), \
"Invalid Response (must match: '%s')" % var['validate']
var_validate = var.get('validate', None)
if var_validate is not None:
assert re.match(var_validate, val), \
"Invalid Response (must match: '%s')" % var_validate

data[var['name']] = val
data[var_name] = val

try:
self.app.template.copy(source, dest, data,
Expand Down Expand Up @@ -129,6 +123,28 @@ def _default(self):
else:
self._generate(source, dest)

@staticmethod
def __prompt(prompt, var_default):
if prompt is not None:
default_text = ' [%s]' % var_default if var_default else ''

class MyPrompt(shell.Prompt):
class Meta:
text = "%s%s:" % (prompt, default_text)
default = var_default

p = MyPrompt()
return p.prompt() # pragma: nocover
else:
assert var_default is not None, \
"Required generate config key missing: prompt"
return var_default

def __render(self, value, data):
if '{{' in value:
value = self.app.template.render(value, data)
return value


def setup_template_items(app):
template_dirs = []
Expand Down
2 changes: 2 additions & 0 deletions cement/ext/ext_jinja2.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ def __init__(self, *args, **kw):

# expose Jinja2 Environment instance so that we can manipulate it
# higher in application code if necessary
from cement.utils.misc import pyid, catif
self.env = Environment(keep_trailing_newline=True)
self.env.filters.update(pyid=pyid, catif=catif)

def load(self, *args, **kw):
"""
Expand Down
46 changes: 46 additions & 0 deletions cement/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,52 @@
from random import random


def pyid(value, case=None, sep=None):
'''Convert a value into a Python identifier.

:param case: function to transform each part, defaults to ``str.lower``.

:param sep: separator to join all parts, defaults to '_' if value contains
it, otherwise it's ''.

For example::

>>> pyid(' foo_Bar v2 ')
'foo_bar_v2'

>>> pyid(' foo Bar v2 ', case='title')
'FooBarV2'

'''
import re
from jinja2.utils import soft_unicode
value = soft_unicode(value)
if case is None:
case = str.lower
elif isinstance(case, str):
case = getattr(str, case)
if sep is None:
sep = '_' if '_' in value else ''
return sep.join(case(p) for p in re.findall('[a-z0-9_]+', value, re.I))


def catif(value, tail):
'''Concatenate a tail if missing.

For example::

>>> catif('MainApp', 'App')
'MainApp'

'''
from jinja2.utils import soft_unicode
value = soft_unicode(value)
tail = soft_unicode(tail)
if not value.endswith(tail):
value += tail
return value


def rando(salt=None):
"""
Generate a random MD5 hash for whatever purpose. Useful for testing
Expand Down