Passed
Push — develop ( e91dc8...2ae66f )
by Jace
04:37 queued 14s
created

doorstop.core.publisher   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 16
eloc 78
dl 0
loc 151
rs 10
c 0
b 0
f 0

3 Functions

Rating   Name   Duplication   Size   Complexity  
A publish_lines() 0 12 2
C publish() 0 90 11
A check() 0 27 3
1
# SPDX-License-Identifier: LGPL-3.0-only
2
3
"""Functions to publish documents and items."""
4
5
import os
6
7
from doorstop import common, settings
8
from doorstop.common import DoorstopError
9
from doorstop.core.publishers.html import HtmlPublisher
10
from doorstop.core.publishers.latex import LaTeXPublisher
11
from doorstop.core.publishers.markdown import MarkdownPublisher
12
from doorstop.core.publishers.text import TextPublisher
13
from doorstop.core.types import is_tree, iter_documents
14
15
log = common.logger(__name__)
16
17
18
def publish(
19
    obj,
20
    path,
21
    ext=None,
22
    linkify=None,
23
    index=None,
24
    matrix=None,
25
    template=None,
26
    toc=True,
27
    **kwargs,
28
):
29
    """Publish an object to a given format.
30
31
    The function can be called in two ways:
32
33
    1. document or item-like object + output file path
34
    2. tree-like object + output directory path
35
36
    :param obj: (1) Item, list of Items, Document or (2) Tree
37
    :param path: (1) output file path or (2) output directory path
38
    :param ext: file extension to override output extension
39
    :param linkify: turn links into hyperlinks (for Markdown, HTML or LaTeX)
40
    :param index: create an index.html (for HTML)
41
    :param matrix: create a traceability matrix, traceability.csv
42
43
    :raises: :class:`doorstop.common.DoorstopError` for unknown file formats
44
45
    :return: output location if files created, else None
46
47
    """
48
    # Check that we have something to publish first.
49
    if is_tree(obj):
50
        if len(obj) == 0:
51
            raise DoorstopError("nothing to publish")
52
53
    # Determine the output format
54
    ext = ext or os.path.splitext(path)[-1] or ".html"
55
    publisher = check(ext, obj=obj)
56
57
    # Setup publisher.
58
    publisher.setPath(path)
59
    publisher.setup(linkify, index, matrix)
60
61
    # Process templates.
62
    publisher.processTemplates(template)
63
    log.info("Template = {}".format(publisher.getTemplate()))
64
    # Run all preparations.
65
    publisher.preparePublish()
66
67
    # Publish documents
68
    count = 0
69
    for obj2, path2 in iter_documents(obj, path, ext):
70
        count += 1
71
        # Run all special actions.
72
        publisher.publishAction(obj2, path2)
73
74
        # Publish content to the specified path
75
        log.info("publishing to {}...".format(publisher.getDocumentPath()))
76
        lines = publish_lines(
77
            obj2,
78
            ext,
79
            publisher=publisher,
80
            linkify=publisher.getLinkify(),
81
            template=publisher.getTemplate(),
82
            toc=toc,
83
            **kwargs,
84
        )
85
        common.write_lines(
86
            lines, publisher.getDocumentPath(), end=settings.WRITE_LINESEPERATOR
87
        )
88
        if obj2.copy_assets(publisher.getAssetsPath()):
89
            log.info(
90
                "Copied assets from %s to %s", obj.assets, publisher.getAssetsPath()
91
            )
92
93
    # Create index
94
    if publisher.getIndex():
95
        publisher.create_index(path, tree=obj if is_tree(obj) else None)
96
97
    # Create traceability matrix
98
    if (publisher.getIndex() or ext == ".tex") and (publisher.getMatrix()):
99
        publisher.create_matrix(path)
100
101
    # Run all concluding operations.
102
    publisher.concludePublish()
103
104
    # Return the published path
105
    msg = "published to {} file{}".format(count, "s" if count > 1 else "")
106
    log.info(msg)
107
    return path
108
109
110
def publish_lines(obj, ext=".txt", publisher=None, **kwargs):
111
    """Yield lines for a report in the specified format.
112
113
    :param obj: Item, list of Items, or Document to publish
114
    :param ext: file extension to specify the output format
115
116
    """
117
    if not publisher:
118
        publisher = check(ext, obj=obj)
119
    gen = publisher.get_line_generator()
120
    log.debug("yielding {} as lines of {}...".format(obj, ext))
121
    yield from gen(obj, **kwargs)
122
123
124
def check(ext, obj=None):
125
    """Confirm an extension is supported for publish.
126
127
    :raises: :class:`doorstop.common.DoorstopError` for unknown formats
128
129
    :return: publisher class if available
130
131
    """
132
    # Mapping from file extension to class.
133
    PUBLISHER_LIST = {
134
        ".txt": TextPublisher(obj, ext),
135
        ".md": MarkdownPublisher(obj, ext),
136
        ".html": HtmlPublisher(obj, ext),
137
        ".tex": LaTeXPublisher(obj, ext),
138
    }
139
140
    exts = ", ".join(ext for ext in PUBLISHER_LIST)
141
    msg = "unknown publish format: {} (options: {})".format(ext or None, exts)
142
    exc = DoorstopError(msg)
143
144
    try:
145
        publisherClass = PUBLISHER_LIST[ext]
146
    except KeyError:
147
        raise exc from None
148
    else:
149
        log.debug("found publisher class for: {}".format(ext))
150
        return publisherClass
151