utils.py 6.51 KB
Newer Older
1
2
import os
import shutil
3
import itertools
4
import datetime
5

6
import boop.boopsy as boopsy
berumuron's avatar
berumuron committed
7
from boop.atom import Atom
8
from boop.article import Article
9
from boop.page import Page
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26


def init_output_folder(output_path):
    """Create the output directory, empty it if it contains files.
    """
    if os.path.isdir(output_path):
        # it should be a directory...
        shutil.rmtree(output_path)
    elif os.path.exists(output_path):
        # ... but it also can be a simple file if user created it manually!
        os.remove(output_path)
    os.mkdir(output_path)


def build_article_from_filepath(filepath, configuration, serie=None):
    """Read a file and initialize an Article from it.
    """
berumuron's avatar
berumuron committed
27
    last_modification = os.path.getmtime(filepath)
28
29
30
    filepath_no_ext, _ = os.path.splitext(filepath)
    slug = os.path.basename(filepath_no_ext)
    with open(filepath, "r") as article_file:
berumuron's avatar
berumuron committed
31
        article = Article(
32
33
            slug, article_file.read(), configuration=configuration, serie=serie
        )
berumuron's avatar
berumuron committed
34
35
        article.meta["ARTICLE_LAST_MODIFICATION"] = last_modification
        return article
36
37


38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def build_page_from_filepath(filepath, configuration, basename=None, slug=None):
    """Read a file and initialize a Page from it.
    """
    filepath_no_ext, _ = os.path.splitext(filepath)
    if slug:
        final_slug = slug
    elif basename and filepath_no_ext.startswith(basename):
        index_basename = len(basename) + 1
        final_slug = filepath_no_ext[index_basename:]
    else:
        final_slug = os.path.basename(filepath_no_ext)

    with open(filepath, "r") as page_file:
        return Page(final_slug, page_file.read(), configuration=configuration)


54
def build_blog_page(articles, series, configuration):
55
    articles = [article for article in articles.values() if not article.is_private()]
56
    articles.sort(key=lambda article: article.date(), reverse=True)
57
    series.sort(key=lambda serie: serie.title(), reverse=False)
58

berumuron's avatar
berumuron committed
59
60
61
    slug = configuration["SITE_BLOG_SLUG"]

    blog_page = Page(slug, "", configuration=configuration)
berumuron's avatar
berumuron committed
62
63
    blog_page.meta["PAGE_TEMPLATE"] = "blog"
    blog_page.meta["ARTICLES"] = articles
64
65
66
    blog_page.meta["ARTICLES_BY_YEAR"] = itertools.groupby(
        articles, key=lambda article: article.date().year
    )
67
    blog_page.meta["SERIES"] = series
berumuron's avatar
berumuron committed
68
69
70
    return blog_page


berumuron's avatar
berumuron committed
71
72
73
templates_cache = {}


74
75
76
77
78
79
80
81
82
def write_content(content, output_path):
    """Write content (Article or Page) under output_path.
    """
    output_filepath = os.path.join(output_path, f"{content.slug()}.html")
    template_filepath = os.path.join(
        os.curdir, "templates", f"{content.template()}.html"
    )

    # Initialize the template defined within content
berumuron's avatar
berumuron committed
83
84
    if template_filepath in templates_cache:
        template = templates_cache[template_filepath]
85
86
87
88
89
90
91
        try:
            rendered_content = template.render(content.meta)
        except Exception as e:
            print(
                f"An error occured when rendering {content.slug()} in {output_filepath}"
            )
            raise e
berumuron's avatar
berumuron committed
92
    elif os.path.exists(template_filepath):
93
        template = boopsy.Template(template_filepath)
berumuron's avatar
berumuron committed
94
        templates_cache[template_filepath] = template
95
96
97
98
99
100
101
        try:
            rendered_content = template.render(content.meta)
        except Exception as e:
            print(
                f"An error occured when rendering {content.slug()} in {output_filepath}"
            )
            raise e
102
103
104
105
106
107
108
109
    else:
        rendered_content = content.content()

    mkdirs_for_file(output_filepath)
    with open(output_filepath, "w") as output_file:
        output_file.write(rendered_content)


berumuron's avatar
berumuron committed
110
111
112
113
114
115
def write_feed(feed, output_path):
    """Write a feed as an Atom feed under output_path.
    """
    feed_filepath = os.path.join(output_path, f"{feed.slug()}.atom.xml")
    articles = feed.articles()

116
117
118
    # We want to sort the articles by publication date
    articles.sort(key=lambda article: article.date(), reverse=True)

119
120
121
122
123
    if articles:
        updated = articles[0].update() or articles[0].date()
    else:
        updated = datetime.datetime.now()

berumuron's avatar
berumuron committed
124
125
126
127
128
129
130
131
132
133
134
    feed_header = {
        "title": feed.title(),
        "self_link": feed.url(),
        "site_link": feed.site_link(),
        "id": f"urn:uuid:{feed.uuid()}",
        "updated": updated,
    }

    if feed.hub_link():
        feed_header["hub_link"] = feed.hub_link()

berumuron's avatar
berumuron committed
135
    atom = Atom()
berumuron's avatar
berumuron committed
136
    atom.add_header(feed_header)
berumuron's avatar
berumuron committed
137
138

    for article in articles:
berumuron's avatar
berumuron committed
139
140
141
        if article.is_private():
            continue

berumuron's avatar
berumuron committed
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
        entry = {
            "title": article.title(),
            "id": f"urn:uuid:{article.uuid()}",
            "article_link": article.url(),
            "published": article.date(),
            "content": article.content(),
        }

        article_author = article.author()
        if article_author:
            entry["author"] = article_author
        article_update = article.update()
        if article_update:
            entry["updated"] = article_update

        atom.add_entry(entry)

    mkdirs_for_file(feed_filepath)
    atom.write(feed_filepath)


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
def list_dirs(path):
    """List all the directories within a path.
    """
    for filename in os.listdir(path):
        full_path = os.path.join(path, filename)
        if os.path.isdir(full_path):
            yield filename


def match_extensions(filename, extensions):
    """Return True if filename extension is part of extensions array.
    """
    if len(extensions) > 0:
        _, ext = os.path.splitext(filename)
        if ext[1:] not in extensions:
            return False
    return True


def list_files(path, only=[]):
    """List all the files within a path.
    """
    for filename in os.listdir(path):
        full_path = os.path.join(path, filename)

        # We don't want to yield directories
        if not os.path.isfile(full_path):
            continue

        # If file extension is not part of "only" array, we don't yield it. If
        # only is empty, it means we accept all extensions.
        if not match_extensions(full_path, only):
            continue

        yield filename


def dir_tree(path, only=[]):
    """List all the tree directory under the given path.
    """
    for filename in os.listdir(path):
        full_path = os.path.join(path, filename)
        if os.path.isdir(full_path):
            for sub_filename in dir_tree(full_path, only=only):
                yield os.path.join(filename, sub_filename)
        elif match_extensions(filename, only):
            yield filename


def mkdirs_for_file(filepath):
    """Create parent directories for a given file.
    """
    path = os.path.dirname(filepath)
    os.makedirs(path, exist_ok=True)