Passed
Pull Request — develop (#573)
by
unknown
01:37
created

doorstop.core.template   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 175
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 26
eloc 115
dl 0
loc 175
rs 10
c 0
b 0
f 0

3 Functions

Rating   Name   Duplication   Size   Complexity  
B check_latex_template_data() 0 40 8
F get_template() 0 85 15
A read_template_data() 0 12 3
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
    if document_template and not template:
64
        raise common.DoorstopError(
65
            "'template' folder found, but template flag not set."
66
        )
67
68
    # Get the builtin templates.
69
    template_assets = os.path.join(os.path.dirname(__file__), "files", "templates")
70
    builtin_template = None
71
    # Check extension and set template folder accordingly.
72
    if ext == ".md":
73
        template_assets = template_assets + "/markdown"
74
    elif ext == ".tex":
75
        template_assets = template_assets + "/latex"
76
        builtin_template = "doorstop"
77
    elif ext == ".txt":
78
        template_assets = template_assets + "/text"
79
    else:
80
        template_assets = template_assets + "/html"
81
        builtin_template = HTMLTEMPLATE
82
83
    # Remove existing templates and assets first.
84
    if os.path.isdir(assets_dir):
85
        log.info("Deleting contents of assets directory %s", assets_dir)
86
        common.delete_contents(assets_dir)
87
    if os.path.isdir(template_dir):
88
        log.info("Deleting contents of template directory %s", template_dir)
89
        common.delete(template_dir)
90
91
    # Create the output path only.
92
    if not os.path.isdir(output_dir):
93
        os.makedirs(output_dir)
94
95
    # Copy template from document if it exists.
96
    if document_template:
97
        os.makedirs(template_dir)
98
        log.info(
99
            "Copying %s to %s",
100
            document_template,
101
            os.path.join(os.path.dirname(path), "template"),
102
        )
103
        common.copy_dir_contents(document_template, template_dir)
104
    # Only create template_dir if template actually exists.
105
    elif os.path.isdir(template_assets):
106
        os.makedirs(template_dir)
107
        log.info(
108
            "Copying %s to %s",
109
            template_assets,
110
            os.path.join(os.path.dirname(path), "template"),
111
        )
112
        common.copy_dir_contents(template_assets, template_dir)
113
114
    # Return correct template and assets folder.
115
    if not template:
116
        return assets_dir, builtin_template
117
118
    return assets_dir, template
119
120
121
def read_template_data(assets_dir, template):
122
    """Read the template data file and return the data."""
123
    try:
124
        template_data_file = os.path.abspath(
125
            os.path.join(assets_dir, "..", "template", "%s.yml" % template)
126
        )
127
        with open(template_data_file, "r") as f:
128
            template_data = safe_load(f)
129
    except Exception as ex:
130
        msg = "Template data load '{}' failed: {}".format(template_data_file, ex)
131
        raise DoorstopError(msg)
132
    return template_data
133
134
135
def check_latex_template_data(template_data):
136
    """Check that all required packages have been defined in template data."""
137
    # Check basics first.
138
    if "usepackage" not in template_data:
139
        log.error(
140
            "There is no dictionary of packages in the template configuration file."
141
        )
142
        raise DoorstopError(
143
            "There is no list of packages in the template configuration file."
144
        )
145
    if not isinstance(template_data["usepackage"], dict):
146
        log.error(
147
            "The 'usepackage' data in the configuration file is not a dictionary."
148
        )
149
        raise DoorstopError(
150
            "The 'usepackage' data in the configuration file is not a dictionary."
151
        )
152
153
    # Iterate over all required packages.
154
    for package, options in REQUIRED_LATEX_PACKAGES.items():
155
        if package not in template_data["usepackage"]:
156
            log.error(
157
                "%s is a required package. Please add it to the template configuration file.",
158
                package,
159
            )
160
            raise DoorstopError(
161
                "%s is a required package. Please add it to the template configuration file."
162
                % package,
163
            )
164
        if options:
165
            for option in options:
166
                if option not in template_data["usepackage"][package]:
167
                    log.error(
168
                        "%s is a required option for the %s package. Please add it to the template configuration file.",
169
                        option,
170
                        package,
171
                    )
172
                    raise DoorstopError(
173
                        "%s is a required option for the %s package. Please add it to the template configuration file."
174
                        % (option, package)
175
                    )
176