doorstop.core.template.get_template()   F
last analyzed

Complexity

Conditions 23

Size

Total Lines 111
Code Lines 71

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 71
dl 0
loc 111
rs 0
c 0
b 0
f 0
cc 23
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
HTMLTEMPLATE = "doorstop"
15
INDEX = "index.html"
16
MATRIX = "traceability.csv"
17
18
REQUIRED_LATEX_PACKAGES = {
19
    "inputenc": None,
20
    "amsmath": None,
21
    "ulem": None,
22
    "longtable": None,
23
    "fancyvrb": None,
24
    "xr-hyper": None,
25
    "hyperref": ["unicode", "colorlinks"],
26
    "zref-user": None,
27
    "zref-xr": None,
28
}
29
30
log = common.logger(__name__)
31
32
33
def get_template(obj, path, ext, template):
34
    """Return the correct template.
35
36
    Return correct template according to the published type.
37
    If a template has been specified, use that. Otherwise, use doorstop's
38
    built-in templates.
39
40
    Create the output folder and template folder.
41
    """
42
43
    # Set assets, ouput and template folders.
44
    # If path ends with ext, use dirname since it is a file.
45
    if path.endswith(ext):
46
        path = os.path.dirname(path)
47
    # If html, add documents to path.
48
    if ext == ".html":
49
        assets_dir = os.path.join(path, "documents", Document.ASSETS)
50
    else:
51
        assets_dir = os.path.join(path, Document.ASSETS)
52
    template_dir = os.path.join(path, "template")
53
    output_dir = path
54
55
    if is_tree(obj):
56
        document_template = None
57
        template_count = 0
58
        for each in obj.documents:
59
            if each.template:
60
                document_template = each.template
61
                template_count += 1
62
        if template_count > 1:
63
            raise common.DoorstopError(
64
                """Multiple templates found in tree. Please specify a single template for the tree.
65
I.e., only one of the documents in the tree should have a template folder."""
66
            )
67
    else:
68
        document_template = obj.template
69
70
    # Check for custom template and verify that it is available.
71
    if template and not document_template:
72
        raise common.DoorstopError(
73
            "Template flag set, but no 'template' folder was found."
74
        )
75
76
    # Get the builtin templates.
77
    template_assets = os.path.join(os.path.dirname(__file__), "files", "templates")
78
    builtin_template = None
79
    # Check extension and set template folder accordingly.
80
    if ext == ".md":
81
        template_assets = os.sep.join([template_assets, "markdown"])
82
    elif ext == ".tex":
83
        template_assets = os.sep.join([template_assets, "latex"])
84
        builtin_template = "doorstop"
85
    elif ext == ".txt":
86
        template_assets = os.sep.join([template_assets, "text"])
87
    else:
88
        template_assets = os.sep.join([template_assets, "html"])
89
        builtin_template = HTMLTEMPLATE
90
91
    # Remove existing templates and assets first.
92
    if os.path.isdir(assets_dir):
93
        log.info("Deleting contents of assets directory %s", assets_dir)
94
        common.delete_contents(assets_dir)
95
    if os.path.isdir(template_dir):
96
        log.info("Deleting contents of template directory %s", template_dir)
97
        common.delete(template_dir)
98
99
    # Create the output path only.
100
    if not os.path.isdir(output_dir):
101
        os.makedirs(output_dir)
102
103
    # Copy template from document if it exists and template is given.
104
    if document_template and template:
105
        os.makedirs(template_dir)
106
        if is_tree(obj):
107
            for each in obj.documents:
108
                log.info(
109
                    "Copying %s to %s",
110
                    each.template,
111
                    os.path.join(os.path.dirname(path), "template"),
112
                )
113
                common.copy_dir_contents(each.template, template_dir)
114
        else:
115
            log.info(
116
                "Copying %s to %s",
117
                document_template,
118
                os.path.join(os.path.dirname(path), "template"),
119
            )
120
            common.copy_dir_contents(document_template, template_dir)
121
122
    # Only create template_dir if template actually exists.
123
    elif os.path.isdir(template_assets):
124
        os.makedirs(template_dir)
125
        log.info(
126
            "Copying %s to %s",
127
            template_assets,
128
            os.path.join(os.path.dirname(path), "template"),
129
        )
130
        common.copy_dir_contents(template_assets, template_dir)
131
        # If html template, also copy the default views files.
132
        if ext == ".html" and builtin_template:
133
            views_src_dir = os.path.join(os.path.dirname(__file__), "..", "views")
134
            views_tgt_dir = os.path.join(template_dir, "views")
135
            log.info("Copying %s to %s", views_src_dir, views_tgt_dir)
136
            os.makedirs(views_tgt_dir)
137
            common.copy_dir_contents(views_src_dir, views_tgt_dir)
138
139
    # Return correct template and assets folder.
140
    if not template:
141
        return assets_dir, builtin_template
142
143
    return assets_dir, template
144
145
146
def read_template_data(assets_dir, template):
147
    """Read the template data file and return the data."""
148
    try:
149
        template_data_file = os.path.abspath(
150
            os.path.join(assets_dir, "..", "template", "%s.yml" % template)
151
        )
152
        with open(template_data_file, "r") as f:
153
            template_data = safe_load(f)
154
    except Exception as ex:
155
        msg = "Template data load '{}' failed: {}".format(template_data_file, ex)
156
        raise DoorstopError(msg)
157
    return template_data
158
159
160
def check_latex_template_data(template_data, filename=None):
161
    """Check that all required packages have been defined in template data."""
162
    # Check basics first.
163
    if not isinstance(template_data, dict):
164
        log.error(
165
            "There seems to be a problem with the template configuration file '{}'.".format(
166
                filename
167
            )
168
        )
169
        raise DoorstopError(
170
            "There seems to be a problem with the template configuration file '{}'.".format(
171
                filename
172
            )
173
        )
174
    if "usepackage" not in template_data:
175
        log.error(
176
            "There is no dictionary of packages in the template configuration file '{}'.".format(
177
                filename
178
            )
179
        )
180
        raise DoorstopError(
181
            "There is no list of packages in the template configuration file '{}'.".format(
182
                filename
183
            )
184
        )
185
    if not isinstance(template_data["usepackage"], dict):
186
        log.error(
187
            "The 'usepackage' data in the configuration file is not a dictionary '{}'.".format(
188
                filename
189
            )
190
        )
191
        raise DoorstopError(
192
            "The 'usepackage' data in the configuration file is not a dictionary '{}'.".format(
193
                filename
194
            )
195
        )
196
197
    # Iterate over all required packages.
198
    for package, options in REQUIRED_LATEX_PACKAGES.items():
199
        if package not in template_data["usepackage"]:
200
            log.error(
201
                "%s is a required package. Please add it to the template configuration file.",
202
                package,
203
            )
204
            raise DoorstopError(
205
                "%s is a required package. Please add it to the template configuration file."
206
                % package,
207
            )
208
        if options:
209
            for option in options:
210
                if option not in template_data["usepackage"][package]:
211
                    log.error(
212
                        "%s is a required option for the %s package. Please add it to the template configuration file.",
213
                        option,
214
                        package,
215
                    )
216
                    raise DoorstopError(
217
                        "%s is a required option for the %s package. Please add it to the template configuration file."
218
                        % (option, package)
219
                    )
220