Source code for cookiecutter.generate

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
cookiecutter.generate
---------------------

Functions for generating a project from a project template.
"""
from __future__ import unicode_literals
import logging
import os
import io
import shutil
import sys

from jinja2 import FileSystemLoader, Template
from jinja2.environment import Environment
from jinja2.exceptions import TemplateSyntaxError
from binaryornot.check import is_binary

from .exceptions import NonTemplatedInputDirException
from .find import find_template
from .utils import make_sure_path_exists, work_in
from .hooks import run_hook


if sys.version_info[:2] < (2, 7):
    import simplejson as json
    from ordereddict import OrderedDict
else:
    import json
    from collections import OrderedDict


[docs]def generate_context(context_file='cookiecutter.json', default_context=None): """ Generates the context for a Cookiecutter project template. Loads the JSON file as a Python object, with key being the JSON filename. :param context_file: JSON file containing key/value pairs for populating the cookiecutter's variables. :param config_dict: Dict containing any config to take into account. """ context = {} file_handle = open(context_file) obj = json.load(file_handle, object_pairs_hook=OrderedDict) # Add the Python object to the context dictionary file_name = os.path.split(context_file)[1] file_stem = file_name.split('.')[0] context[file_stem] = obj # Overwrite context variable defaults with the default context from the # user's global config, if available if default_context: obj.update(default_context) logging.debug('Context generated is {0}'.format(context)) return context
[docs]def generate_file(project_dir, infile, context, env): """ 1. Render the filename of infile as the name of outfile. 2. Deal with infile appropriately: a. If infile is a binary file, copy it over without rendering. b. If infile is a text file, render its contents and write the rendered infile to outfile. Precondition: When calling `generate_file()`, the root template dir must be the current working directory. Using `utils.work_in()` is the recommended way to perform this directory change. :param project_dir: Absolute path to the resulting generated project. :param infile: Input file to generate the file from. Relative to the root template dir. :param context: Dict for populating the cookiecutter's variables. :param env: Jinja2 template execution environment. """ logging.debug("Generating file {0}".format(infile)) # Render the path to the output file (not including the root project dir) outfile_tmpl = Template(infile) outfile = os.path.join(project_dir, outfile_tmpl.render(**context)) logging.debug("outfile is {0}".format(outfile)) # Just copy over binary files. Don't render. logging.debug("Check {0} to see if it's a binary".format(infile)) if is_binary(infile): logging.debug("Copying binary {0} to {1} without rendering" .format(infile, outfile)) shutil.copyfile(infile, outfile) else: # Force fwd slashes on Windows for get_template # This is a by-design Jinja issue infile_fwd_slashes = infile.replace(os.path.sep, '/') # Render the file try: tmpl = env.get_template(infile_fwd_slashes) except TemplateSyntaxError as exception: # Disable translated so that printed exception contains verbose # information about syntax error location exception.translated = False raise rendered_file = tmpl.render(**context) logging.debug("Writing {0}".format(outfile)) with io.open(outfile, 'w', encoding="utf-8") as fh: fh.write(rendered_file) # Apply file permissions to output file shutil.copymode(infile, outfile)
[docs]def render_and_create_dir(dirname, context, output_dir): """ Renders the name of a directory, creates the directory, and returns its path. """ name_tmpl = Template(dirname) rendered_dirname = name_tmpl.render(**context) logging.debug('Rendered dir {0} must exist in output_dir {1}'.format( rendered_dirname, output_dir )) dir_to_create = os.path.normpath( os.path.join(output_dir, rendered_dirname) ) make_sure_path_exists(dir_to_create) return dir_to_create
[docs]def ensure_dir_is_templated(dirname): """ Ensures that dirname is a templated directory name. """ if '{{' in dirname and \ '}}' in dirname: return True else: raise NonTemplatedInputDirException
[docs]def generate_files(repo_dir, context=None, output_dir="."): """ Renders the templates and saves them to files. :param repo_dir: Project template input directory. :param context: Dict for populating the template's variables. :param output_dir: Where to output the generated project dir into. """ template_dir = find_template(repo_dir) logging.debug('Generating project from {0}...'.format(template_dir)) context = context or {} unrendered_dir = os.path.split(template_dir)[1] ensure_dir_is_templated(unrendered_dir) project_dir = render_and_create_dir(unrendered_dir, context, output_dir) # We want the Jinja path and the OS paths to match. Consequently, we'll: # + CD to the template folder # + Set Jinja's path to "." # # In order to build our files to the correct folder(s), we'll use an # absolute path for the target folder (project_dir) project_dir = os.path.abspath(project_dir) logging.debug("project_dir is {0}".format(project_dir)) # run pre-gen hook from repo_dir with work_in(repo_dir): run_hook('pre_gen_project', project_dir) with work_in(template_dir): env = Environment() env.loader = FileSystemLoader(".") for root, dirs, files in os.walk("."): for d in dirs: unrendered_dir = os.path.join(project_dir, os.path.join(root, d)) render_and_create_dir(unrendered_dir, context, output_dir) for f in files: infile = os.path.join(root, f) logging.debug("f is {0}".format(f)) generate_file(project_dir, infile, context, env) # run post-gen hook from repo_dir with work_in(repo_dir): run_hook('post_gen_project', project_dir)