# -*- coding: utf-8 -*-
"""
Functionality to generate and work with the directory structure of a project
"""
from __future__ import absolute_import, print_function
import os
from os.path import exists as path_exists
from os.path import join as join_path
from . import templates, utils
from .contrib.six import string_types
from .log import logger
[docs]class FileOp(object):
"""Namespace for file operations during an update"""
NO_OVERWRITE = 0
"""Do not overwrite an existing file during update
(still created if not exists)
"""
NO_CREATE = 1
"""Do not create the file during an update"""
[docs]def define_structure(_, opts):
"""Creates the project structure as dictionary of dictionaries
Args:
struct (dict): previous directory structure (ignored)
opts (dict): options of the project
Returns:
tuple(dict, dict):
structure as dictionary of dictionaries and input options
"""
struct = {opts['project']: {
'.gitignore': (templates.gitignore(opts), FileOp.NO_OVERWRITE),
'src': {
opts['package']: {'__init__.py': templates.init(opts),
'skeleton.py': (templates.skeleton(opts),
FileOp.NO_CREATE)},
},
'tests': {'conftest.py': (templates.conftest_py(opts),
FileOp.NO_OVERWRITE),
'test_skeleton.py': (templates.test_skeleton(opts),
FileOp.NO_CREATE)},
'docs': {'conf.py': templates.sphinx_conf(opts),
'authors.rst': templates.sphinx_authors(opts),
'index.rst': (templates.sphinx_index(opts),
FileOp.NO_OVERWRITE),
'license.rst': templates.sphinx_license(opts),
'changelog.rst': templates.sphinx_changelog(opts),
'Makefile': templates.sphinx_makefile(opts),
'_static': {
'.gitignore': templates.gitignore_empty(opts)}},
'README.rst': (templates.readme(opts), FileOp.NO_OVERWRITE),
'AUTHORS.rst': (templates.authors(opts), FileOp.NO_OVERWRITE),
'LICENSE.txt': (templates.license(opts), FileOp.NO_OVERWRITE),
'CHANGELOG.rst': (templates.changelog(opts), FileOp.NO_OVERWRITE),
'setup.py': templates.setup_py(opts),
'setup.cfg': (templates.setup_cfg(opts), FileOp.NO_OVERWRITE),
'requirements.txt': (templates.requirements(opts),
FileOp.NO_OVERWRITE),
'.coveragerc': (templates.coveragerc(opts), FileOp.NO_OVERWRITE)}}
return struct, opts
[docs]def create_structure(struct, opts, prefix=None):
"""Manifests a directory structure in the filesystem
Args:
struct (dict): directory structure as dictionary of dictionaries
opts (dict): options of the project
prefix (str): prefix path for the structure
Returns:
tuple(dict, dict):
directory structure as dictionary of dictionaries (similar to
input, but only containing the files that actually changed) and
input options
Raises:
:obj:`RuntimeError`: raised if content type in struct is unknown
"""
update = opts.get('update') or opts.get('force')
pretend = opts.get('pretend')
if prefix is None:
prefix = os.getcwd()
changed = {}
for name, content in struct.items():
if isinstance(content, string_types):
utils.create_file(join_path(prefix, name), content, pretend)
changed[name] = content
elif isinstance(content, dict):
utils.create_directory(join_path(prefix, name), update, pretend)
changed[name], _ = create_structure(
struct[name], opts, prefix=join_path(prefix, name))
elif content is None:
pass
else:
raise RuntimeError("Don't know what to do with content type "
"{type}.".format(type=type(content)))
return changed, opts
[docs]def apply_update_rules(struct, opts, prefix=None):
"""Apply update rules using :obj:`~.FileOp` to a directory structure.
As a result the filtered structure keeps only the files that actually will
be written.
Args:
opts (dict): options of the project, containing the following flags:
- **update**: when the project already exists and should be updated
- **force**: overwrite all the files that already exist
struct (dict): directory structure as dictionary of dictionaries
(in this tree representation, each leaf can be just a
string or a tuple also containing an update rule)
prefix (str): prefix path for the structure
Returns:
tuple(dict, dict):
directory structure with keys removed according to the rules
(in this tree representation, all the leaves are strings) and input
options
"""
if prefix is None:
prefix = os.getcwd()
filtered = {}
for k, v in struct.items():
if isinstance(v, dict):
v, _ = apply_update_rules(v, opts, join_path(prefix, k))
else:
path = join_path(prefix, k)
v = apply_update_rule_to_file(path, v, opts)
if v:
filtered[k] = v
return filtered, opts
[docs]def apply_update_rule_to_file(path, value, opts):
"""Applies the update rule to a given file path
Args:
path (str): file path
value (tuple or str): content (and update rule)
opts (dict): options of the project, containing the following flags:
- **update**: when the project already exists and should be updated
- **force**: overwrite all the files that already exist
Returns:
content of the file if it should be generated or None otherwise.
"""
if isinstance(value, (tuple, list)):
content, rule = value
else:
content, rule = value, None
update = opts.get('update')
force = opts.get('force')
skip = update and not force and (
rule == FileOp.NO_CREATE or
path_exists(path) and rule == FileOp.NO_OVERWRITE)
if skip:
logger.report('skip', path)
return None
return content