initial commit

This commit is contained in:
ClariSys 2025-03-11 21:03:39 -07:00
parent 46328a92a8
commit c449f9d9ac
12 changed files with 457 additions and 4 deletions

1
.gitignore vendored
View file

@ -162,3 +162,4 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
output/

View file

@ -220,7 +220,7 @@ If you develop a new program, and you want it to be of the greatest possible use
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
python-webgen python-webgen
Copyright (C) 2025 ClariSys Copyright (C) 2025 ChloeCat
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

View file

@ -1,17 +1,17 @@
# python-webgen # python-webgen
a set of python scripts to generate a blog-style website from a set of markdown pages. a set of python scripts and html templates to generate blog-style websites
--- ---
## Getting Started ## Getting Started
Welcome to your new KitsuDev Repo, ClariSys! Feel free to make this place your own! Welcome to your new KitsuDev Repo, ChloeCat! Feel free to make this place your own!
## Make your first push! ## Make your first push!
Once you're ready to push your files, use these commands to get to your spot! Once you're ready to push your files, use these commands to get to your spot!
```bash ```bash
git remote add origin https://kitsunes.dev/ClariSys/python-webgen.git git remote add origin https://kitsunes.dev/ChloeCat/python-webgen.git
git branch -M main git branch -M main
git push -uf origin main git push -uf origin main
``` ```

23
generate.py Normal file
View file

@ -0,0 +1,23 @@
import argparse
from datetime import datetime
# Argument Parser
argparser = argparser = argparse.ArgumentParser(description = "Generate a .md file with a pre-filled title, date, etc.")
argparser.add_argument("output", help = "The location to generate the template to")
argparser.add_argument("--title", help = "The title of the article (default: Blog Post)", default = "Blog Post")
argparser.add_argument("--author", help = "The name of the author (default: Blog Author)", default = "Blog Author")
argparser.add_argument("-v", help = "more output", action = "store_true", dest = "verbose")
args = argparser.parse_args()
date = datetime.now().astimezone().strftime('%Y-%m-%dT%H:%M:%S%z')
template = "+++\ntitle = '{}'\ndate = {}\nauthor = '{}'\ndraft = True\n+++"
output = template.format(args.title, date, args.author)
if args.verbose: print(template)
# time to write the file :3
with open(args.output, "w+") as file:
if args.verbose: print("Writing to {}...".format(args.output))
file.write(output)
print("Finished writing file to {}".format(args.output))
print("Done.")

89
parser.py Normal file
View file

@ -0,0 +1,89 @@
# import modules
import markdown as md
# function definitions
# convertFile(): takes in a path to a markdown file and
# outputs an html snippet.
def convertFile(infilePath):
rawfile = open(infilePath)
output = ""
isComment = False
for line in rawfile:
if "+++" in line:
if not isComment:
isComment = True
continue
else:
isComment = False
continue
if isComment:
continue
else:
output = output + line
output = md.markdown(output)
return output
# getMetadata(): returns the metadata as a dictionary
def getMetadata(infilePath):
rawfile = open(infilePath)
isComment = False
metadata = {}
for line in rawfile:
if "+++" in line:
if not isComment:
isComment = True
continue
else:
isComment=False
break
if isComment:
key = line.split(" = ")[0]
value = line.split(' = ')[1][:-1]
metadata[key] = value
if metadata[key] == "true":
metadata[key] = True
if metadata[key] == "false":
metadata[key] = False
if isinstance(metadata[key], str):
metadata[key] = metadata[key][1:-1]
return metadata
# getTitle(): returns the title from the metadata
def getTitle(infilePath):
return getMetadata(infilePath)['title']
def convertDate(timestamp):
dateStr = ""
newTime = timestamp.split('T')[0]
date = newTime.split("-")
day = date[2].lstrip("0")
year = "20" + date[0][-2:]
match date[1]:
case "01":
month = "January"
case "02":
month = "February"
case "03":
month = "March"
case "04":
month = "April"
case "05":
month = "May"
case "06":
month = "June"
case "07":
month = "July"
case "08":
month = "August"
case "09":
month = "September"
case "10":
month = "October"
case "11":
month = "November"
case "12":
month = "December"
case _:
month = ""
return "{} {}, {}".format(day, month, year)

35
stitcher.py Normal file
View file

@ -0,0 +1,35 @@
import parser
# function defs
def generateBody(infilePath):
title = parser.getTitle(infilePath)
body = parser.convertFile(infilePath)
timestamp = parser.getMetadata(infilePath)["date"]
date = parser.convertDate(timestamp)
final = "<h1> {} </h1>\n<h3 class=\"date\">{}</h3>\n<hr/>\n{}".format(title, date, body)
return final
def createArticle(infilePath, templatePath, mainTitle = ""):
if mainTitle != "":
mainTitle = " | " + mainTitle
template = open("{}article.html".format(templatePath), 'r').read()
navbar = open("{}navbar.html".format(templatePath), 'r').read()
foot = open("{}footer.html".format(templatePath), 'r').read()
body = generateBody(infilePath)
articleTitle = parser.getTitle(infilePath) + mainTitle
article = template.format(title = articleTitle, main = body, nav = navbar, footer = foot)
return article
def createMainPage(titles, templatePath, main_title):
template = open("{}main.html".format(templatePath), 'r').read()
navbar = open("{}navbar.html".format(templatePath), 'r').read()
foot = open("{}footer.html".format(templatePath), 'r').read()
articles = ""
fstring = '<article><h2><a href = "{}">{}</a></h2></article>\n'
for title in titles:
articles = articles + fstring.format(title[1], title[0])
final = template.format(title = main_title, toc = articles, nav = navbar, footer = foot)
return final

22
templates/article.html Normal file
View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title> {title} </title>
<link rel="stylesheet" href="/stylesheet.css">
</head>
<body>
<header>
<nav>
{nav}
</nav>
</header>
<main>
{main}
</main>
<footer>
{footer}
</footer>
</body>
</html>

13
templates/footer.html Normal file
View file

@ -0,0 +1,13 @@
Contact me:<br/>
Sharkey <a href = "https://catwithaclari.net/@Chloe">@Chloe@catwithaclari.net</a>
Email <a href = "mailto:chloe@catwitaclari.net">chloe@catwithaclari.net</a><br/>
<a href="https://blog.catwithaclari.net/"><img src="/button.png" alt="A pink-purple-blue gradient with the text ChloeCat written in a fancy script font"/></a>
<a id="w3chtml" href="https://validator.w3.org/nu/?doc=https%3A%2F%2Fblog.catwithaclari.net%2F"><img src="https://www.w3.org/Icons/valid-html401.png" alt="WC3 HTML 4.01 Validated"/></a>
<a id="w3ccss" href="https://jigsaw.w3.org/css-validator/validator?uri=https%253A%252F%252Fblog.catwithaclari.net"><img src="https://www.w3.org/Icons/valid-css.png" alt="W3C CSS Validated"/></a>
<script>
href = document.location;
w3chtml = document.getElementById("w3chtml");
w3ccss = document.getElementById("w3ccss");
w3chtml.href = "https://validator.w3.org/nu/?doc=" + href;
w3ccss.href = "https://jigsaw.w3.org/css-validator/validator?uri=" + href;
</script>

23
templates/main.html Normal file
View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title> {title} </title>
<link rel="stylesheet" href="/stylesheet.css">
</head>
<body>
<header>
<nav>
{nav}
</nav>
</header>
<main>
<h1>Welcome to {title}!</h1>
{toc}
</main>
<footer>
{footer}
</footer>
</body>
</html>

19
templates/navbar.html Normal file
View file

@ -0,0 +1,19 @@
<div class = "logo"> <a href = "https://blog.catwithaclari.net">ChloeCat</a> </div><div class = sep> | </div>
<div class="theme-switch">
<button id="theme-toggle" accesskey="t" title="(Alt + T)">
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</button>
</div>

134
templates/stylesheet.css Executable file
View file

@ -0,0 +1,134 @@
@import url('https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Comfortaa:wght@300..700&display=swap');
:root {
--bg-color: rgb(39, 08, 46);
--fg-color: rgb(255, 186, 255);
}
#sun {
display: none;
}
@media (prefers-color-scheme: light) {
:root {
--fg-color: rgb(39, 08, 46);
--bg-color: rgb(255, 186, 255);
}
#moon {
display: none;
}
#sun {
display: initial;
}
}
.theme-switch button {
background: none;
border: none;
vertical-align: center;
}
svg {
stroke: var(--fg-color);
}
a {
color: var(--fg-color);
}
a:visited {
color: lch(from var(--fg-color) calc(l - 20) c h);
}
body {
background-color: var(--bg-color);
color: var(--fg-color);
}
nav {
display: flex;
font-size: 150%;
align-items: center;
margin-left: 25px;
}
.logo {
color: var(--fg-color);
border: solid lch(from var(--bg-color) calc(l + 15) c h);
border-radius: 10px;
background-color: lch(from var(--bg-color) calc(l + 10) c h);
font-family: 'Caveat', sans-serif;
}
.logo a {
text-decoration: none;
margin: 15px;
}
.logo a:visited{
color: var(--fg-color);
}
.sep {
font-size: 175%;
font-family: 'Fira Code', 'Noto Sans Mono', monospace;
margin-left: auto;
}
.theme-switch {
margin-right: 25px
}
article{
border: solid lch(from var(--bg-color) calc(l + 8) c h);
border-radius: 15px;
margin: 10px;
background-color: lch(from var(--bg-color) calc(l + 5) c h);
}
article h2{
margin-left: 15px;
margin-right: 15px;
}
h1 {
font-size: 300%;
}
hr {
max-width: 720px;
margin-left: 0px;
margin-right: 20%;
color: var(--fg-color)
}
h3 .date {
font-size: 80%
}
main {
display: grid;
align-items: center;
justify-content: center;
flex-direction: column;
font-family: 'Comfortaa', 'Noto Sans', sans-serif;
}
code {
font-family: "Fira Code", monospace;
border: solid lch(from var(--bg-color) calc(l + 15) c h);
border-radius: 5px;
background-color: lch(from var(--bg-color) calc(l + 5) c h);
}
main * {
max-width: 720px;
justify-content: left;
}
footer {
justify-self: center;
text-align: center;
font-size: 75%;
margin-top: 25px;
}

94
webgen.py Normal file
View file

@ -0,0 +1,94 @@
# imports
import parser
import stitcher
import argparse
import os
from bs4 import BeautifulSoup as bs
# argument parser
argparser = argparse.ArgumentParser(description = "A simple set of python scripts to generate HTML files from a set of markdown files.")
argparser.add_argument("input_path", help = "the location of the folder to parse")
argparser.add_argument("output_path", help = "the location to output the finalised files")
argparser.add_argument("--template_path", help = "the location of the template files (default: ./templates/)", default = "./templates/")
argparser.add_argument("--css_file", help = "the location of a css file to copy to the output directory (default: ./templates/stylesheet.css)", default = "./templates/stylesheet.css")
argparser.add_argument("--site_title", help = "the title shown on the main page (default: \"Blog\")", default="Blog")
argparser.add_argument("--title_all", help = "add the main title to all articles", action="store_true", dest="allTitles")
argparser.add_argument("-v", help = "more output", action = "store_true", dest = "verbose")
args = argparser.parse_args()
# global vars
articles = []
root = args.input_path
htmls = {}
article_timestamps = {}
titles = []
for rootDir, dirs, files in os.walk(args.input_path):
for article in files:
articles.append(article)
print("\nDiscovering articles...\n")
for article in articles:
article = root + article
fstring = "Found {draft}{title} at {rootPath}{path}"
isDraft = "[Draft] " if parser.getMetadata(article)['draft'] else ""
print(fstring.format(title = parser.getTitle(article), rootPath = root, path = article, draft = isDraft))
print("\nGenerating HTML files from template...")
for article in articles:
article = root + article
if parser.getMetadata(article)['draft'] == True:
if args.verbose: print('"{}" is a draft, skipping...'.format(parser.getTitle(article)))
continue
if args.allTitles:
htmls[article] = stitcher.createArticle(article, args.template_path, args.site_title)
else:
htmls[article] = stitcher.createArticle(article, args.template_path)
if args.verbose:
print(htmls[article], end='\n')
print("\nWriting files to {}...\n".format(args.output_path))
for article in articles:
workingDir = args.output_path + article[:-3] + "/"
article = root + article
if parser.getMetadata(article)['draft'] == True:
if args.verbose: print('{} is a draft, skipping...'.format(article))
continue
html = bs(htmls[article]).prettify()
if not os.path.exists(workingDir):
os.makedirs(workingDir)
if args.verbose: print("creating directory {}".format(workingDir))
open(workingDir + "index.html", 'w').write(html)
if args.verbose:
print("wrote {} to {}".format(article, workingDir))
print("Files written")
print("\nSorting non-draft articles by timestamp...\n")
for article in articles:
article = root + article
if parser.getMetadata(article)['draft'] == True:
if args.verbose: print('{} is a draft, skipping...'.format(parser.getTitle(article)))
continue
article_timestamps[article] = parser.getMetadata(article)['date']
sorted_articles = sorted(article_timestamps.items(), key = lambda item: item[1], reverse = True)
if args.verbose: print(sorted_articles)
print("\nGenerating main page...")
for article in sorted_articles:
article = article[0]
title = parser.getTitle(article)
link = article.split("/")[-1][:-3]
titles.append([title, link])
if args.verbose: print("added {} to titles list with link {}".format(title, link))
mainPage = bs(stitcher.createMainPage(titles, args.template_path, args.site_title)).prettify()
print("Generated main page")
print("Writing main page to {}".format(args.output_path))
open(args.output_path + "index.html", 'w').write(mainPage)
print("Succesfully wrote main page to {}index.html".format(args.output_path))
print("\nCopying CSS file...")
if args.verbose: print(args.css_file, "-->", args.output_path + "stylesheet.css")
css = open(args.css_file, 'r').read()
open(args.output_path + "stylesheet.css", 'w').write(css)
print("Copied CSS file")
print("\nAll done!")