def prompt_for_config(context, no_input=False, custom_filters={}): """Prompt user to enter a new config. :param dict context: Source for field names and sample values. :param no_input: Prompt the user at command line for manual configuration? """ cookiecutter_dict = OrderedDict([]) env = StrictEnvironment(context=context) env.filters.update(custom_filters) # 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 context['cookiecutter'].items(): if key.startswith('_') and not key.startswith('__'): cookiecutter_dict[key] = raw continue elif key.startswith('__'): cookiecutter_dict[key] = render_variable(env, raw, cookiecutter_dict) continue try: if isinstance(raw, list): # 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 context['cookiecutter'].items(): # Skip private type dicts if key.startswith('_') and not key.startswith('__'): continue 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 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 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 parse_context(context, env, cc_dict, context_key, no_input): """Parse the context and iterate over values. :param dict context: Source for field names and sample values. :param env: Jinja environment to render values with. :param context_key: The key to insert all the outputs under in the context dict. :param no_input: Prompt the user at command line for manual configuration. :param existing_context: A dictionary of values to use during rendering. :return: cc_dict """ for key, raw in context[context_key].items(): if key.startswith(u'_') and not key.startswith('__'): cc_dict[key] = raw continue elif key.startswith('__'): cc_dict[key] = render_variable(env, raw, cc_dict, context_key) continue try: if isinstance(raw, list): # We are dealing with a choice variable val = prompt_choice_for_config(cc_dict, env, key, raw, no_input, context_key) cc_dict[key] = val elif not isinstance(raw, dict): # We are dealing with a regular variable val = render_variable(env, raw, cc_dict, context_key) if not no_input: val = read_user_variable(key, val) cc_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 context[context_key].items(): if key.startswith('_') and not key.startswith('__'): continue try: if isinstance(raw, dict): # dict parsing logic if 'type' not in raw: val = render_variable(env, raw, cc_dict, context_key) if not no_input: val = read_user_dict(key, val) cc_dict[key] = val else: cc_dict = parse_operator( context, key, dict(cc_dict), no_input=no_input, context_key=context_key, ) except UndefinedError as err: msg = "Unable to render variable '{}'".format(key) raise UndefinedVariableInTemplate(msg, err, context) 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) logger.debug("Generating project from {}...".format(template_dir)) context = context or OrderedDict([]) 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, 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 {}".format(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 _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)) logger.debug("Copying dir {} to {} 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: 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 {} to {} without rendering" "".format(infile, outfile)) shutil.copyfile(infile, outfile) shutil.copymode(infile, outfile) continue try: generate_file(project_dir, infile, context, env) except UndefinedError as err: if delete_project_on_failure: 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, delete_project_on_failure, ) return project_dir