diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..709fb97
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,8 @@
+[tool.black]
+line-length = 88
+target-version = ['py312']
+
+[tool.flake8]
+max-line-length = 88
+extend-ignore = "E203"
+exclude = ".venv"
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..d9dafae
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,15 @@
+# AGENTS.md
+
+## Core Workflow
+
+- **Setup**: `make prep` (creates `.venv` and installs `requirements.txt`)
+- **Build**: `make build` (runs `atom_gen.py`, `prep.py`, and `build_pages.py`)
+- **Events**: Update `dogadjaji.csv` then run `make events` (updates pages and generates images via `image_poster.py`)
+- **Development**: `make dev` (starts Nginx using `nginx.dev.conf`) and `make stop` to terminate.
+
+## Technical Details
+
+- **Environment**: Uses a local virtual environment in `.venv`. Always use `./.venv/bin/python` or ensure the venv is activated.
+- **Output**: The generated website is stored in the `site/` directory.
+- **Data**: Events are driven by `dogadjaji.csv`.
+- **Web Server**: Uses Nginx for the development server.
diff --git a/Makefile b/Makefile
index 5197b65..f390e42 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: build events dev stop help prep
+.PHONY: build events dev stop help prep lint format
help:
@echo "Available commands:"
@@ -29,3 +29,9 @@ dev:
stop:
nginx -p . -s stop
+lint:
+ ./.venv/bin/flake8 . --config .flake8 --exclude .venv
+
+format:
+ ./.venv/bin/black .
+
diff --git a/atom_gen.py b/atom_gen.py
index 52b7485..40f3e84 100755
--- a/atom_gen.py
+++ b/atom_gen.py
@@ -11,7 +11,7 @@ import os
blogs_dir = os.fsencode("blog")
-#def blogposts_list_gen():
+# def blogposts_list_gen():
# output_list = []
# for file in os.listdir(blogs_dir):
# filename = os.fsdecode(file)
@@ -26,46 +26,48 @@ blogs_dir = os.fsencode("blog")
# output_list.append([author, title, time, content_html, full_path])
# return output_list
+
def events_list_gen():
output_list = []
events_file = open("dogadjaji.csv", "r")
for line in events_file.readlines():
- date, time, location, title = line.split(", ")
- author = "Decentrala"
- content_html = f"Event is taking place at {location} on {date} at {time}. For more information see the forum at https://forum.dmz.rs"
+ date, time, location, title = line.split(", ")
+ author = "Decentrala"
+ content_html = f"Event is taking place at {location} on {date} at {time}. For more information see the forum at https://forum.dmz.rs"
- output_list.append([author, title, content_html])
+ output_list.append([author, title, content_html])
events_file.close()
return output_list
+
def feedgen(blogs, events):
fg_blog = FeedGenerator()
- fg_blog.id('http://dmz.rs/')
- fg_blog.title('Decentrala Blog')
- fg_blog.author( {'name':'Decentrala','email':'dmz@dmz.rs'} )
- fg_blog.link( href='https://dmz.rs/atom_blog.xml', rel='self' )
+ fg_blog.id("http://dmz.rs/")
+ fg_blog.title("Decentrala Blog")
+ fg_blog.author({"name": "Decentrala", "email": "dmz@dmz.rs"})
+ fg_blog.link(href="https://dmz.rs/atom_blog.xml", rel="self")
fg_events = FeedGenerator()
- fg_events.id('http://dmz.rs/')
- fg_events.title('Decentrala Blog')
- fg_events.author( {'name':'Decentrala','email':'dmz@dmz.rs'} )
- fg_events.link( href='https://dmz.rs/atom_events.xml', rel='self' )
+ fg_events.id("http://dmz.rs/")
+ fg_events.title("Decentrala Blog")
+ fg_events.author({"name": "Decentrala", "email": "dmz@dmz.rs"})
+ fg_events.link(href="https://dmz.rs/atom_events.xml", rel="self")
for post in blogs:
fe_blogs = fg_blog.add_entry()
fe_blogs.id("https://dmz.rs/" + post[4][:-3] + ".html")
- fe_blogs.author({'name': post[0]})
+ fe_blogs.author({"name": post[0]})
fe_blogs.title(post[1])
fe_blogs.updated(post[2])
- fe_blogs.content(content=post[3], type='html')
+ fe_blogs.content(content=post[3], type="html")
for event in events:
fe_events = fg_events.add_entry()
fe_events.id("https://dmz.rs/pages/events.html")
- fe_events.author({'name': event[0]})
+ fe_events.author({"name": event[0]})
fe_events.title(event[1])
fe_events.updated(datetime.datetime.now(datetime.timezone.utc))
- fe_events.content(content=event[2], type='html')
+ fe_events.content(content=event[2], type="html")
- fg_blog.atom_file('site/atom_blog.xml')
- fg_events.atom_file('site/atom_events.xml')
+ fg_blog.atom_file("site/atom_blog.xml")
+ fg_events.atom_file("site/atom_events.xml")
diff --git a/blog.py b/blog.py
index 4bfb304..545777f 100755
--- a/blog.py
+++ b/blog.py
@@ -5,8 +5,8 @@ from markdown import markdown as to_markdown
blog = ""
-with open('blogs/Lorem Ipsum.md','rt') as file:
+with open("blogs/Lorem Ipsum.md", "rt") as file:
blog = file.read()
-with open('blogs/Lorem Ipsum.html', 'wt') as file:
+with open("blogs/Lorem Ipsum.html", "wt") as file:
file.write(to_markdown(blog))
diff --git a/build_pages.py b/build_pages.py
index 87b1eb7..e9b827d 100755
--- a/build_pages.py
+++ b/build_pages.py
@@ -2,59 +2,87 @@ from jinja2 import Environment, FileSystemLoader
import os
PAGES = [
- {'name': 'index', 'titleSR': 'Početna', 'titleEN': 'Home', 'style': 'home'},
- {'name': 'account', 'titleSR': 'Nalog', 'titleEN': 'Account', 'style': 'account'},
- {'name': 'about', 'titleSR': 'O nama', 'titleEN': 'About us', 'style': 'about'},
- {'name': 'statute', 'titleSR': 'Statut', 'titleEN': 'Statute', 'style': 'statute'},
- {'name': 'events', 'titleSR': 'Događaji', 'titleEN': 'Events', 'style': 'events'},
- {'name': 'events_archive', 'titleSR': 'Arhiva događaja', 'titleEN': 'Events archive', 'style': 'events'},
- {'name': 'services', 'titleSR': 'Servisi', 'titleEN': 'Services', 'style': 'services'},
- {'name': 'webring', 'titleSR': 'Webring', 'titleEN': 'Webring', 'style': ''},
- {'name': 'support', 'titleSR': 'Podrška', 'titleEN': 'Support', 'style': 'support'},
- {'name': 'deconference', 'titleSR': 'Dekonferencija', 'titleEN': 'Deconference', 'style': 'deconference'},
+ {"name": "index", "titleSR": "Početna", "titleEN": "Home", "style": "home"},
+ {"name": "account", "titleSR": "Nalog", "titleEN": "Account", "style": "account"},
+ {"name": "about", "titleSR": "O nama", "titleEN": "About us", "style": "about"},
+ {"name": "statute", "titleSR": "Statut", "titleEN": "Statute", "style": "statute"},
+ {"name": "events", "titleSR": "Događaji", "titleEN": "Events", "style": "events"},
+ {
+ "name": "events_archive",
+ "titleSR": "Arhiva događaja",
+ "titleEN": "Events archive",
+ "style": "events",
+ },
+ {
+ "name": "services",
+ "titleSR": "Servisi",
+ "titleEN": "Services",
+ "style": "services",
+ },
+ {"name": "webring", "titleSR": "Webring", "titleEN": "Webring", "style": ""},
+ {"name": "support", "titleSR": "Podrška", "titleEN": "Support", "style": "support"},
+ {
+ "name": "deconference",
+ "titleSR": "Dekonferencija",
+ "titleEN": "Deconference",
+ "style": "deconference",
+ },
]
-env = Environment(loader=FileSystemLoader('template'))
+env = Environment(loader=FileSystemLoader("template"))
+
def main():
- os.makedirs('site/en/', exist_ok=True)
+ os.makedirs("site/en/", exist_ok=True)
for page in PAGES:
# Build SR Page
with open(f'pages/sr/{page["name"]}.html') as f:
page_content = "

"
page_content += f.read()
-
- sr_html = env.get_template('page-sr.html').render(
- title=page['titleSR'],
+
+ sr_html = env.get_template("page-sr.html").render(
+ title=page["titleSR"],
content=page_content,
- extra_styles=f'' if page['style'] else '',
+ extra_styles=(
+ f''
+ if page["style"]
+ else ""
+ ),
lang="sr",
sr_link=f"/en/{page['name']}",
- current_path=f"/{page['name']}" if page['name'] != 'index' else "/"
+ current_path=f"/{page['name']}" if page["name"] != "index" else "/",
)
-
- sr_filename = "index.html" if page['name'] == 'index' else f"{page['name']}.html"
- with open(f'site/{sr_filename}', 'w') as f:
+
+ sr_filename = (
+ "index.html" if page["name"] == "index" else f"{page['name']}.html"
+ )
+ with open(f"site/{sr_filename}", "w") as f:
f.write(sr_html)
# Build EN Page
with open(f'pages/en/{page["name"]}.html') as f:
page_content = "
"
page_content += f.read()
-
- en_html = env.get_template('page-en.html').render(
- title=page['titleEN'],
+
+ en_html = env.get_template("page-en.html").render(
+ title=page["titleEN"],
content=page_content,
- extra_styles=f'' if page['style'] else '',
+ extra_styles=(
+ f''
+ if page["style"]
+ else ""
+ ),
lang="en",
sr_link=f"/{page['name']}",
- current_path=f"/en/{page['name']}" if page['name'] != 'index' else "/en/"
+ current_path=f"/en/{page['name']}" if page["name"] != "index" else "/en/",
)
-
- en_filename = "index.html" if page['name'] == 'index' else f"{page['name']}.html"
- with open(f'site/en/{en_filename}', 'w') as f:
+
+ en_filename = (
+ "index.html" if page["name"] == "index" else f"{page['name']}.html"
+ )
+ with open(f"site/en/{en_filename}", "w") as f:
f.write(en_html)
-if __name__ == '__main__':
- main()
+if __name__ == "__main__":
+ main()
diff --git a/image_poster.py b/image_poster.py
index 269d2d0..2cd9a9e 100755
--- a/image_poster.py
+++ b/image_poster.py
@@ -13,10 +13,34 @@ NEXT_MONTH = CURRENT_TIME + relativedelta.relativedelta(months=1, day=1)
DAYS_OF_WEEK_SR = ("PON", "UTO", "SRE", "ČET", "PET", "SUB", "NED")
DAYS_OF_WEEK_EN = ("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUn")
-MONTHS_SR = ("Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul",
- "Avgust", "Septembar", "Oktobar", "Novembar", "Decembar")
-MONTHS_EN = ("January", "February", "March", "April", "May", "June", "July",
- "August", "September", "October", "November", "December")
+MONTHS_SR = (
+ "Januar",
+ "Februar",
+ "Mart",
+ "April",
+ "Maj",
+ "Jun",
+ "Jul",
+ "Avgust",
+ "Septembar",
+ "Oktobar",
+ "Novembar",
+ "Decembar",
+)
+MONTHS_EN = (
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+)
HEADER_SR = "Plan za {}"
@@ -36,10 +60,15 @@ All events are always free
def parseArgs(parser):
"""
- Parse all arguments and return the list of argument values
+ Parse all arguments and return the list of argument values
"""
- parser.add_argument("month", metavar="MM",
- help="two digit number representing the month for which to generate poster", default="empty", nargs="?")
+ parser.add_argument(
+ "month",
+ metavar="MM",
+ help="two digit number representing the month for which to generate poster",
+ default="empty",
+ nargs="?",
+ )
return parser.parse_args()
@@ -51,18 +80,19 @@ def load_events(csv_path: str, month: int) -> list[dict]:
next(csv_reader, None)
for event in csv_reader:
event_date = event[0]
- event_date_parsed = dt.datetime.strptime(
- event_date, "%d-%m-%Y").date()
+ event_date_parsed = dt.datetime.strptime(event_date, "%d-%m-%Y").date()
event_time = event[1]
event_title = event[3]
event_title_en = event[3]
if len(event) > 6:
event_title_en = event[6]
- current_event = {"date": event_date_parsed,
- "time": event_time,
- "title": event_title.strip(),
- "title_en": event_title_en.strip()}
+ current_event = {
+ "date": event_date_parsed,
+ "time": event_time,
+ "title": event_title.strip(),
+ "title_en": event_title_en.strip(),
+ }
if event_date_parsed >= month and event_date_parsed < monthafter:
events.append(current_event)
return events
@@ -71,8 +101,7 @@ def load_events(csv_path: str, month: int) -> list[dict]:
def drawMesh(draw, img, fg, bg, font, W, H):
def drawCircle(x, y):
r = 50
- draw.ellipse((x - r, y - r, x + r, y+r),
- fill=fg, outline=(0, 0, 0), width=0)
+ draw.ellipse((x - r, y - r, x + r, y + r), fill=fg, outline=(0, 0, 0), width=0)
LCX = 415 # logo center x
LCY = 4350 # logo center y
@@ -84,11 +113,15 @@ def drawMesh(draw, img, fg, bg, font, W, H):
drawCircle(LCX + d, LCY)
draw.line([(LCX - d, LCY), (LCX + d, LCY)], fill=fg, width=20, joint=None)
- draw.line([(LCX, LCY), (LCX, LCY + d), (LCX + d, LCY),
- (LCX, LCY - d)], fill=fg, width=20, joint=None)
- draw.text((LCX - 1.7*d, LCY + 1.5*d), "dmz.rs", font=font, fill=fg)
+ draw.line(
+ [(LCX, LCY), (LCX, LCY + d), (LCX + d, LCY), (LCX, LCY - d)],
+ fill=fg,
+ width=20,
+ joint=None,
+ )
+ draw.text((LCX - 1.7 * d, LCY + 1.5 * d), "dmz.rs", font=font, fill=fg)
- mesh_svg = svg2png(url='site/img/mesh-light.svg')
+ mesh_svg = svg2png(url="site/img/mesh-light.svg")
mesh_svg_bytes = io.BytesIO(mesh_svg)
mesh_img = Image.open(mesh_svg_bytes)
if bg == (0, 0, 0):
@@ -107,35 +140,32 @@ def drawMesh(draw, img, fg, bg, font, W, H):
def drawPoster(events, bg, fg, month: int, en: bool):
- fontFacade = ImageFont.truetype('./site/font/Facade-Sud.woff', size=365)
- fontIosevka = ImageFont.truetype(
- './site/font/iosevka-regular.woff', size=200)
- fontIosevkaSmall = ImageFont.truetype(
- './site/font/iosevka-regular.woff', size=150)
+ fontFacade = ImageFont.truetype("./site/font/Facade-Sud.woff", size=365)
+ fontIosevka = ImageFont.truetype("./site/font/iosevka-regular.woff", size=200)
+ fontIosevkaSmall = ImageFont.truetype("./site/font/iosevka-regular.woff", size=150)
W = 3508
H = 4960
- img = Image.new('RGB', (W, H), bg)
+ img = Image.new("RGB", (W, H), bg)
draw = ImageDraw.Draw(img)
drawMesh(draw, img, fg, bg, fontIosevka, W, H)
title = "DECENTRALA"
_, _, w, _ = draw.textbbox((0, 0), title, font=fontFacade)
- draw.text(((W-w)/2, 165), title, font=fontFacade, fill=fg)
+ draw.text(((W - w) / 2, 165), title, font=fontFacade, fill=fg)
header = HEADER_EN if en else HEADER_SR
months = MONTHS_EN if en else MONTHS_SR
header = header.format(months[month.month - 1])
_, _, w, _ = draw.textbbox((0, 0), header, font=fontIosevka)
- draw.text(((W-w)/2, 560), header, font=fontIosevka, fill=fg)
+ draw.text(((W - w) / 2, 560), header, font=fontIosevka, fill=fg)
height = 890
sub_header = SUBHEADER_EN if en else SUBHEADER_SR
- draw.text((165, height), sub_header,
- font=fontIosevkaSmall, fill=fg)
+ draw.text((165, height), sub_header, font=fontIosevkaSmall, fill=fg)
height += 800
# Write list of events to sperate text file as well
@@ -167,8 +197,7 @@ def drawPoster(events, bg, fg, month: int, en: bool):
def main():
# Parse arguments
- parser = argparse.ArgumentParser(
- description="Generate images of the poster")
+ parser = argparse.ArgumentParser(description="Generate images of the poster")
args = parseArgs(parser)
# Set month based on user input
@@ -176,22 +205,24 @@ def main():
if args.month.isdigit():
month = dt.date(CURRENT_TIME.year, int(args.month), 1)
elif args.month != "empty":
- print("Month has to be specified as a number. I will use next month as the default")
+ print(
+ "Month has to be specified as a number. I will use next month as the default"
+ )
# Load events and draw a poseter
events = load_events("dogadjaji.csv", month)
img = drawPoster(events, (0, 0, 0), (20, 250, 50), month, False)
- img.save('poster_dark.png')
+ img.save("poster_dark.png")
img = drawPoster(events, (255, 255, 255), (0, 0, 0), month, False)
- img.save('poster_light.png')
+ img.save("poster_light.png")
img = drawPoster(events, (0, 0, 0), (20, 250, 50), month, True)
- img.save('poster_dark_en.png')
+ img.save("poster_dark_en.png")
img = drawPoster(events, (255, 255, 255), (0, 0, 0), month, True)
- img.save('poster_light_en.png')
+ img.save("poster_light_en.png")
if __name__ == "__main__":
diff --git a/pages/en/support.html b/pages/en/support.html
index 110e6c3..4b17ec1 100644
--- a/pages/en/support.html
+++ b/pages/en/support.html
@@ -24,9 +24,7 @@
By simply using services we are hosting, conts as contributing, because in
that way you joining the decetralization.
-
- Also, we accept donations in bitcoin and monero to the following addresses:
-
+ Also, we accept donations in bitcoin to the following address:
- Bitcoin: bc1qjhsfgq79wuzzv32yml9zglwzf9qcwfj3atuy74