1+ #!/usr/bin/env python3
2+ """
3+ Sync quotes from Google Sheets to Jekyll markdown file.
4+
5+ This script reads quotes from a Google Sheet and generates the quotes.md file.
6+ """
7+
8+ import os
9+ from google .oauth2 import service_account
10+ from googleapiclient .discovery import build
11+
12+ # Scopes for read-only access
13+ SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly' ]
14+
15+ # Your Google Sheets ID (update this with your quotes sheet ID)
16+ SPREADSHEET_ID = 'YOUR_QUOTES_SPREADSHEET_ID_HERE'
17+
18+ # Sheet name and range
19+ RANGE_NAME = 'Sheet1!A:C' # Assumes columns: Section, Quote, Attribution
20+
21+ def authenticate_google_sheets ():
22+ """Authenticate using service account and return Google Sheets service."""
23+ creds = service_account .Credentials .from_service_account_file (
24+ 'service-account-key.json' , scopes = SCOPES )
25+
26+ return build ('sheets' , 'v4' , credentials = creds )
27+
28+ def fetch_sheet_data (service ):
29+ """Fetch data from Google Sheets."""
30+ sheet = service .spreadsheets ()
31+ result = sheet .values ().get (spreadsheetId = SPREADSHEET_ID ,
32+ range = RANGE_NAME ).execute ()
33+ values = result .get ('values' , [])
34+
35+ if not values :
36+ print ('No data found.' )
37+ return []
38+
39+ # First row is headers
40+ headers = values [0 ]
41+ data = []
42+
43+ for row in values [1 :]:
44+ # Pad row to match header length
45+ row_data = row + ['' ] * (len (headers ) - len (row ))
46+ data .append (dict (zip (headers , row_data )))
47+
48+ return data
49+
50+ def escape_markdown (text ):
51+ """Escape special characters for markdown."""
52+ # Don't escape characters inside quotes
53+ return text
54+
55+ def generate_quotes_markdown (quotes_data ):
56+ """Generate the quotes.md file content from quotes data."""
57+ # Group quotes by section
58+ sections = {}
59+ for quote in quotes_data :
60+ section = quote .get ('Section' , 'Uncategorized' )
61+ if section not in sections :
62+ sections [section ] = []
63+ sections [section ].append (quote )
64+
65+ # Sort sections alphabetically
66+ sorted_sections = sorted (sections .keys ())
67+
68+ # Generate markdown content
69+ content = []
70+ content .append ("""---
71+ title: "Brandon - quotes"
72+ layout: textlay
73+ excerpt: "Quote collection"
74+ sitemap: false
75+ permalink: /quotes/
76+ ---
77+
78+ <style>
79+ /* this is kind of a hack */
80+ blockquote {
81+ padding: 5px 20px;
82+ margin: 0 0 20px;
83+ font-size: 16px;
84+ border-left: 5px solid #eee;
85+ }
86+ blockquote strong em {
87+ color: #7F8C8D;
88+ }
89+ </style>
90+
91+ # Table of Contents""" )
92+
93+ # Generate table of contents
94+ toc_items = []
95+ for section in sorted_sections :
96+ # Create anchor-friendly section ID
97+ section_id = section .lower ().replace (' ' , '-' ).replace ('/' , '-' ).replace ('\' ' , '' )
98+ toc_items .append (f"[{ section } | ](#{ section_id } )" )
99+
100+ content .append (' | ' .join (toc_items [:5 ])) # First row
101+ for i in range (5 , len (toc_items ), 5 ):
102+ content .append (' | ' .join (toc_items [i :i + 5 ]))
103+
104+ content .append ("\n [Back to Brandon's page](/brandon/)" )
105+
106+ # Generate sections with quotes
107+ for section in sorted_sections :
108+ # Create anchor-friendly section ID
109+ section_id = section .lower ().replace (' ' , '-' ).replace ('/' , '-' ).replace ('\' ' , '' )
110+
111+ content .append (f"\n ### { section } " )
112+
113+ for quote in sections [section ]:
114+ quote_text = quote .get ('Quote' , '' ).strip ()
115+ attribution = quote .get ('Attribution' , '' ).strip ()
116+
117+ if quote_text :
118+ # Format the quote
119+ content .append (f"> { quote_text } " )
120+ if attribution :
121+ content .append (f"> **--_{ attribution } _**" )
122+ content .append ("" ) # Empty line between quotes
123+
124+ content .append ("[Back to Top](# )\n " )
125+ content .append ("\n [Back to Brandon's page](/brandon/)" )
126+
127+ return '\n ' .join (content )
128+
129+ def save_quotes_file (content ):
130+ """Save the generated content to quotes.md."""
131+ output_path = '_pages/quotes.md'
132+
133+ # Ensure directory exists
134+ os .makedirs (os .path .dirname (output_path ), exist_ok = True )
135+
136+ with open (output_path , 'w' , encoding = 'utf-8' ) as f :
137+ f .write (content )
138+
139+ print (f"Saved quotes to { output_path } " )
140+
141+ def main ():
142+ """Main function to sync quotes from Google Sheets."""
143+ # Check if service account key exists
144+ if not os .path .exists ('service-account-key.json' ):
145+ print ("Error: service-account-key.json not found!" )
146+ print ("Please ensure you're using the same service account as for team sync." )
147+ return
148+
149+ print ("Authenticating with Google Sheets..." )
150+ service = authenticate_google_sheets ()
151+
152+ print ("Fetching quotes from Google Sheets..." )
153+ quotes_data = fetch_sheet_data (service )
154+
155+ if not quotes_data :
156+ print ("No quotes to process." )
157+ return
158+
159+ print (f"Found { len (quotes_data )} quotes" )
160+
161+ # Generate markdown content
162+ markdown_content = generate_quotes_markdown (quotes_data )
163+
164+ # Save to file
165+ save_quotes_file (markdown_content )
166+
167+ print ("Quotes sync complete!" )
168+
169+ if __name__ == '__main__' :
170+ main ()
0 commit comments