blog/scripts/article.py
2024-04-13 22:07:42 +03:00

197 lines
6.9 KiB
Python
Executable file

#!/usr/bin/env python3
# Script to manage articles.
#
# Current functionalities:
# - Update the header of an article or add a new header (command: update)
#
# License: MIT License
# Copyright: (c) 2024 Awiteb <a@4rs.nl>
import sys
import os
import datetime
def help(command: str | None) -> str:
if command is None:
help_message = """Script to manage articles.
Usage: ./scripts/article.py <command>
Commands:
- update: Update the header of an article or add a new header
- new: Create a new article
- card: Create a new social card for an article
- help: Folloing by a command, it will display the help of the command
"""
elif command == "update":
help_message = """Update the header of an article or add a new header
Usage: ./scripts/article.py update <article_name> <header_scope> <header_name> <header_value>
Example: ./scripts/article.py update "hello-world" global "title" "Hello World"
Header scopes: [global, taxonomies, extra]
"""
elif command == "new":
help_message = """Create a new article
Usage: ./scripts/article.py new <article_name>
Example: ./scripts/article.py new "hello-world"
"""
elif command == "card":
help_message = """Create a new social card for an article
Usage: ./scripts/article.py card <article_name>
Example: ./scripts/article.py card "hello-world"
"""
else:
help_message = f"The command `{command}` is not recognized. Available commands: [update, help]"
return help_message
def now() -> str:
"""Return the current date in the format "YYYY-MM-DD"""
return datetime.datetime.now().strftime("%Y-%m-%d")
class Article:
def __init__(self, name: str) -> None:
self.name = name
self.path = f"content/b/{name}/index.md"
self.headers = {
"global": {},
"taxonomies": {},
"extra": {},
}
self.content = ""
self.__reload_header()
self.__reload_content()
def __reload_header(self) -> None:
"""Reloads the content of the article
The header of the article is between the first and second "+++" in the file.
"""
if not os.path.exists(self.path):
return
with open(self.path, "r") as file:
current_header = "global"
for idx, line in enumerate(file):
if line == "+++\n" and idx > 0:
break
if (line == "+++\n" and idx == 0) or line.isspace():
continue
# Update the current header if the line is a header
if line.startswith("[") and line.endswith("]\n"):
current_header = line[1:-2]
continue
if len((splited_line := line.split("="))) == 2:
key, value = (splited_line[0], splited_line[1])
self.headers[current_header][key.strip()] = value.strip()
def __reload_content(self) -> None:
"""Reloads the content of the article"""
if not os.path.exists(self.path):
return
with open(self.path, "r") as file:
content = file.read()
# Remove the header from the file, and keep the rest as the content of the article
self.content = content[content.index("+++", 3) + 3 :].strip() + "\n"
def export(self) -> None:
"""Rewrite the file with the updated headers and content"""
if not os.path.exists(self.path):
os.makedirs(os.path.dirname(self.path), exist_ok=True)
with open(self.path, "w") as file:
file.write("+++\n")
for scope, headers in self.headers.items():
if scope != "global":
file.write(f"[{scope}]\n")
for key, value in headers.items():
file.write(f"{key} = {value}\n")
file.write("\n")
file.write("+++\n")
file.write(self.content)
def update_header(
article_name: str, header_scope: str, header_name: str, header_value: str
) -> None:
if header_scope not in ["global", "taxonomies", "extra"]:
print(
f"The header scope {header_scope} is not recognized. Available scopes: [global, taxonomies, extra]"
)
sys.exit(1)
article = Article(article_name)
if (
article.headers[header_scope].get(header_name) is not None
and article.headers[header_scope][header_name].startswith('"')
) or all([c.isalpha() or c.isspace() for c in header_value]):
header_value = f'"{header_value}"'
article.headers[header_scope][header_name] = header_value
article.export()
print("The header has been updated successfully.")
def create_new_article(article_name: str) -> None:
article = Article(article_name)
if os.path.exists(article.path):
print(f"The article {article_name} already exists.")
sys.exit(1)
article.headers["global"]["title"] = '""'
article.headers["global"]["description"] = '""'
article.headers["global"]["date"] = now()
article.headers["global"]["updated"] = now()
article.headers["global"]["draft"] = "true"
article.headers["taxonomies"]["tags"] = '["tag1", "tag2"]'
article.content = "Write your content here."
article.export()
print(f"The article {article_name} has been created successfully.")
print(f"Path: {article.path}")
def create_social_card(article_name: str) -> None:
article = Article(article_name)
if not os.path.exists(article.path):
print(f"The article {article_name} does not exist.")
sys.exit(1)
os.system(
f"./scripts/social-cards-zola -i b/{article_name}/index.md -b http://127.0.0.1:1111 -o static/img/social_cards/"
)
print(f"The social card for the article {article_name} has been created successfully.")
print(f"The url path is: /img/social_cards/b_{article_name.replace('-', '_')}.jpg")
def main() -> None:
# Check the current working directory, it should be the root of the project
if not (
"content" in os.listdir()
and "scripts" in os.listdir()
and "static" in os.listdir()
):
print("The script should be executed from the root of the project.")
sys.exit(1)
if len(sys.argv) < 2:
print(help(None))
sys.exit(1)
elif sys.argv[1] == "help" and len(sys.argv) == 3:
print(help(sys.argv[2]))
elif sys.argv[1] == "update" and len(sys.argv) == 6:
article_name, header_scope, header_name, header_value = sys.argv[2:]
update_header(article_name, header_scope, header_name, header_value)
elif sys.argv[1] == "new" and len(sys.argv) == 3:
create_new_article(sys.argv[2])
elif sys.argv[1] == "card" and len(sys.argv) == 3:
create_social_card(sys.argv[2])
else:
print(help(None))
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()