boop.py 6.31 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
import boop.boopfeed as boopfeed
import boop.environment as environment
import boop.configuration as config
import boop.utils as utils
15
16


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

19

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

    Examples:

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

    pass


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

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

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

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

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

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

69
70
71
    # Check that articles directory exists and load Articles from it
    articles_path = os.path.join(os.curdir, "articles")
    if os.path.isdir(articles_path):
72
73
74
        # 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.
75
        dirs_with_articles = list(utils.list_dirs(articles_path)) + ["."]
76
77
78
        for dirname in dirs_with_articles:
            dirpath = os.path.realpath(os.path.join(articles_path, dirname))

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

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

100
    # We want to sort the articles by publication date first
101
    articles.sort(key=lambda article: article.date(), reverse=True)
berumuron's avatar
berumuron committed
102
103
    for serie_articles in articles_by_series.values():
        serie_articles.sort(key=lambda article: article.date(), reverse=True)
104

berumuron's avatar
berumuron committed
105
106
107
108
109
110
111
112
113
114
115
    # Load a "blog" page which has access to the list of articles
    blog_template_filepath = os.path.join("templates", "blog.html")
    if os.path.exists(blog_template_filepath):
        blog_page = utils.build_blog_page(articles, configuration)
        pages.append(blog_page)

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

116
    for article in articles:
117
        utils.write_content(article, output_path)
118
        print(f"Written article: {article.url()}")
119

berumuron's avatar
berumuron committed
120
    # STEP 3: we write also the Atom feeds (the main one + the series').
121
122
123
124
125
126
127
128
    # 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
129
130
131
132
    feed_dirpath = os.path.join(output_path, "feeds")
    os.mkdir(feed_dirpath)

    # Build the main Atom feed
133
134
135
136
137
138
139
140
141
    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,
        }
142
    )
143
    utils.add_articles_to_feed(feed, articles)
144
145
146

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

berumuron's avatar
berumuron committed
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
    # 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,
            }
        )
165
        utils.add_articles_to_feed(feed, serie_articles)
berumuron's avatar
berumuron committed
166
167
168
169
170

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

171
172
173
174
175
    # 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")
176

177
178

if __name__ == "__main__":
179
    environment = environment.load_from_argv(sys.argv)
180
    try:
181
        main(environment)
182
183
184
        print("Boop!")
    except ProgramError as e:
        print(e.args[0], file=sys.stderr)