diff --git a/generate_post.py b/generate_post.py index c0e0161..1b8e612 100644 --- a/generate_post.py +++ b/generate_post.py @@ -1,5 +1,6 @@ import os import csv +import re import requests from bs4 import BeautifulSoup from fpdf import FPDF @@ -7,31 +8,30 @@ from fpdf import FPDF # ------------------------------------------------------------------------- # CODE & CANVAS BRAND DESIGN TOKENS # ------------------------------------------------------------------------- -COLOR_LINEN = (250, 248, 245) # #FAF8F5 - Default background canvas[span_2](start_span)[span_2](end_span) -COLOR_NAVY = (26, 35, 50) # #1A2332 - Deep structural headers[span_3](start_span)[span_3](end_span) -COLOR_TERRACOTTA = (211, 107, 77) # #D36B4D - Primary visual accents[span_4](start_span)[span_4](end_span) -COLOR_CHARCOAL = (43, 43, 43) # #2B2B2B - High-legibility body text[span_5](start_span)[span_5](end_span) -COLOR_BORDER = (229, 222, 201) # #E5DEC9 - Thin muted gridlines[span_6](start_span)[span_6](end_span) +COLOR_LINEN = (250, 248, 245) # #FAF8F5 - Default background canvas +COLOR_NAVY = (26, 35, 50) # #1A2332 - Deep structural base color +COLOR_TERRACOTTA = (211, 107, 77) # #D36B4D - Primary visual accents +COLOR_CHARCOAL = (43, 43, 43) # #2B2B2B - High-legibility body text +COLOR_BORDER = (229, 222, 201) # #E5DEC9 - Thin muted gridlines -CSV_URL = "https://raw.githubusercontent.com/CodeAndCanvas728/repobase/refs/heads/main/RepoDatabase.csv" +LOCAL_CSV_PATH = "RepoDatabase.csv" +REMOTE_CSV_URL = "https://raw.githubusercontent.com/CodeAndCanvas728/repobase/refs/heads/main/RepoDatabase.csv" class CodeCanvasPDF(FPDF): def header(self): - # Establish Editorial Minimalism and generous margins self.set_fill_color(*COLOR_LINEN) self.rect(0, 0, 210, 297, 'F') - # Subtle top accent line instead of heavy borders self.set_draw_color(*COLOR_BORDER) self.set_line_width(0.5) self.line(15, 15, 195, 15) - # Standalone Ampersand Shorthand as the brand anchor + # Standalone Ampersand Shorthand self.set_text_color(*COLOR_TERRACOTTA) - self.set_font("Times", "I", 16) # Elegant serif substitution for Georgia + 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 for technical meta-tags + self.set_font("Helvetica", "B", 8) # Sans-serif technical data self.text(23, 23, "CODE & CANVAS | INSIGHTS") def footer(self): @@ -40,82 +40,122 @@ class CodeCanvasPDF(FPDF): self.text(185, 285, f"Page {self.page_no()}") def clean_typography(text): - """Enforces the strict micro-styling rule: Absolutely no em-dashes.""" + """Enforces absolute purity of text: No unicode or markdown em-dashes.""" return text.replace("—", " : ").replace("--", " - ") -def fetch_subject(): - try: - response = requests.get(CSV_URL, timeout=10) - if response.status_code == 200 and response.text.strip(): - lines = response.text.splitlines() - reader = csv.DictReader(lines) +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 = [] + selected_subject = None + file_exists_locally = os.path.exists(LOCAL_CSV_PATH) + + # 1. Read Database Data + if file_exists_locally: + with open(LOCAL_CSV_PATH, mode='r', newline='', encoding='utf-8') as f: + reader = csv.DictReader(f) + fieldnames = reader.fieldnames for row in reader: - if row.get('Used', '').lower() == 'no': - return { + # Find the first unused item matching criteria + if not selected_subject and row.get('Used', '').strip().lower() == 'no': + row['Used'] = 'yes' # State Update Mutation + selected_subject = { "title": row['Title'], "desc": row['Description'], "points": [row['Point1'], row['Point2'], row['Point3']], - "url": row.get('URL', '') + "url": row.get('URL', f"https://github.com/search?q={row['Title']}") } - except Exception: - pass - - # Fallback Logic: Scrape GitHub Trending AI/LLM Python/Go repositories - try: - fb_res = requests.get("https://github.com/trending", timeout=10) - soup = BeautifulSoup(fb_res.text, 'html.parser') - repo = soup.find('article', class_='Box-row') - title = repo.find('h2', class_='h3').text.strip().replace('\n', '').replace(' ', '') - desc = repo.find('p', class_='col-9').text.strip() - return { - "title": title, - "desc": desc, - "points": ["Open-source community standard", "Highly scalable architecture", "Streamlined local environment deployment"], - "url": f"https://github.com/{title}" - } - except Exception: - return { - "title": "Model Context Protocol", - "desc": "An open standard enabling secure, structured data communication layers between local environments and LLMs.", - "points": ["Eliminates custom integration glue code", "Maintains clean architectural boundaries", "Built for developer tool interoperability"], - "url": "https://github.com/modelcontextprotocol" - } + 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: + with open(LOCAL_CSV_PATH, mode='w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(rows) + return selected_subject + + # 3. Dynamic GitHub Scraping Fallback Routine + if not selected_subject: + print("Database exhausted or unreachable. Querying trending open-source indexes...") + try: + fb_res = requests.get("https://github.com/trending", timeout=10) + soup = BeautifulSoup(fb_res.text, 'html.parser') + 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(): - data = fetch_subject() + data = fetch_and_update_subject() title = clean_typography(data['title']) desc = clean_typography(data['desc']) points = [clean_typography(p) for p in data['points']] + # Target path configuration + os.makedirs("_drafts", exist_ok=True) + safe_date = "weekly-draft" + # ------------------------------------------------------------------------- - # 1. GENERATE THE BRANDED PDF + # PDF COMPILATION BLOCK # ------------------------------------------------------------------------- pdf = CodeCanvasPDF() pdf.set_margins(15, 30, 15) pdf.add_page() - # Title Layout (High-contrast Serif Display Style) + # Header display layout pdf.set_y(40) pdf.set_text_color(*COLOR_NAVY) - pdf.set_font("Times", "", 28) # Georgia alternative + pdf.set_font("Times", "", 26) pdf.multi_cell(180, 12, title) - # Accent Divider Line + # Layout dividing grid rules pdf.set_draw_color(*COLOR_BORDER) pdf.line(15, pdf.get_y() + 5, 80, pdf.get_y() + 5) - # Brief Informational Summary + # Copy layout execution pdf.set_y(pdf.get_y() + 15) pdf.set_text_color(*COLOR_CHARCOAL) - pdf.set_font("Helvetica", "", 11) # Sans-serif structural body text + pdf.set_font("Helvetica", "", 11) pdf.multi_cell(180, 7, desc) - # Structured Grid for Key Highlights + # 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) - # Structural thin-bordered card layouts pdf.rect(15, pdf.get_y(), 180, 18, 'DF') pdf.set_x(20) @@ -128,11 +168,11 @@ def generate_assets(): pdf.cell(160, 18, pt) pdf.set_y(pdf.get_y() + 22) - pdf_filename = "linkedin_insight_asset.pdf" - pdf.output(pdf_filename) + pdf_path = f"_drafts/{safe_date}.pdf" + pdf.output(pdf_path) # ------------------------------------------------------------------------- - # 2. GENERATE LINKEDIN POST COPY + # MARKDOWN CONVERSION BLOCK # ------------------------------------------------------------------------- post_copy = f"""💡 Weekly AI Architecture Breakdown @@ -152,10 +192,11 @@ Repository Link: {data['url']} #SoftwareArchitecture #DeveloperTools #TechInsights #CodeAndCanvas""" - print("--- LINKEDIN POST DRAFT ---") - print(post_copy) - print("\n--- ASSET STATUS ---") - print(f"Success: {pdf_filename} generated inside your directory following brand design guidelines.") + md_path = f"_drafts/{safe_date}.md" + with open(md_path, mode='w', encoding='utf-8') as f: + f.write(post_copy) + + print(f"Workflow Complete: Created tracking drafts inside configuration paths.") if __name__ == "__main__": generate_assets()