Compare commits

...

7 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
945aa6e57b Ignore main.spec 2025-06-12 18:40:51 +02:00
241c44a380 Ajoute l'action install 2025-06-12 18:39:16 +02:00
7 changed files with 130 additions and 55 deletions

View File

@@ -1,6 +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'
outbox: './outbox'
url: 'https://blog.example.com'
theme: './theme/default'
title: 'Blog title'
url: 'https://blog.example.com'
outbox: './outbox'
presentation: 'Blog presentation.'

2
.gitignore vendored
View File

@@ -5,6 +5,7 @@ build/
dist/
wheels/
*.egg-info
main.spec
# Virtual environments
.venv
@@ -14,3 +15,4 @@ wheels/
inbox/
outbox/
themes/
draft/

View File

@@ -1,3 +1,4 @@
default: build
.PHONY: build install-tools install-dep init clean
init: install-dep install-tools
@@ -15,3 +16,10 @@ install-dep:
clean:
@rm -r ./dist ./build
install:
ifeq ($(USER), root)
@cp ./dist/main /usr/local/bin/blog
else
@cp ./dist/main ~/.local/bin/blog
endif

View File

@@ -1,10 +1,11 @@
import glob
import shutil
import datetime
import textwrap
from pathlib import Path
from blog.page import Page
from blog.config import Config
from jinja2 import Environment, FileSystemLoader, Template
from rss import RssFeed
class Blog:
@@ -13,18 +14,14 @@ class Blog:
self.conf = conf
self.pages = dict()
def load_pages(self):
"""Charge tous les fichiers .md dans le dossier inbox"""
files_list = glob.glob(f"{self.conf.inbox}/*.md")
def make(self, draft: bool = False):
"""Convertit les pages en un site html"""
self.pages = dict()
for file in files_list:
self.pages[Path(file).stem] = Page(Path(file))
self._load_pages(self.conf.inbox)
def make(self):
"""Convertit les pages en un site html"""
if not self.pages:
self.load_pages()
if draft:
self._load_pages(self.conf.draft)
env = Environment(loader=FileSystemLoader(self.conf.theme))
@@ -34,7 +31,13 @@ class Blog:
self._copy_css()
self._build_all_pages(page_template)
self._build_index(index_template)
self._build_rss()
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):
"""Convertit les pages markdown dans conf.inbox en html dans conf.outbox"""
@@ -44,7 +47,7 @@ class Blog:
outbox_path.mkdir()
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:
html_file.write(html_content)
@@ -56,20 +59,52 @@ class Blog:
with open(f"{self.conf.outbox}/index.html", "w+") as html_file:
html_file.write(html_content)
def _build_rss(self):
def _build_atom(self):
""" """
feed = RssFeed(language="fr-fr", title=self.conf.title, link=self.conf.url, description=self.conf.presentation )
for page in self.pages:
feed.add_item(
title = self.pages[page].meta["title"],
link = Path(self.conf.url) / 'pages' / (page + '.html'),
description = "Ma description a moi !",
pubdate = self.pages[page].meta["date"]
)
with open(Path(self.conf.outbox) / "flux.rss", 'w+') as rss_file:
feed.write(rss_file, 'utf-8')
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):
"""Copie les fichiers CSS du theme vers l'export"""
css_path = Path(self.conf.theme) / "css"
dest_path = Path(self.conf.outbox) / "css"

View File

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

View File

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

36
main.py
View File

@@ -2,6 +2,7 @@
import os
import argparse
import subprocess
from pathlib import Path
from blog.page import new_page
from blog.config import Config
@@ -18,35 +19,42 @@ def load_args():
parser = argparse.ArgumentParser(description="Gestion du blog")
parser.add_argument("action", choices=actions_list, help="L'action a réaliser.")
parser.add_argument(
"-c",
"--config",
default=".blog",
help="Chemin vers le fichier de configuration",
)
parser.add_argument(
"-i",
"--inbox",
default="./inbox",
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())
def load_make_args(args: str) -> dict:
"""Charge les paramêtres spécifiques à l'action make"""
parser = argparse.ArgumentParser(description="Compile le blog")
parser.add_argument(
"-t",
"--theme",
default="./themes/default",
help="Chemin vers le theme utilisé pour exporter le blog",
help="Chemin vers le theme utilisé",
)
parser.add_argument(
"-o",
"--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))
@@ -65,15 +73,21 @@ def main():
match args["action"]:
case "new":
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)
case "make":
args = load_make_args(args["all"])
conf.overload(args)
make_args = load_make_args(args["all"])
conf.overload(make_args)
blog = Blog(conf)
blog.make()
blog.make(draft=args["d"])
os._exit(0)