#!/usr/bin/env python3 # Script to manage articles. # # License: MIT License # Copyright: (c) 2024 Awiteb 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 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 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 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 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() os.system("shot-scraper --silent 'http://127.0.0.1:1111/' -w 700 -h 400 --retina --quality 60 -o static/img/social_cards/index.jpg") 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"] = '"Title"' article.headers["global"]["description"] = '"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()