def run_script_with_context(script_path, cwd, context): """Execute a script after rendering it with Jinja. :param script_path: Absolute path to the script to run. :param cwd: The directory to run the script from. :param context: Cookiecutter project template context. """ _, extension = os.path.splitext(script_path) contents = io.open(script_path, 'r', encoding='utf-8').read() with tempfile.NamedTemporaryFile( delete=False, mode='wb', suffix=extension ) as temp: env = StrictEnvironment( context=context, keep_trailing_newline=True, ) template = env.from_string(contents) output = template.render(**context) temp.write(output.encode('utf-8')) run_script(temp.name, cwd)
def test_env_should_raise_for_unknown_extension(): context = {'cookiecutter': {'_extensions': ['foobar']}} with pytest.raises(UnknownExtension) as err: StrictEnvironment(context=context, keep_trailing_newline=True) assert 'Unable to load extension: ' in str(err.value)
def prompt_for_config(context, no_input=False): """ Prompts the user to enter new config, using context as a source for the field names and sample values. :param no_input: Prompt the user at command line for manual configuration? """ cookiecutter_dict = OrderedDict([]) env = StrictEnvironment(context=context) # First pass: Handle simple and raw variables, plus choices. # These must be done first because the dictionaries keys and # values might refer to them. for key, raw in iteritems(context[u'cookiecutter']): if key.startswith(u'_'): cookiecutter_dict[key] = raw continue try: if isinstance(raw, list): if isinstance(raw[0], dict): val = _prompt_choice_and_subitems( cookiecutter_dict, env, key, raw, no_input ) cookiecutter_dict[key] = val else: # We are dealing with a choice variable val = prompt_choice_for_config( cookiecutter_dict, env, key, raw, no_input ) cookiecutter_dict[key] = val elif not isinstance(raw, dict): # We are dealing with a regular variable val = render_variable(env, raw, cookiecutter_dict) if not no_input: val = read_user_variable(key, val) cookiecutter_dict[key] = val except UndefinedError as err: msg = "Unable to render variable '{}'".format(key) raise UndefinedVariableInTemplate(msg, err, context) # Second pass; handle the dictionaries. for key, raw in iteritems(context[u'cookiecutter']): try: if isinstance(raw, dict): # We are dealing with a dict variable val = render_variable(env, raw, cookiecutter_dict) if not no_input: val = read_user_dict(key, val) cookiecutter_dict[key] = val except UndefinedError as err: msg = "Unable to render variable '{}'".format(key) raise UndefinedVariableInTemplate(msg, err, context) return cookiecutter_dict
def test_env_should_come_with_default_extensions(): """Verify default extensions loaded with StrictEnvironment.""" env = StrictEnvironment(keep_trailing_newline=True) assert 'jinja2_time.jinja2_time.TimeExtension' in env.extensions assert 'cookiecutter.extensions.JsonifyExtension' in env.extensions assert 'cookiecutter.extensions.RandomStringExtension' in env.extensions assert 'cookiecutter.extensions.SlugifyExtension' in env.extensions assert 'cookiecutter.extensions.UUIDExtension' in env.extensions
def test_local_extension_not_available(tmpdir, cli_runner): """Test handling of included but unavailable local extension.""" context = {'cookiecutter': {'_extensions': ['foobar']}} with pytest.raises(UnknownExtension) as err: StrictEnvironment(context=context, keep_trailing_newline=True) assert 'Unable to load extension: ' in str(err.value)
def render_file_template(template_path, use_defaults=False): """Render a single-file template with Cookiecutter. Currently this function only renders a file using default values defined in a ``cookiecutter.json`` file. Parameters ---------- template_path : `str` Path to the file template. There should be a ``cookecutter.json`` in the same directory as the template file. This JSON file is used to define a provide defaults for the template's variables. Returns ------- rendered_text : `str` Content rendered from the template and ``cookiecutter.json`` defaults. """ logger = logging.getLogger(__name__) logger.debug('Rendering file template %s', template_path) # Get variables for rendering the template template_dir = os.path.dirname(template_path) context_file = os.path.join(template_dir, 'cookiecutter.json') context = generate_context(context_file=context_file) context['cookiecutter'] = prompt_for_config(context, use_defaults) # Jinja2 template rendering environment env = StrictEnvironment( context=context, keep_trailing_newline=True, ) env.loader = FileSystemLoader(template_dir) try: tmpl = env.get_template(os.path.basename(template_path)) except TemplateSyntaxError as exception: # Disable translated so that printed exception contains verbose # information about syntax error location exception.translated = False raise rendered_text = tmpl.render(**context) return rendered_text
def run_script_with_context(script_path, cwd, context): """Execute a script after rendering it with Jinja. :param script_path: Absolute path to the script to run. :param cwd: The directory to run the script from. :param context: Cookiecutter project template context. """ _, extension = os.path.splitext(script_path) contents = io.open(script_path, "r", encoding="utf-8").read() with tempfile.NamedTemporaryFile(delete=False, mode="wb", suffix=extension) as temp: env = StrictEnvironment(context=context, keep_trailing_newline=True) template = env.from_string(contents) output = template.render(**context) temp.write(output.encode("utf-8")) run_script(temp.name, cwd)
def _execute(self): env = StrictEnvironment(context=self.context) env.loader = FileSystemLoader(self.file_system_loader) template = env.get_template(self.operator_dict['template_path']) jinja_context = (self.operator_dict['context'] if 'context' in self.operator_dict else {}) if 'extra_context' in self.operator_dict: jinja_context.update(self.operator_dict['extra_context']) output_from_parsed_template = template.render( **{self.context_key: jinja_context}) with open(self.operator_dict['output_path'], 'w') as fh: fh.write(output_from_parsed_template) return self.operator_dict['output_path']
def get_next_option(cc_context, user_inputs): """Parses the cookiecutter template and current context and determines the input to be requested.""" context = {} context["cookiecutter"] = cc_context env = StrictEnvironment(context=context) for key in context["cookiecutter"]: if key not in user_inputs: rendered_value = render_variable(env, context["cookiecutter"][key], user_inputs) return key, rendered_value, False return None, None, True
def _prepare_cookiecutter_env(cookiecutter_dir) -> _CookiecutterEnv: """Prepare the cookiecutter environment to render its default values when prompting user on the CLI for inputs. """ # pylint: disable=import-outside-toplevel from cookiecutter.environment import StrictEnvironment from cookiecutter.generate import generate_context cookiecutter_json = cookiecutter_dir / "cookiecutter.json" cookiecutter_context = generate_context( context_file=cookiecutter_json).get("cookiecutter", {}) cookiecutter_env = StrictEnvironment(context=cookiecutter_context) return _CookiecutterEnv(context=cookiecutter_context, env=cookiecutter_env)
def test_passes_custom_arguments_from_context(): context = { 'cookiecutter': { '_environment': { 'variable_start_string': '[[', 'variable_end_string': ']]' } } } env = StrictEnvironment(context=context) assert env.variable_start_string == '[[' assert env.variable_end_string == ']]'
def env(): environment = StrictEnvironment() environment.loader = FileSystemLoader('.') return environment
def env(): """Fixture. Set Jinja2 environment settings for other tests.""" environment = StrictEnvironment() environment.loader = FileSystemLoader('.') return environment
def test_env_should_come_with_jinja2_time_extension(): env = StrictEnvironment(keep_trailing_newline=True) assert "jinja2_time.jinja2_time.TimeExtension" in env.extensions
def parse_operator( context, key, cc_dict, append_key: bool = False, no_input: bool = False, context_key=None, ): """Parse input dict for loop and when logic and calls hooks. :return: cc_dict """ global post_gen_operator_list if not context_key: context_key = next(iter(context)) env = StrictEnvironment(context=context) logger.debug("Parsing context_key: %s and key: %s" % (context_key, key)) operator_dict = context[context_key][key] when_condition = evaluate_when(operator_dict, env, cc_dict, context_key) if when_condition: # Extract loop if 'loop' in operator_dict: loop_targets = render_variable( env, operator_dict['loop'], cc_dict, context_key ) operator_dict.pop('loop') loop_output = [] for i, l in enumerate(loop_targets): loop_cookiecutter = cc_dict loop_cookiecutter.update({'index': i, 'item': l}) loop_output += [ parse_operator( context, key, loop_cookiecutter, append_key=True, no_input=no_input, ) ] cc_dict.pop('item') cc_dict.pop('index') cc_dict[key] = loop_output return cc_dict if 'block' not in operator_dict['type']: operator_dict = render_variable(env, operator_dict, cc_dict, context_key) # Run the operator if operator_dict['merge'] if 'merge' in operator_dict else False: to_merge, post_gen_operator = run_operator( operator_dict, context, no_input, context_key, cc_dict, env ) cc_dict.update(to_merge) else: cc_dict[key], post_gen_operator = run_operator( operator_dict, context, no_input, context_key, cc_dict, env, key ) if post_gen_operator: post_gen_operator_list.append(post_gen_operator) if append_key: return cc_dict[key] return cc_dict
def generate_files(repo_dir, context=None, output_dir='.', overwrite_if_exists=False): """Render 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. :param overwrite_if_exists: Overwrite the contents of the output directory if it exists. """ 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) env = StrictEnvironment( context=context, keep_trailing_newline=True, ) try: project_dir = render_and_create_dir( unrendered_dir, context, output_dir, env, overwrite_if_exists ) except UndefinedError as err: msg = "Unable to create project directory '{}'".format(unrendered_dir) raise UndefinedVariableInTemplate(msg, err, context) # 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_hook_from_repo_dir(repo_dir, 'pre_gen_project', project_dir, context) with work_in(template_dir): env.loader = FileSystemLoader('.') for root, dirs, files in os.walk('.'): # We must separate the two types of dirs into different lists. # The reason is that we don't want ``os.walk`` to go through the # unrendered directories, since they will just be copied. copy_dirs = [] render_dirs = [] for d in dirs: d_ = os.path.normpath(os.path.join(root, d)) # We check the full path, because that's how it can be # specified in the ``_copy_without_render`` setting, but # we store just the dir name if copy_without_render(d_, context): copy_dirs.append(d) else: render_dirs.append(d) for copy_dir in copy_dirs: indir = os.path.normpath(os.path.join(root, copy_dir)) outdir = os.path.normpath(os.path.join(project_dir, indir)) logging.debug( 'Copying dir {0} to {1} without rendering' ''.format(indir, outdir) ) shutil.copytree(indir, outdir) # We mutate ``dirs``, because we only want to go through these dirs # recursively dirs[:] = render_dirs for d in dirs: unrendered_dir = os.path.join(project_dir, root, d) try: render_and_create_dir( unrendered_dir, context, output_dir, env, overwrite_if_exists ) except UndefinedError as err: rmtree(project_dir) _dir = os.path.relpath(unrendered_dir, output_dir) msg = "Unable to create directory '{}'".format(_dir) raise UndefinedVariableInTemplate(msg, err, context) for f in files: infile = os.path.normpath(os.path.join(root, f)) if copy_without_render(infile, context): outfile_tmpl = env.from_string(infile) outfile_rendered = outfile_tmpl.render(**context) outfile = os.path.join(project_dir, outfile_rendered) logging.debug( 'Copying file {0} to {1} without rendering' ''.format(infile, outfile) ) shutil.copyfile(infile, outfile) shutil.copymode(infile, outfile) continue logging.debug('f is {0}'.format(f)) try: generate_file(project_dir, infile, context, env) except UndefinedError as err: rmtree(project_dir) msg = "Unable to create file '{}'".format(infile) raise UndefinedVariableInTemplate(msg, err, context) _run_hook_from_repo_dir(repo_dir, 'post_gen_project', project_dir, context) return project_dir
remove_folder('templatetags') for file in [ 'urls.py', 'views.py', 'hooks.py', ]: remove_file(file) if '{{ cookiecutter.content_renderer_plugin }}' == 'Yes': context = {'content_kind': ''} while context['content_kind'] not in dict(content_kinds.choices): print('Choose a content kind that this plugin renders, choose from {}'. format(', '.join(dict(content_kinds.choices).keys()))) context['content_kind'] = raw_input( 'Select the content kind that this plugin renders: ') if context['content_kind'] not in dict(content_kinds.choices): print('Invalid kind.') context['file_extension'] = raw_input( 'Please provide the file extension that this plugin renders: ') env = StrictEnvironment( context=context, keep_trailing_newline=True, ) env.loader = FileSystemLoader('/') render_file('kolibri_plugin.py', context, env) render_file('assets/src/module.js', context, env) if '{{ cookiecutter.has_own_page }}' == 'Yes' and '{{ cookiecutter.frontend_plugin }}' == 'Yes': shutil.move(location('assets/src/module.js'), location('assets/src/app.js'))
def generate_files( repo_dir, context=None, output_dir='.', overwrite_if_exists=False, skip_if_file_exists=False, context_key=None, accept_hooks=True, ): """Render 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. :param overwrite_if_exists: Overwrite the contents of the output directory if it exists. :param accept_hooks: Accept pre and post hooks if set to `True`. """ if not context_key: context_key = next(iter(context)) template_dir = find_template(repo_dir, context_key) if template_dir: envvars = context.get(context_key, {}).get('_jinja2_env_vars', {}) unrendered_dir = os.path.split(template_dir)[1] ensure_dir_is_templated(unrendered_dir) env = StrictEnvironment(context=context, keep_trailing_newline=True, **envvars) try: project_dir, output_directory_created = render_and_create_dir( unrendered_dir, context, output_dir, env, overwrite_if_exists) except UndefinedError as err: msg = "Unable to create project directory '{}'".format( unrendered_dir) raise UndefinedVariableInTemplate(msg, err, context) # 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) logger.debug('Project directory is %s', project_dir) # if we created the output directory, then it's ok to remove it # if rendering fails delete_project_on_failure = output_directory_created if accept_hooks: _run_hook_from_repo_dir( repo_dir, 'pre_gen_project', project_dir, context, delete_project_on_failure, ) with work_in(template_dir): env.loader = FileSystemLoader('.') for root, dirs, files in os.walk('.'): # We must separate the two types of dirs into different lists. # The reason is that we don't want ``os.walk`` to go through the # unrendered directories, since they will just be copied. copy_dirs = [] render_dirs = [] for d in dirs: d_ = os.path.normpath(os.path.join(root, d)) # We check the full path, because that's how it can be # specified in the ``_copy_without_render`` setting, but # we store just the dir name if is_copy_only_path(d_, context): copy_dirs.append(d) else: render_dirs.append(d) for copy_dir in copy_dirs: indir = os.path.normpath(os.path.join(root, copy_dir)) outdir = os.path.normpath(os.path.join(project_dir, indir)) outdir = env.from_string(outdir).render(**context) logger.debug('Copying dir %s to %s without rendering', indir, outdir) shutil.copytree(indir, outdir) # We mutate ``dirs``, because we only want to go through these dirs # recursively dirs[:] = render_dirs for d in dirs: unrendered_dir = os.path.join(project_dir, root, d) try: render_and_create_dir( unrendered_dir, context, output_dir, env, overwrite_if_exists, ) except UndefinedError as err: if delete_project_on_failure: rmtree(project_dir) _dir = os.path.relpath(unrendered_dir, output_dir) msg = "Unable to create directory '{}'".format(_dir) raise UndefinedVariableInTemplate(msg, err, context) for f in files: infile = os.path.normpath(os.path.join(root, f)) if is_copy_only_path(infile, context): outfile_tmpl = env.from_string(infile) outfile_rendered = outfile_tmpl.render(**context) outfile = os.path.join(project_dir, outfile_rendered) logger.debug('Copying file %s to %s without rendering', infile, outfile) shutil.copyfile(infile, outfile) shutil.copymode(infile, outfile) continue try: generate_file( project_dir, infile, context, env, skip_if_file_exists, context_key, ) except UndefinedError as err: if delete_project_on_failure: rmtree(project_dir) msg = "Unable to create file '{}'".format(infile) raise UndefinedVariableInTemplate(msg, err, context) if accept_hooks: _run_hook_from_repo_dir( repo_dir, 'post_gen_project', project_dir, context, delete_project_on_failure, ) for o in post_gen_operator_list: o.execute() return project_dir else: if accept_hooks: _run_hook_from_repo_dir( repo_dir, 'post_gen_project', '.', # TODO: This needs context switching context, False, ) for o in post_gen_operator_list: o.execute() return None
def generate_files(repo_dir, context=None, output_dir='.', overwrite_if_exists=False): """Render 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. :param overwrite_if_exists: Overwrite the contents of the output directory if it exists. """ 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) env = StrictEnvironment( context=context, keep_trailing_newline=True, ) try: project_dir = render_and_create_dir(unrendered_dir, context, output_dir, env, overwrite_if_exists) except UndefinedError as err: msg = "Unable to create project directory '{}'".format(unrendered_dir) raise UndefinedVariableInTemplate(msg, err, context) # 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_hook_from_repo_dir(repo_dir, 'pre_gen_project', project_dir, context) with work_in(template_dir): env.loader = FileSystemLoader('.') for root, dirs, files in os.walk('.'): # We must separate the two types of dirs into different lists. # The reason is that we don't want ``os.walk`` to go through the # unrendered directories, since they will just be copied. copy_dirs = [] render_dirs = [] for d in dirs: d_ = os.path.normpath(os.path.join(root, d)) # We check the full path, because that's how it can be # specified in the ``_copy_without_render`` setting, but # we store just the dir name if copy_without_render(d_, context): copy_dirs.append(d) else: render_dirs.append(d) for copy_dir in copy_dirs: indir = os.path.normpath(os.path.join(root, copy_dir)) outdir = os.path.normpath(os.path.join(project_dir, indir)) logging.debug('Copying dir {0} to {1} without rendering' ''.format(indir, outdir)) shutil.copytree(indir, outdir) # We mutate ``dirs``, because we only want to go through these dirs # recursively dirs[:] = render_dirs for d in dirs: unrendered_dir = os.path.join(project_dir, root, d) try: render_and_create_dir(unrendered_dir, context, output_dir, env, overwrite_if_exists) except UndefinedError as err: rmtree(project_dir) _dir = os.path.relpath(unrendered_dir, output_dir) msg = "Unable to create directory '{}'".format(_dir) raise UndefinedVariableInTemplate(msg, err, context) for f in files: infile = os.path.normpath(os.path.join(root, f)) if copy_without_render(infile, context): outfile_tmpl = env.from_string(infile) outfile_rendered = outfile_tmpl.render(**context) outfile = os.path.join(project_dir, outfile_rendered) logging.debug('Copying file {0} to {1} without rendering' ''.format(infile, outfile)) shutil.copyfile(infile, outfile) shutil.copymode(infile, outfile) continue logging.debug('f is {0}'.format(f)) try: generate_file(project_dir, infile, context, env) except UndefinedError as err: rmtree(project_dir) msg = "Unable to create file '{}'".format(infile) raise UndefinedVariableInTemplate(msg, err, context) _run_hook_from_repo_dir(repo_dir, 'post_gen_project', project_dir, context) return project_dir