Skip to content

Instantly share code, notes, and snippets.

@blakev
Created November 17, 2016 02:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blakev/8ee7beea178f75b95d6ff5171027d871 to your computer and use it in GitHub Desktop.
Save blakev/8ee7beea178f75b95d6ff5171027d871 to your computer and use it in GitHub Desktop.
cookiecutter helper function that will replace |FILENAME,filter,filter,..| patterns with the contents of FILENAME, each line being applied with filters.
import os
import re
RE_CONTENT = re.compile(r'(\|[\w\.\,]+\|)', re.I)
fns = {
'blockquote': lambda s: '> ' + s,
'code': lambda s: ' ' + s,
'comment': lambda s: '# {}'.format(s).rstrip() + '\n',
'list': lambda s: ' - ' + s
}
def inject_file_contents(from_folder, edit_fns=None, verbose=False):
""" Inject's a file's contents into other files inside ``from_folder``.
Args:
from_folder (str)
edit_fns (dict[str, Function])
Returns:
None
"""
ROOT = from_folder
if not edit_fns:
edit_fns = {}
assert os.path.exists(ROOT)
for key, fn in edit_fns.items():
assert callable(fn), '%s is not callable' % key
# read all our input file's content, so we can do a find-replace
# on all the output files one at a time, and inject the content
read_contents = {}
mark_contents = set()
mark_edits = []
for root, dirs, files in os.walk(ROOT, topdown=False):
for ins in files:
f = os.path.join(root, ins)
with open(f, 'r') as ins_file:
lines = ins_file.readlines()
for x in lines:
# if we have a content-replace marker in
# this file, then we need to save it for
# afterwards
match = RE_CONTENT.match(x)
if match:
mark_contents.add(match.group().strip('|').split(',')[0])
mark_edits.append(f)
# only read the contents of the files we need
for root, dirs, files in os.walk(ROOT, topdown=False):
for outs in files:
if outs not in mark_contents:
continue
with open(os.path.join(root, outs), 'r') as ins_file:
read_contents[outs] = ins_file.readlines()
# do the find-replace on the files we marked for editing
for f in mark_edits:
if verbose:
print '..', f
with open(f, 'r+') as out_file:
contents = out_file.read()
for m in RE_CONTENT.search(contents).groups():
rc_name = m.strip('|').split(',', 1)
text = read_contents.get(rc_name[0], None)
if text is None:
# continue instead of replacing because
# we might want to do a second pass...
continue
if len(rc_name) > 1:
# get the function names to apply
filters = rc_name[1].split(',')
for fn in filters:
if fn not in edit_fns:
print 'err: no filter named %s' % fn
continue
text = map(edit_fns[fn], text)
contents = contents.replace(m, ''.join(text).strip())
# write it out and flush
out_file.seek(0)
out_file.write(contents)
out_file.truncate()
@blakev
Copy link
Author

blakev commented Nov 17, 2016

USAGE EXAMPLE

tasks.py (invoke)

@task
def build(ctx, from_new=False):
    """ Build the cookiecutter with default inputs. """
    
    cc_folder = cookiecutter(HERE,
                             no_input=not from_new,
                             overwrite_if_exists=False)

    fns = {
        'blockquote': lambda s: '> ' + s,
        'code':       lambda s: '    ' + s,
        'comment':    lambda s: '#  {}'.format(s).rstrip() + '\n',
        'list':       lambda s: ' - ' + s
    }

    # do a find-replace for |FILE_NAME| to inject contents
    inject_file_contents(cc_folder, fns)

    # find our module name
    for item in os.listdir(cc_folder):
        fs_item = os.path.join(cc_folder, item)

        if item in ['docs', 'requirements', 'tests']:
            # folders we don't care about
            continue

        if os.path.isdir(fs_item):
            # this should be on the only "folder" left
            break

    # prep for the second pass
    os.chdir(cc_folder)
    ctx.run('python -m {}.app --help >> cli.output'.format(item), echo=True)

    # second find-replace pass
    inject_file_contents(cc_folder, fns)

    return cc_folder

Turning the {{ cookiecutter.project_name }}/README.md

# {{ cookiecutter.project_name }}
{{ cookiecutter.short_description }}
LONG DESCRIPTION
## Getting Started
    ```bash
$ mkdir {{ cookiecutter.project_name }}
$ cd {{ cookiecutter.project_name }}
$ virtualenv venv
$ source venv/bin/activate
$ pip install {{ cookiecutter.module }}
$ {{ cookiecutter.command }}
    ```
## Usage
    ```bash
|cli.output,code|
    ```
## Authors
|AUTHORS,list|
{% if cookiecutter.license %}
## License
|LICENSE,blockquote|
{% endif %}

Into..

# cli-application
A basic command line application written in Python.

LONG DESCRIPTION

## Getting Started
    ```bash
$ mkdir cli-application
$ cd cli-application
$ virtualenv venv
$ source venv/bin/activate
$ pip install cliapp
$ cliapp
    ```
## Usage
    ```bash
Usage: app.py [OPTIONS]
    
      cli-application
    
      A basic command line application written in Python.
    
    Options:
      -c, --config PATH  Path to the configuration file (JSON)
      --help             Show this message and exit.
    ```

## Authors
- Vivint, inc. (info@vivint.com)

## License
> 
>     cli-application
>     A basic command line application written in Python.
>     
>     Copyright (C) 2016 Vivint, inc.
> 
>     MIT License
> 
>     Permission is hereby granted, free of charge, to any person obtaining a copy
>     of this software and associated documentation files (the "Software"), to deal
>     in the Software without restriction, including without limitation the rights
>     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
>     copies of the Software, and to permit persons to whom the Software is
>     furnished to do so, subject to the following conditions:
> 
>     The above copyright notice and this permission notice shall be included in all
>     copies or substantial portions of the Software.
> 
>     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
>     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
>     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
>     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
>     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
>     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
>     SOFTWARE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment