Refactor PDF generation and update class structure
This commit is contained in:
parent
1d54412d34
commit
d8350ef83e
1 changed files with 132 additions and 107 deletions
239
generate_post.py
239
generate_post.py
|
|
@ -1,6 +1,5 @@
|
||||||
import os
|
import os
|
||||||
import csv
|
import csv
|
||||||
import re
|
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from fpdf import FPDF
|
from fpdf import FPDF
|
||||||
|
|
@ -17,48 +16,55 @@ COLOR_BORDER = (229, 222, 201) # #E5DEC9 - Thin muted gridlines
|
||||||
LOCAL_CSV_PATH = "RepoDatabase.csv"
|
LOCAL_CSV_PATH = "RepoDatabase.csv"
|
||||||
REMOTE_CSV_URL = "https://raw.githubusercontent.com/CodeAndCanvas728/repobase/refs/heads/main/RepoDatabase.csv"
|
REMOTE_CSV_URL = "https://raw.githubusercontent.com/CodeAndCanvas728/repobase/refs/heads/main/RepoDatabase.csv"
|
||||||
|
|
||||||
class CodeCanvasPDF(FPDF):
|
class CodeCanvasCarousel(FPDF):
|
||||||
def header(self):
|
def __init__(self):
|
||||||
self.set_fill_color(*COLOR_LINEN)
|
# Initialize in Landscape mode ('L') using millimeters, explicitly custom sizing (A5 Landscape)
|
||||||
self.rect(0, 0, 210, 297, 'F')
|
super().__init__(orientation='L', unit='mm', format='A5')
|
||||||
|
self.set_margins(20, 25, 20)
|
||||||
self.set_draw_color(*COLOR_BORDER)
|
self.set_auto_page_break(False)
|
||||||
self.set_line_width(0.5)
|
|
||||||
self.line(15, 15, 195, 15)
|
|
||||||
|
|
||||||
# Standalone Ampersand Shorthand
|
|
||||||
self.set_text_color(*COLOR_TERRACOTTA)
|
|
||||||
self.set_font("Times", "I", 16) # Classic serif
|
|
||||||
self.text(15, 25, "&")
|
|
||||||
self.set_text_color(*COLOR_NAVY)
|
|
||||||
self.set_font("Helvetica", "B", 8) # Sans-serif technical data
|
|
||||||
self.text(23, 23, "CODE & CANVAS | INSIGHTS")
|
|
||||||
|
|
||||||
def footer(self):
|
def draw_brand_canvas(self):
|
||||||
|
"""Fills background with Linen Canvas and draws the minimal header framework."""
|
||||||
|
self.set_fill_color(*COLOR_LINEN)
|
||||||
|
self.rect(0, 0, 210, 148, 'F') # A5 Landscape boundary dimensions
|
||||||
|
|
||||||
|
# Thin editorial top rule
|
||||||
|
self.set_draw_color(*COLOR_BORDER)
|
||||||
|
self.set_line_width(0.4)
|
||||||
|
self.line(20, 15, 190, 15)
|
||||||
|
|
||||||
|
# Standalone Ampersand Shorthand as visual anchor
|
||||||
|
self.set_text_color(*COLOR_TERRACOTTA)
|
||||||
|
self.set_font("Times", "I", 16)
|
||||||
|
self.text(20, 24, "&")
|
||||||
|
|
||||||
|
# Sans-serif technical meta-tag
|
||||||
|
self.set_text_color(*COLOR_NAVY)
|
||||||
|
self.set_font("Helvetica", "B", 7)
|
||||||
|
self.text(28, 22, "CODE & CANVAS | INSIGHTS")
|
||||||
|
|
||||||
|
def draw_footer(self, current_slide, total_slides):
|
||||||
|
"""Draws page numbering following clean, minimal layout scales."""
|
||||||
self.set_text_color(*COLOR_CHARCOAL)
|
self.set_text_color(*COLOR_CHARCOAL)
|
||||||
self.set_font("Helvetica", "", 8)
|
self.set_font("Helvetica", "", 8)
|
||||||
self.text(185, 285, f"Page {self.page_no()}")
|
self.text(180, 138, f"{current_slide} / {total_slides}")
|
||||||
|
|
||||||
def clean_typography(text):
|
def clean_typography(text):
|
||||||
"""Enforces absolute purity of text: No unicode or markdown em-dashes."""
|
"""Enforces absolute micro-styling purity: completely strips out em-dashes."""
|
||||||
return text.replace("—", " : ").replace("--", " - ")
|
return text.replace("—", " : ").replace("--", " - ")
|
||||||
|
|
||||||
def fetch_and_update_subject():
|
def fetch_and_update_subject():
|
||||||
"""Reads local repository CSV to find an unused row, updates it to 'yes',
|
|
||||||
and writes it back immediately to prevent duplicate scheduling entries."""
|
|
||||||
rows = []
|
rows = []
|
||||||
selected_subject = None
|
selected_subject = None
|
||||||
file_exists_locally = os.path.exists(LOCAL_CSV_PATH)
|
file_exists_locally = os.path.exists(LOCAL_CSV_PATH)
|
||||||
|
|
||||||
# 1. Read Database Data
|
|
||||||
if file_exists_locally:
|
if file_exists_locally:
|
||||||
with open(LOCAL_CSV_PATH, mode='r', newline='', encoding='utf-8') as f:
|
with open(LOCAL_CSV_PATH, mode='r', newline='', encoding='utf-8') as f:
|
||||||
reader = csv.DictReader(f)
|
reader = csv.DictReader(f)
|
||||||
fieldnames = reader.fieldnames
|
fieldnames = reader.fieldnames
|
||||||
for row in reader:
|
for row in reader:
|
||||||
# Find the first unused item matching criteria
|
|
||||||
if not selected_subject and row.get('Used', '').strip().lower() == 'no':
|
if not selected_subject and row.get('Used', '').strip().lower() == 'no':
|
||||||
row['Used'] = 'yes' # State Update Mutation
|
row['Used'] = 'yes'
|
||||||
selected_subject = {
|
selected_subject = {
|
||||||
"title": row['Title'],
|
"title": row['Title'],
|
||||||
"desc": row['Description'],
|
"desc": row['Description'],
|
||||||
|
|
@ -66,27 +72,7 @@ def fetch_and_update_subject():
|
||||||
"url": row.get('URL', f"https://github.com/search?q={row['Title']}")
|
"url": row.get('URL', f"https://github.com/search?q={row['Title']}")
|
||||||
}
|
}
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
else:
|
|
||||||
# Fallback tracking if working outside local repository layout
|
|
||||||
print("Warning: Local RepoDatabase.csv not found. Reverting to remote live endpoint.")
|
|
||||||
try:
|
|
||||||
res = requests.get(REMOTE_CSV_URL, timeout=10)
|
|
||||||
if res.status_code == 200:
|
|
||||||
lines = res.text.splitlines()
|
|
||||||
reader = csv.DictReader(lines)
|
|
||||||
fieldnames = reader.fieldnames
|
|
||||||
for row in reader:
|
|
||||||
if not selected_subject and row.get('Used', '').strip().lower() == 'no':
|
|
||||||
selected_subject = {
|
|
||||||
"title": row['Title'],
|
|
||||||
"desc": row['Description'],
|
|
||||||
"points": [row['Point1'], row['Point2'], row['Point3']],
|
|
||||||
"url": row.get('URL', '')
|
|
||||||
}
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 2. Save Updated Database State if modified locally
|
|
||||||
if selected_subject and file_exists_locally:
|
if selected_subject and file_exists_locally:
|
||||||
with open(LOCAL_CSV_PATH, mode='w', newline='', encoding='utf-8') as f:
|
with open(LOCAL_CSV_PATH, mode='w', newline='', encoding='utf-8') as f:
|
||||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||||
|
|
@ -94,29 +80,13 @@ def fetch_and_update_subject():
|
||||||
writer.writerows(rows)
|
writer.writerows(rows)
|
||||||
return selected_subject
|
return selected_subject
|
||||||
|
|
||||||
# 3. Dynamic GitHub Scraping Fallback Routine
|
# Ultimate fallback logic if CSV down or empty
|
||||||
if not selected_subject:
|
return {
|
||||||
print("Database exhausted or unreachable. Querying trending open-source indexes...")
|
"title": "Model Context Protocol",
|
||||||
try:
|
"desc": "An open standard enabling secure, structured data communication layers between local environments and generative applications.",
|
||||||
fb_res = requests.get("https://github.com/trending", timeout=10)
|
"points": ["Eliminates custom integration glue code", "Maintains clean architectural boundaries", "Built for cross-tool interoperability"],
|
||||||
soup = BeautifulSoup(fb_res.text, 'html.parser')
|
"url": "https://github.com/modelcontextprotocol"
|
||||||
repo = soup.find('article', class_='Box-row')
|
}
|
||||||
raw_title = repo.find('h2', class_='h3').text.strip().replace('\n', '').replace(' ', '')
|
|
||||||
desc = repo.find('p', class_='col-9').text.strip()
|
|
||||||
return {
|
|
||||||
"title": raw_title.split('/')[-1],
|
|
||||||
"desc": desc,
|
|
||||||
"points": ["Open-source workflow standard", "Highly modular architectural design", "Streamlined environment footprint"],
|
|
||||||
"url": f"https://github.com/{raw_title}"
|
|
||||||
}
|
|
||||||
except Exception:
|
|
||||||
# Absolute hard-fail recovery state
|
|
||||||
return {
|
|
||||||
"title": "Model Context Protocol",
|
|
||||||
"desc": "An open standard enabling secure, structured data communication layers between local environments and generative applications.",
|
|
||||||
"points": ["Eliminates custom integration glue code", "Maintains clear architectural boundaries", "Built for cross-tool interoperability"],
|
|
||||||
"url": "https://github.com/modelcontextprotocol"
|
|
||||||
}
|
|
||||||
|
|
||||||
def generate_assets():
|
def generate_assets():
|
||||||
data = fetch_and_update_subject()
|
data = fetch_and_update_subject()
|
||||||
|
|
@ -124,55 +94,113 @@ def generate_assets():
|
||||||
desc = clean_typography(data['desc'])
|
desc = clean_typography(data['desc'])
|
||||||
points = [clean_typography(p) for p in data['points']]
|
points = [clean_typography(p) for p in data['points']]
|
||||||
|
|
||||||
# Target path configuration
|
|
||||||
os.makedirs("_drafts", exist_ok=True)
|
os.makedirs("_drafts", exist_ok=True)
|
||||||
safe_date = "weekly-draft"
|
TOTAL_SLIDES = 4
|
||||||
|
|
||||||
|
pdf = CodeCanvasCarousel()
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# PDF COMPILATION BLOCK
|
# SLIDE 1: DISPLAY TITLE CARD
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
pdf = CodeCanvasPDF()
|
|
||||||
pdf.set_margins(15, 30, 15)
|
|
||||||
pdf.add_page()
|
pdf.add_page()
|
||||||
|
pdf.draw_brand_canvas()
|
||||||
|
|
||||||
|
pdf.set_y(45)
|
||||||
|
pdf.set_text_color(*COLOR_NAVY)
|
||||||
|
pdf.set_font("Times", "", 32) # Editorial high-contrast serif display
|
||||||
|
pdf.multi_cell(170, 14, title)
|
||||||
|
|
||||||
|
pdf.set_y(pdf.get_y() + 6)
|
||||||
|
pdf.set_text_color(*COLOR_CHARCOAL)
|
||||||
|
pdf.set_font("Helvetica", "", 12) # Sans-serif structural subtitle
|
||||||
|
pdf.multi_cell(170, 6, "An architectural breakdown of engineering utility and integration footprints.")
|
||||||
|
pdf.draw_footer(1, TOTAL_SLIDES)
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# SLIDE 2: THE CORE PROBLEMATIQUE / OVERVIEW
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
pdf.add_page()
|
||||||
|
pdf.draw_brand_canvas()
|
||||||
|
|
||||||
# Header display layout
|
|
||||||
pdf.set_y(40)
|
pdf.set_y(40)
|
||||||
pdf.set_text_color(*COLOR_NAVY)
|
pdf.set_text_color(*COLOR_NAVY)
|
||||||
pdf.set_font("Times", "", 26)
|
pdf.set_font("Times", "", 18)
|
||||||
pdf.multi_cell(180, 12, title)
|
pdf.cell(170, 8, "Core Structural Objective", ln=True)
|
||||||
|
|
||||||
# Layout dividing grid rules
|
# Subtle geometric divider line
|
||||||
pdf.set_draw_color(*COLOR_BORDER)
|
pdf.set_draw_color(*COLOR_BORDER)
|
||||||
pdf.line(15, pdf.get_y() + 5, 80, pdf.get_y() + 5)
|
pdf.line(20, pdf.get_y() + 2, 60, pdf.get_y() + 2)
|
||||||
|
|
||||||
# Copy layout execution
|
pdf.set_y(pdf.get_y() + 12)
|
||||||
pdf.set_y(pdf.get_y() + 15)
|
|
||||||
pdf.set_text_color(*COLOR_CHARCOAL)
|
pdf.set_text_color(*COLOR_CHARCOAL)
|
||||||
pdf.set_font("Helvetica", "", 11)
|
pdf.set_font("Helvetica", "", 11)
|
||||||
pdf.multi_cell(180, 7, desc)
|
pdf.multi_cell(170, 7, desc)
|
||||||
|
pdf.draw_footer(2, TOTAL_SLIDES)
|
||||||
# Balanced spacing grids for parameters
|
|
||||||
pdf.set_y(pdf.get_y() + 15)
|
|
||||||
for idx, pt in enumerate(points, 1):
|
|
||||||
pdf.set_draw_color(*COLOR_BORDER)
|
|
||||||
pdf.set_fill_color(*COLOR_LINEN)
|
|
||||||
pdf.rect(15, pdf.get_y(), 180, 18, 'DF')
|
|
||||||
|
|
||||||
pdf.set_x(20)
|
|
||||||
pdf.set_text_color(*COLOR_TERRACOTTA)
|
|
||||||
pdf.set_font("Times", "I", 12)
|
|
||||||
pdf.cell(10, 18, f"0{idx}")
|
|
||||||
|
|
||||||
pdf.set_text_color(*COLOR_CHARCOAL)
|
|
||||||
pdf.set_font("Helvetica", "", 10)
|
|
||||||
pdf.cell(160, 18, pt)
|
|
||||||
pdf.set_y(pdf.get_y() + 22)
|
|
||||||
|
|
||||||
pdf_path = f"_drafts/{safe_date}.pdf"
|
|
||||||
pdf.output(pdf_path)
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# MARKDOWN CONVERSION BLOCK
|
# SLIDE 3: TECHNICAL CONSIDERATIONS (GRID BLOCK)
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
pdf.add_page()
|
||||||
|
pdf.draw_brand_canvas()
|
||||||
|
|
||||||
|
pdf.set_y(38)
|
||||||
|
pdf.set_text_color(*COLOR_NAVY)
|
||||||
|
pdf.set_font("Times", "", 18)
|
||||||
|
pdf.cell(170, 8, "Key Architecture Vectors", ln=True)
|
||||||
|
|
||||||
|
start_y = pdf.get_y() + 8
|
||||||
|
for idx, pt in enumerate(points):
|
||||||
|
current_item_y = start_y + (idx * 22)
|
||||||
|
|
||||||
|
# Structural card containers using thin borders
|
||||||
|
pdf.set_draw_color(*COLOR_BORDER)
|
||||||
|
pdf.set_fill_color(*COLOR_LINEN)
|
||||||
|
pdf.rect(20, current_item_y, 170, 16, 'DF')
|
||||||
|
|
||||||
|
# Terracotta index anchor points
|
||||||
|
pdf.set_y(current_item_y + 4)
|
||||||
|
pdf.set_x(24)
|
||||||
|
pdf.set_text_color(*COLOR_TERRACOTTA)
|
||||||
|
pdf.set_font("Times", "I", 11)
|
||||||
|
pdf.cell(10, 8, f"0{idx+1}")
|
||||||
|
|
||||||
|
# Clean structural sans body text
|
||||||
|
pdf.set_x(36)
|
||||||
|
pdf.set_text_color(*COLOR_CHARCOAL)
|
||||||
|
pdf.set_font("Helvetica", "", 10)
|
||||||
|
pdf.cell(150, 8, pt)
|
||||||
|
|
||||||
|
pdf.draw_footer(3, TOTAL_SLIDES)
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# SLIDE 4: THE CALL TO ACTION / ARCHITECTURE ROOT
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
pdf.add_page()
|
||||||
|
pdf.draw_brand_canvas()
|
||||||
|
|
||||||
|
pdf.set_y(45)
|
||||||
|
pdf.set_text_color(*COLOR_NAVY)
|
||||||
|
pdf.set_font("Times", "", 24)
|
||||||
|
pdf.multi_cell(170, 10, f"Explore the Open-Source Blueprint")
|
||||||
|
|
||||||
|
pdf.set_y(pdf.get_y() + 10)
|
||||||
|
pdf.set_text_color(*COLOR_CHARCOAL)
|
||||||
|
pdf.set_font("Helvetica", "", 11)
|
||||||
|
pdf.multi_cell(170, 6, "Review the configuration patterns, track the baseline execution logs, and implement the layout block directly into your development container environments.")
|
||||||
|
|
||||||
|
# High-contrast action footer block
|
||||||
|
pdf.set_y(pdf.get_y() + 12)
|
||||||
|
pdf.set_text_color(*COLOR_TERRACOTTA)
|
||||||
|
pdf.set_font("Helvetica", "B", 10)
|
||||||
|
pdf.cell(170, 6, f"Source Index: {data['url']}", ln=True)
|
||||||
|
|
||||||
|
pdf.draw_footer(4, TOTAL_SLIDES)
|
||||||
|
|
||||||
|
# Output file generation paths
|
||||||
|
pdf.output("_drafts/weekly-draft.pdf")
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# GENERATE MARKDOWN TEXT FILE
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
post_copy = f"""💡 Weekly AI Architecture Breakdown
|
post_copy = f"""💡 Weekly AI Architecture Breakdown
|
||||||
|
|
||||||
|
|
@ -192,11 +220,8 @@ Repository Link: {data['url']}
|
||||||
|
|
||||||
#SoftwareArchitecture #DeveloperTools #TechInsights #CodeAndCanvas"""
|
#SoftwareArchitecture #DeveloperTools #TechInsights #CodeAndCanvas"""
|
||||||
|
|
||||||
md_path = f"_drafts/{safe_date}.md"
|
with open("_drafts/weekly-draft.md", mode='w', encoding='utf-8') as f:
|
||||||
with open(md_path, mode='w', encoding='utf-8') as f:
|
|
||||||
f.write(post_copy)
|
f.write(post_copy)
|
||||||
|
|
||||||
print(f"Workflow Complete: Created tracking drafts inside configuration paths.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
generate_assets()
|
generate_assets()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue