Compare commits

...

6 Commits

Author SHA1 Message Date
4c49d4e96a Ajoute le flux atom 2025-07-17 18:12:51 +02:00
c1b730d396 Merge branch 'main' into rss 2025-07-13 19:49:03 +02:00
4321429dbf Update makefile 2025-06-24 20:06:44 +02:00
8c4cb8dad2 Ajoute la gestion des brouillons 2025-06-24 19:58:56 +02:00
Florestan Bredow
ee19286ee8 WIP ajoute generation flux rss 2025-06-16 16:57:50 +02:00
Florestan Bredow
1e30909e52 WIP ajoute generation flux rss 2025-06-06 08:41:37 +02:00
7 changed files with 124 additions and 42 deletions

View File

@@ -1,5 +1,11 @@
theme: './theme/default' author_name: 'Firstname Name'
author_mail: 'name@example.com'
draft: './draft'
# python -m uuid -u uuid4
id: 'a06cd9a6-3a48-479b-bf7f-40ddcdde7982'
inbox: './inbox' inbox: './inbox'
outbox: './outbox' theme: './theme/default'
title: 'Blog title' title: 'Blog title'
url: 'https://blog.example.com'
outbox: './outbox'
presentation: 'Blog presentation.' presentation: 'Blog presentation.'

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@ main.spec
inbox/ inbox/
outbox/ outbox/
themes/ themes/
draft/

View File

@@ -1,3 +1,4 @@
default: build
.PHONY: build install-tools install-dep init clean .PHONY: build install-tools install-dep init clean
init: install-dep install-tools init: install-dep install-tools

View File

@@ -1,11 +1,12 @@
import glob import glob
import shutil import shutil
import datetime
import textwrap
from pathlib import Path from pathlib import Path
from blog.page import Page from blog.page import Page
from blog.config import Config from blog.config import Config
from jinja2 import Environment, FileSystemLoader, Template from jinja2 import Environment, FileSystemLoader, Template
class Blog: class Blog:
def __init__(self, conf: Config): def __init__(self, conf: Config):
@@ -13,18 +14,14 @@ class Blog:
self.conf = conf self.conf = conf
self.pages = dict() self.pages = dict()
def load_pages(self): def make(self, draft: bool = False):
"""Charge tous les fichiers .md dans le dossier inbox""" """Convertit les pages en un site html"""
files_list = glob.glob(f"{self.conf.inbox}/*.md")
self.pages = dict() self.pages = dict()
for file in files_list: self._load_pages(self.conf.inbox)
self.pages[Path(file).stem] = Page(Path(file))
def make(self): if draft:
"""Convertit les pages en un site html""" self._load_pages(self.conf.draft)
if not self.pages:
self.load_pages()
env = Environment(loader=FileSystemLoader(self.conf.theme)) env = Environment(loader=FileSystemLoader(self.conf.theme))
@@ -34,6 +31,13 @@ class Blog:
self._copy_css() self._copy_css()
self._build_all_pages(page_template) self._build_all_pages(page_template)
self._build_index(index_template) self._build_index(index_template)
self._build_atom()
def _load_pages(self, path: Path):
"""Charge tous les fichiers .md dans le dossier inbox"""
files_list = glob.glob(f"{path}/*.md")
for file in files_list:
self.pages[Path(file).stem] = Page(Path(file))
def _build_all_pages(self, template: Template): def _build_all_pages(self, template: Template):
"""Convertit les pages markdown dans conf.inbox en html dans conf.outbox""" """Convertit les pages markdown dans conf.inbox en html dans conf.outbox"""
@@ -43,7 +47,7 @@ class Blog:
outbox_path.mkdir() outbox_path.mkdir()
for filename in self.pages: for filename in self.pages:
html_content = self.pages[filename].html(template) html_content = self.pages[filename].html_template(template)
with open(f"{self.conf.outbox}/pages/{filename}.html", "w+") as html_file: with open(f"{self.conf.outbox}/pages/{filename}.html", "w+") as html_file:
html_file.write(html_content) html_file.write(html_content)
@@ -55,7 +59,52 @@ class Blog:
with open(f"{self.conf.outbox}/index.html", "w+") as html_file: with open(f"{self.conf.outbox}/index.html", "w+") as html_file:
html_file.write(html_content) html_file.write(html_content)
def _build_atom(self):
""" """
updated = datetime.date(1970, 1, 1)
articles = ""
for filename in self.pages:
date = self.pages[filename].date
if updated < date:
updated = date
articles += textwrap.indent(
textwrap.dedent(f"""\
<entry>
<title>{self.pages[filename].title}</title>
<link href="{self.conf.url}/pages/{filename}.html"/>
<updated>{date}T00:00:00Z</updated>
<id>urn:uuid:{self.pages[filename].id}</id>
<content type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml">
{self.pages[filename].html()}
</div>
</content>
</entry>
"""),
" ")
header = textwrap.dedent(f""" <?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{self.conf.title}</title>
<subtitle>{self.conf.presentation}</subtitle>
<link href="{self.conf.url}/atom.xml" rel="self"/>
<link href="{self.conf.url}/index.html"/>
<updated>{updated}T00:00:00Z</updated>
<author>
<name>{self.conf.author_name}</name>
<email>{self.conf.author_mail}</email>
</author>
<id>urn:uuid:{self.conf.id}</id>
""")
footer = "</feed>"
with open(Path(self.conf.outbox) / "atom.xml", 'w+') as rss_file:
rss_file.write(header + articles + footer)
def _copy_css(self): def _copy_css(self):
"""Copie les fichiers CSS du theme vers l'export"""
css_path = Path(self.conf.theme) / "css" css_path = Path(self.conf.theme) / "css"
dest_path = Path(self.conf.outbox) / "css" dest_path = Path(self.conf.outbox) / "css"

View File

@@ -5,7 +5,18 @@ from pathlib import Path
class Config: class Config:
_conf = dict() _conf = dict()
_list_valid_parameters = {"inbox", "outbox", "theme", "title", "presentation"} _list_valid_parameters = {
"inbox",
"outbox",
"theme",
"title",
"presentation",
"url",
"draft",
"author_name",
"author_mail",
"id"
}
def __init__(self, config_file: Path): def __init__(self, config_file: Path):
"""Constructeur : charge les paramètres depuis le fichier de configuration""" """Constructeur : charge les paramètres depuis le fichier de configuration"""

View File

@@ -1,33 +1,31 @@
import re import re
import os import os
import yaml import yaml
import uuid
import jinja2 import jinja2
import textwrap import textwrap
import markdown import markdown
import subprocess
from pathlib import Path from pathlib import Path
from datetime import date from datetime import date
# from bs4 import BeautifulSoup as bs
def new_page(title: str, path: Path) -> Path:
def new_page(title: str):
"""Crée un nouvel article à partir du titre de celui ci.""" """Crée un nouvel article à partir du titre de celui ci."""
today = date.today().strftime("%Y-%m-%d") today = date.today().strftime("%Y-%m-%d")
filename = "./inbox/" + re.sub("[^a-zA-Z0-9-]", "_", title) + ".md" filename = path + '/' + re.sub("[^a-zA-Z0-9-]", "_", title) + ".md"
id = uuid.uuid4()
content = textwrap.dedent( content = textwrap.dedent(
f"""\ f"""\
--- ---
date: {today} date: {today}
title: {title} title: {title}
id: {id}
category: category:
tags: tags:
- -
--- ---
# {title}
""" """
) )
@@ -36,7 +34,7 @@ def new_page(title: str):
with open(filename, "w") as file: with open(filename, "w") as file:
file.write(content) file.write(content)
subprocess.run(["nvim", "+normal G$", "+startinsert", filename]) return filename
class Page: class Page:
@@ -59,13 +57,10 @@ class Page:
if "date" not in self.meta or type(self.meta["date"]) is not date: if "date" not in self.meta or type(self.meta["date"]) is not date:
self.meta["date"] = date(1970, 1, 1) self.meta["date"] = date(1970, 1, 1)
def __getattr__(self, name): def __getattr__(self, name: str):
if name not in self.meta: if name not in self.meta:
raise AttributeError(f"L'attribut '{name}' n'existe pas.") raise AttributeError(f"L'attribut '{name}' n'existe pas.")
# if name == date:
# return datetime(self.meta['date'], "%Y-%m-%d")
return self.meta[name] return self.meta[name]
def _extract_meta_and_markdown(self, content: str) -> list: def _extract_meta_and_markdown(self, content: str) -> list:
@@ -87,14 +82,19 @@ class Page:
meta += line + "\n" meta += line + "\n"
return meta, markdown return meta, markdown
def html(self, template: jinja2.Template) -> str: def html(self) -> str:
"""Convertit le markdown en html"""
return markdown.markdown(
self.md_content, extensions=["codehilite", "fenced_code"]
)
def html_template(self, template: jinja2.Template) -> str:
"""Convertit la page en html a partir d'un template jinja2""" """Convertit la page en html a partir d'un template jinja2"""
title = self.meta["title"] or "Sans titre" title = self.meta["title"] or "Sans titre"
date = self.meta["date"] or None date = self.meta["date"] or None
return template.render( return template.render(
title=title, title=title,
date=date, date=date,
content=markdown.markdown( content=self.html(),
self.md_content, extensions=["codehilite", "fenced_code"]
),
) )

36
main.py
View File

@@ -2,6 +2,7 @@
import os import os
import argparse import argparse
import subprocess
from pathlib import Path from pathlib import Path
from blog.page import new_page from blog.page import new_page
from blog.config import Config from blog.config import Config
@@ -18,35 +19,42 @@ def load_args():
parser = argparse.ArgumentParser(description="Gestion du blog") parser = argparse.ArgumentParser(description="Gestion du blog")
parser.add_argument("action", choices=actions_list, help="L'action a réaliser.") parser.add_argument("action", choices=actions_list, help="L'action a réaliser.")
parser.add_argument( parser.add_argument(
"-c",
"--config", "--config",
default=".blog", default=".blog",
help="Chemin vers le fichier de configuration", help="Chemin vers le fichier de configuration",
) )
parser.add_argument( parser.add_argument(
"-i",
"--inbox", "--inbox",
default="./inbox", default="./inbox",
help="Chemin vers les fichiers markdown du blog", help="Chemin vers les fichiers markdown du blog",
) )
parser.add_argument("all", nargs=argparse.REMAINDER) parser.add_argument(
"-d",
action="store_true",
help="Construit le blog avec les brouillons si spécifié.",
)
parser.add_argument(
"--draft",
default="./draft",
help="Chemin vers les brouillons",
)
parser.add_argument("all", nargs=argparse.REMAINDER, help=argparse.SUPPRESS)
return vars(parser.parse_args()) return vars(parser.parse_args())
def load_make_args(args: str) -> dict: def load_make_args(args: str) -> dict:
"""Charge les paramêtres spécifiques à l'action make""" """Charge les paramêtres spécifiques à l'action make"""
parser = argparse.ArgumentParser(description="Compile le blog") parser = argparse.ArgumentParser(description="Compile le blog")
parser.add_argument( parser.add_argument(
"-t",
"--theme", "--theme",
default="./themes/default", default="./themes/default",
help="Chemin vers le theme utilisé pour exporter le blog", help="Chemin vers le theme utilisé",
) )
parser.add_argument( parser.add_argument(
"-o",
"--output", "--output",
default="./output", default="./output",
help="Nom du dossier ou sera exporté le blog en html", help="Nom du dossier où sera exporté le blog en html",
) )
return vars(parser.parse_args(args)) return vars(parser.parse_args(args))
@@ -65,15 +73,21 @@ def main():
match args["action"]: match args["action"]:
case "new": case "new":
page_title = " ".join(args["all"]) page_title = " ".join(args["all"])
new_page(page_title)
path = conf.inbox
if args["d"]:
path = conf.draft
subprocess.run(
["nvim", "+normal G$", "+startinsert", new_page(page_title, path)]
)
os._exit(0) os._exit(0)
case "make": case "make":
args = load_make_args(args["all"]) make_args = load_make_args(args["all"])
conf.overload(args) conf.overload(make_args)
blog = Blog(conf) blog = Blog(conf)
blog.make() blog.make(draft=args["d"])
os._exit(0) os._exit(0)