Completed
Push — develop ( 0f6046...f40dba )
by Jace
15s queued 13s
created

doorstop.core.template.get_template()   F

Complexity

Conditions 14

Size

Total Lines 81
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 50
dl 0
loc 81
rs 3.6
c 0
b 0
f 0
cc 14
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like doorstop.core.template.get_template() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# SPDX-License-Identifier: LGPL-3.0-only
2
3
"""Functions to apply templates to documents."""
4
5
import os
6
7
from yaml import safe_load
8
9
from doorstop import common
10
from doorstop.common import DoorstopError
11
from doorstop.core import Document
12
from doorstop.core.types import is_tree
13
14
CSS = os.path.join(os.path.dirname(__file__), "files", "doorstop.css")
15
HTMLTEMPLATE = "sidebar"
16
INDEX = "index.html"
17
MATRIX = "traceability.csv"
18
19
REQUIRED_LATEX_PACKAGES = {
20
    "inputenc": None,
21
    "amsmath": None,
22
    "ulem": None,
23
    "longtable": None,
24
    "fancyvrb": None,
25
    "xr-hyper": None,
26
    "hyperref": ["unicode", "colorlinks"],
27
    "zref-user": None,
28
    "zref-xr": None,
29
}
30
31
log = common.logger(__name__)
32
33
34
def get_template(obj, path, ext, template):
35
    """Return the correct template.
36
37
    Return correct template according to the published type.
38
    If a template has been specified, use that. Otherwise, use doorstop's
39
    built-in templates.
40
41
    Create the output folder and template folder.
42
    """
43
44
    # Set assets, ouput and template folders.
45
    if is_tree(obj):
46
        assets_dir = os.path.join(path, Document.ASSETS)  # path is a directory name
47
        document_template = obj.documents[0].template
48
        template_dir = os.path.join(path, "template")
49
        output_dir = path
50
    else:
51
        assets_dir = os.path.join(
52
            os.path.dirname(path), Document.ASSETS
53
        )  # path is a filename
54
        document_template = obj.template
55
        template_dir = os.path.join(os.path.dirname(path), "template")
56
        output_dir = os.path.dirname(path)
57
58
    # Check for custom template and verify that it is available.
59
    if template and not document_template:
60
        raise common.DoorstopError(
61
            "Template flag set, but no 'template' folder was found."
62
        )
63
64
    # Get the builtin templates.
65
    template_assets = os.path.join(os.path.dirname(__file__), "files", "templates")
66
    builtin_template = None
67
    # Check extension and set template folder accordingly.
68
    if ext == ".md":
69
        template_assets = template_assets + "/markdown"
70
    elif ext == ".tex":
71
        template_assets = template_assets + "/latex"
72
        builtin_template = "doorstop"
73
    elif ext == ".txt":
74
        template_assets = template_assets + "/text"
75
    else:
76
        template_assets = template_assets + "/html"
77
        builtin_template = HTMLTEMPLATE
78
79
    # Remove existing templates and assets first.
80
    if os.path.isdir(assets_dir):
81
        log.info("Deleting contents of assets directory %s", assets_dir)
82
        common.delete_contents(assets_dir)
83
    if os.path.isdir(template_dir):
84
        log.info("Deleting contents of template directory %s", template_dir)
85
        common.delete(template_dir)
86
87
    # Create the output path only.
88
    if not os.path.isdir(output_dir):
89
        os.makedirs(output_dir)
90
91
    # Copy template from document if it exists and template is given.
92
    if document_template and template:
93
        os.makedirs(template_dir)
94
        log.info(
95
            "Copying %s to %s",
96
            document_template,
97
            os.path.join(os.path.dirname(path), "template"),
98
        )
99
        common.copy_dir_contents(document_template, template_dir)
100
    # Only create template_dir if template actually exists.
101
    elif os.path.isdir(template_assets):
102
        os.makedirs(template_dir)
103
        log.info(
104
            "Copying %s to %s",
105
            template_assets,
106
            os.path.join(os.path.dirname(path), "template"),
107
        )
108
        common.copy_dir_contents(template_assets, template_dir)
109
110
    # Return correct template and assets folder.
111
    if not template:
112
        return assets_dir, builtin_template
113
114
    return assets_dir, template
115
116
117
def read_template_data(assets_dir, template):
118
    """Read the template data file and return the data."""
119
    try:
120
        template_data_file = os.path.abspath(
121
            os.path.join(assets_dir, "..", "template", "%s.yml" % template)
122
        )
123
        with open(template_data_file, "r") as f:
124
            template_data = safe_load(f)
125
    except Exception as ex:
126
        msg = "Template data load '{}' failed: {}".format(template_data_file, ex)
127
        raise DoorstopError(msg)
128
    return template_data
129
130
131
def check_latex_template_data(template_data):
132
    """Check that all required packages have been defined in template data."""
133
    # Check basics first.
134
    if "usepackage" not in template_data:
135
        log.error(
136
            "There is no dictionary of packages in the template configuration file."
137
        )
138
        raise DoorstopError(
139
            "There is no list of packages in the template configuration file."
140
        )
141
    if not isinstance(template_data["usepackage"], dict):
142
        log.error(
143
            "The 'usepackage' data in the configuration file is not a dictionary."
144
        )
145
        raise DoorstopError(
146
            "The 'usepackage' data in the configuration file is not a dictionary."
147
        )
148
149
    # Iterate over all required packages.
150
    for package, options in REQUIRED_LATEX_PACKAGES.items():
151
        if package not in template_data["usepackage"]:
152
            log.error(
153
                "%s is a required package. Please add it to the template configuration file.",
154
                package,
155
            )
156
            raise DoorstopError(
157
                "%s is a required package. Please add it to the template configuration file."
158
                % package,
159
            )
160
        if options:
161
            for option in options:
162
                if option not in template_data["usepackage"][package]:
163
                    log.error(
164
                        "%s is a required option for the %s package. Please add it to the template configuration file.",
165
                        option,
166
                        package,
167
                    )
168
                    raise DoorstopError(
169
                        "%s is a required option for the %s package. Please add it to the template configuration file."
170
                        % (option, package)
171
                    )
172