def add_doc_requirements(struct: Structure, opts: ScaffoldOpts) -> ActionParams: """In order to build the docs new requirements are necessary now. The default ``tox.ini`` generated by PyScaffold should already include ``-e {toxinidir}/docs/requirements.txt`` in its dependencies. Therefore, this action will make sure ``tox -e docs`` run without problems. It is important to sort the requirements otherwise pre-commit will raise an error for a newly generated file and that would correspond to a bad user experience. """ leaf = struct.get("docs", {}).get("requirements.txt") original, file_op = reify_leaf(leaf, opts) contents = original or "" missing = [req for req in DOC_REQUIREMENTS if req not in contents] requirements = [*contents.splitlines(), *missing] # It is not trivial to sort the requirements because they include a comment header j = (i for (i, line) in enumerate(requirements) if line and not is_commented(line)) comments_end = next( j, 0) # first element of the iterator is a non commented line comments = requirements[:comments_end] sorted_requirements = sorted(requirements[comments_end:]) new_contents = "\n".join([*comments, *sorted_requirements]) + "\n" # ^ pre-commit requires a new line at the end of the file files: Structure = {"docs": {"requirements.txt": (new_contents, file_op)}} return merge(struct, files), opts
def add_dsproject(struct: Structure, opts: ScaffoldOpts) -> ActionParams: """Adds basic module for custom extension See :obj:`pyscaffold.actions.Action` """ gitignore_all = (template("gitignore_all"), NO_OVERWRITE) files: Structure = { "configs": {".gitignore": ("", NO_OVERWRITE)}, "data": { ".gitignore": (template("gitignore_data"), NO_OVERWRITE), **{ folder: {".gitignore": gitignore_all} for folder in ("external", "interim", "preprocessed", "raw") }, }, "environment.yml": (template("environment_yml"), NO_OVERWRITE), "models": {".gitignore": gitignore_all}, "notebooks": {"template.ipynb": (template("template_ipynb"), NO_OVERWRITE)}, "references": {".gitignore": ("", NO_OVERWRITE)}, "reports": {"figures": {".gitignore": ("", NO_OVERWRITE)}}, "scripts": { "train_model.py": ( template("train_model_py"), add_permissions(stat.S_IXUSR, NO_OVERWRITE), ) }, } return merge(struct, files), opts
def add_files(struct: Structure, opts: ScaffoldOpts) -> ActionParams: """Add custom extension files. See :obj:`pyscaffold.actions.Action`""" files: Structure = { ".github": { "workflows": { "publish-package.yml": (template("publish_package"), NO_OVERWRITE) } }, "README.rst": (template("readme"), NO_OVERWRITE), "CONTRIBUTING.rst": (template("contributing"), NO_OVERWRITE), "setup.cfg": modify_setupcfg(struct["setup.cfg"], opts), "src": { opts["package"]: { f"{EXTENSION_FILE_NAME}.py": (template("extension"), NO_OVERWRITE) } }, "tests": { "__init__.py": ("", NO_OVERWRITE), "conftest.py": (template("conftest"), NO_OVERWRITE), "helpers.py": (template("helpers"), NO_OVERWRITE), "test_custom_extension.py": ( template("test_custom_extension"), NO_OVERWRITE, ), }, } return merge(struct, files), opts
def test_empty_string_leads_to_empty_file_during_merge(): # When the original struct contains a leaf, struct = {"a": {"b": "0"}} # and the merged struct overrides it with an empty content extra_files = {"a": {"b": ""}} struct = structure.merge(struct, extra_files) # then the resulting content should exist and be empty assert struct["a"]["b"] == ""
def test_merge_rules_just_in_original(): # When an update rule exists in the original struct, struct = {"a": {"b": ("0", SKIP_ON_UPDATE)}} # but not in the merged, extra_files = {"a": {"b": "3"}} struct = structure.merge(struct, extra_files) # then just the content should be updated # and the rule should be kept identical assert struct["a"]["b"] == ("3", SKIP_ON_UPDATE)
def replace_files(struct: Structure, opts: ScaffoldOpts) -> ActionParams: """Replace all rst files to proper md and activate Sphinx md. See :obj:`pyscaffold.actions.Action` """ # Define new files NO_OVERWRITE = no_overwrite() files: Structure = { "README.md": (template("readme"), NO_OVERWRITE), "AUTHORS.md": (template("authors"), NO_OVERWRITE), "CHANGELOG.md": (template("changelog"), NO_OVERWRITE), "CONTRIBUTING.md": (template("contributing"), NO_OVERWRITE), "docs": { "index.md": (template("index"), NO_OVERWRITE), "readme.md": (default_myst_include("README.md"), NO_OVERWRITE), "license.md": (template("license"), NO_OVERWRITE), "authors.md": (default_myst_include("AUTHORS.md"), NO_OVERWRITE), "changelog.md": (default_myst_include("CHANGELOG.md"), NO_OVERWRITE), "contributing.md": (default_myst_include("CONTRIBUTING.md"), NO_OVERWRITE), }, } # TODO: Automatically convert RST to MD # # >>> content, file_op = reify_leaf(struct.get("CONTRIBUTING.rst"), opts) # >>> md_content = rst_to_myst(content or "", **RST2MYST_OPTS).text # >>> files["CONTRIBUTING.md"] = (md_content, file_op) # # Currently there is a problem in rst-to-myst, preventing automatic conversion: # https://github.com/executablebooks/rst-to-myst/issues/33#issuecomment-922264030 # Modify pre-existing files content, file_op = reify_leaf(struct["setup.cfg"], opts) files["setup.cfg"] = (add_long_desc(content), file_op) content, file_op = reify_leaf(struct["docs"]["conf.py"], opts) files["docs"]["conf.py"] = (add_myst(content), file_op) # Remove all unnecessary .rst files from struct unnecessary = [ "README.rst", "AUTHORS.rst", "CHANGELOG.rst", "CONTRIBUTING.rst", "docs/index.rst", "docs/readme.rst", "docs/license.rst", "docs/authors.rst", "docs/changelog.rst", "docs/contributing.rst", ] struct = reduce(reject, unnecessary, struct) return merge(struct, files), opts
def test_merge_basics(): # Given an existing struct, struct = {"a": {"b": {"c": "1", "d": "2"}}} # when it is merged to another struct with some common folder extra_files = {"a": {"b": {"c": "0"}, "e": "2"}, "f": {"g": {"h": "0"}}} struct = structure.merge(struct, extra_files) # then the result, should contain both files from the original and the # merged struct, assert struct["a"]["b"]["d"] == "2" assert struct["f"]["g"]["h"] == "0" assert struct["a"]["e"] == "2" # the common leaves should be overridden and a tuple (content, rule) assert struct["a"]["b"]["c"] == "0"
def add_files(struct: Structure, opts: ScaffoldOpts) -> ActionParams: """Add some Travis files to structure Args: struct: project representation as (possibly) nested :obj:`dict`. opts: given options, see :obj:`create_project` for an extensive list. Returns: struct, opts: updated project representation and options """ files: Structure = { ".travis.yml": (template("travis"), no_overwrite()), "tests": { "travis_install.sh": (template("travis_install"), no_overwrite()) }, } return structure.merge(struct, files), opts
def add_files(struct, opts): nov, sou = operations.no_overwrite(), operations.skip_on_update() struct = structure.ensure(struct, "tests/file0", "new") struct = structure.ensure(struct, "tests/file1", "new", nov) struct = structure.ensure(struct, "tests/file2", "new", sou) struct = structure.merge( struct, { "tests": { "file3": ("new", nov), "file4": ("new", sou), "file5": ("new", operations.create), "file6": "new", } }, ) return struct, opts
def add_files(struct, opts): struct = structure.ensure(struct, "tests/extra.file", "content") struct = structure.merge(struct, {"tests": {"another.file": "content"}}) return struct, opts