boop.py 6.66 KB
Newer Older
1
2
3
#!/bin/env python3

import os
4
import sys
5
import distutils.dir_util
6
import locale
7
import datetime
8

9
import pytz
10

11
12
13
14
15
import boop.boopsy as boopsy
import boop.boopfeed as boopfeed
import boop.environment as environment
import boop.configuration as config
import boop.utils as utils
16
17


18
locale.setlocale(locale.LC_ALL, "")
19

20

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class ProgramError(Exception):
    """Exception raised during the program execution.

    Examples:

    >>> try:
    ...     raise ProgramError("Oops")
    ... except ProgramError as e:
    ...     print(e.args[0])
    Oops
    """

    pass


36
37
38
def main(environment):
    # Make sure ./site folder is empty
    output_path = os.path.join(os.curdir, "site")
39
    utils.init_output_folder(output_path)
40
41
42

    # Load the configuration (it can be overidden by a configuration.yml file)
    configuration_path = os.path.join(os.curdir, "configuration.yml")
43
    configuration = config.load(configuration_path, environment, output_path)
44

45
46
47
    # STEP 1: we load all the Articles and Pages
    pages = []
    articles = []
berumuron's avatar
berumuron committed
48
    articles_by_series = {}
49
50

    # Check that index file exists
51
52
53
    index_filepath = os.path.join(os.curdir, "index.html")
    if not os.path.exists(index_filepath):
        raise ProgramError("./index.html file does not exist")
54

55
56
57
58
    index_page = utils.build_page_from_filepath(
        index_filepath, configuration, basename=os.curdir
    )
    pages.append(index_page)
59
60

    # Check that pages directory exists and load Pages from it
berumuron's avatar
berumuron committed
61
62
    pages_path = os.path.join(os.curdir, "pages")
    if os.path.isdir(pages_path):
63
        for filename in utils.dir_tree(pages_path, only=["html"]):
berumuron's avatar
berumuron committed
64
            page_filepath = os.path.join(pages_path, filename)
65
66
67
68
            page = utils.build_page_from_filepath(
                page_filepath, configuration, basename=pages_path
            )
            pages.append(page)
69

70
71
72
    # Check that articles directory exists and load Articles from it
    articles_path = os.path.join(os.curdir, "articles")
    if os.path.isdir(articles_path):
73
74
75
        # We iterate on the directories of articles dir. If they contain
        # "serie.html" file, it means they contain a serie. Otherwise, we list
        # articles as usual.
76
        dirs_with_articles = list(utils.list_dirs(articles_path)) + ["."]
77
78
79
        for dirname in dirs_with_articles:
            dirpath = os.path.realpath(os.path.join(articles_path, dirname))

80
81
            serie_filepath = os.path.join(dirpath, "serie.html")
            serie = None
82
            if dirname != "." and os.path.exists(serie_filepath):
83
84
85
                # We have a serie Page! Each article of the directory must be
                # considered part of the serie.
                slug = f"serie/{dirname}"
86
87
88
89
90
                serie = utils.build_page_from_filepath(
                    serie_filepath, configuration, slug=slug
                )
                pages.append(serie)
                articles_by_series[serie] = []
berumuron's avatar
berumuron committed
91

92
            for filename in utils.list_files(dirpath, only=["md"]):
93
                article_filepath = os.path.join(dirpath, filename)
94
                article = utils.build_article_from_filepath(
95
96
97
98
99
                    article_filepath, configuration, serie
                )
                articles.append(article)
                if serie:
                    articles_by_series[serie].append(article)
100

101
    # STEP 2: we write all the pages and articles
102
    for page in pages:
103
        utils.write_content(page, output_path)
104
        print(f"Written page: {page.url()}")
105

106
    # We want to sort the articles by publication date first
107
    articles.sort(key=lambda article: article.date(), reverse=True)
berumuron's avatar
berumuron committed
108
109
    for serie_articles in articles_by_series.values():
        serie_articles.sort(key=lambda article: article.date(), reverse=True)
110

111
    for article in articles:
112
        utils.write_content(article, output_path)
113
        print(f"Written article: {article.url()}")
114

115
116
117
118
119
120
121
122
123
124
    # Write the page with the list of articles if blog template exists
    blog_template_filepath = os.path.join("templates", "blog.html")
    if os.path.exists(blog_template_filepath):
        blog_template = boopsy.Template(blog_template_filepath)
        context = {}
        context.update(configuration)
        context["ARTICLES"] = articles
        blog_output_filepath = os.path.join(output_path, f"blog.html")
        with open(blog_output_filepath, "w") as output_file:
            output_file.write(blog_template.render(context))
125
        print(f"Written page: {configuration['SITE_URL']}blog.html")
126

berumuron's avatar
berumuron committed
127
    # STEP 3: we write also the Atom feeds (the main one + the series').
128
129
130
131
132
133
134
135
    # Just create some variables so it's easier to manipulate after
    site_url = configuration["SITE_URL"]
    site_uuid = configuration["SITE_UUID"]
    site_title = configuration["SITE_TITLE"]
    site_timezone = configuration["SITE_TIMEZONE"]
    timezone = pytz.timezone(site_timezone)
    current_datetime = datetime.datetime.now(tz=timezone)

berumuron's avatar
berumuron committed
136
137
138
139
    feed_dirpath = os.path.join(output_path, "feeds")
    os.mkdir(feed_dirpath)

    # Build the main Atom feed
140
141
142
143
144
145
146
147
148
    feed = boopfeed.Atom()
    feed.add_header(
        {
            "title": site_title,
            "self_link": f"{site_url}feeds/all.atom.xml",
            "site_link": site_url,
            "id": f"urn:uuid:{site_uuid}",
            "updated": current_datetime,
        }
149
    )
150
    utils.add_articles_to_feed(feed, articles)
151
152
153

    # And write it on the filesystem
    feed_filepath = os.path.join(feed_dirpath, "all.atom.xml")
154
    feed.write(feed_filepath)
155
156
    print(f"Written feed: {site_url}feeds/all.atom.xml")

berumuron's avatar
berumuron committed
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
    # Now, build the feeds for series (same process)
    for serie, serie_articles in articles_by_series.items():
        feed_filename = f"{os.path.basename(serie.slug())}.atom.xml"
        feed_filepath = os.path.join(feed_dirpath, feed_filename)

        feed = boopfeed.Atom()
        feed.add_header(
            {
                "title": f"{serie.title()} - {site_title}",
                "self_link": f"{site_url}feeds/{feed_filename}",
                "site_link": serie.url(),
                "id": f"urn:uuid:{serie.uuid()}",
                "updated": current_datetime,
            }
        )
172
        utils.add_articles_to_feed(feed, serie_articles)
berumuron's avatar
berumuron committed
173
174
175
176
177

        # And write it on the filesystem
        feed.write(feed_filepath)
        print(f"Written feed: {site_url}feeds/{feed_filename}")

178
179
180
181
182
    # STEP 4: and we finish by copying the static folder.
    static_dirpath = os.path.join(os.curdir, "static")
    if os.path.isdir(static_dirpath):
        distutils.dir_util.copy_tree(static_dirpath, output_path)
        print(f"Static files copied")
183

184
185

if __name__ == "__main__":
186
    environment = environment.load_from_argv(sys.argv)
187
    try:
188
        main(environment)
189
190
191
        print("Boop!")
    except ProgramError as e:
        print(e.args[0], file=sys.stderr)