Skip to content

Commit 34fe5b4

Browse files
committed
Implement TypeScript legacy site generator
Replace Python 2 legacy/generate-text-directory.py with TypeScript. The generator reads church data from map/data.json, groups by region, and generates static HTML pages using the legacy template. Static files (images, index pages) are copied from legacy/ to public/legacy/. Runs automatically via prebuild script: npm run generate:legacy
1 parent 9019b32 commit 34fe5b4

2 files changed

Lines changed: 231 additions & 2 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ dist
44
.vite-tmp
55
*.tsbuildinfo
66
.runtime/
7+
public/legacy/

scripts/generate-legacy.ts

Lines changed: 230 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,234 @@
11
/**
22
* Generate legacy static pages from templates.
3-
* TODO: Implement actual legacy page generation from legacy-templates/
3+
*
4+
* This TypeScript script replaces the Python 2 legacy/generate-text-directory.py
5+
* It reads church data from map/data.json, groups by region, and generates
6+
* static HTML pages using the legacy template, then copies everything to public/legacy/
47
*/
58

6-
console.log('generate-legacy: stub - skipping legacy generation for now');
9+
import {
10+
readFileSync,
11+
writeFileSync,
12+
mkdirSync,
13+
existsSync,
14+
readdirSync,
15+
statSync,
16+
copyFileSync,
17+
} from 'fs';
18+
import { join, dirname } from 'path';
19+
import { fileURLToPath } from 'url';
20+
21+
const __dirname = dirname(fileURLToPath(import.meta.url));
22+
const projectRoot = join(__dirname, '..');
23+
24+
// Paths
25+
const DATA_PATH = join(projectRoot, 'public', 'map', 'data.json');
26+
const REGIONS_PATH = join(projectRoot, 'legacy', 'regions.json');
27+
const TEMPLATE_PATH = join(projectRoot, 'legacy', 'text-dir-template.htm');
28+
const LEGACY_DIR = join(projectRoot, 'legacy');
29+
const OUTPUT_DIR = join(projectRoot, 'public', 'legacy');
30+
31+
// Types
32+
interface ChurchProperties {
33+
name: string;
34+
address: string;
35+
website?: string;
36+
note?: string;
37+
region: string;
38+
}
39+
40+
interface ChurchFeature {
41+
type: 'Feature';
42+
geometry: {
43+
type: 'Point';
44+
coordinates: [number, number];
45+
};
46+
properties: ChurchProperties;
47+
}
48+
49+
interface ChurchData {
50+
type: 'FeatureCollection';
51+
features: ChurchFeature[];
52+
}
53+
54+
interface RegionMapping {
55+
code: string;
56+
'display-name': string;
57+
'file-name': string;
58+
}
59+
60+
// Load region mappings
61+
function loadRegionData(): RegionMapping[] {
62+
const content = readFileSync(REGIONS_PATH, 'utf-8');
63+
return JSON.parse(content);
64+
}
65+
66+
// Get display name for a region code
67+
function getRegionDisplayName(regions: RegionMapping[], code: string): string {
68+
const region = regions.find((r) => r.code === code);
69+
return region?.['display-name'] || code;
70+
}
71+
72+
// Get file name for a region code
73+
function getRegionFileName(regions: RegionMapping[], code: string): string {
74+
const region = regions.find((r) => r.code === code);
75+
return region?.['file-name'] || code.toLowerCase();
76+
}
77+
78+
// Generate HTML for a list of churches
79+
function generateChurchesHtml(churches: ChurchFeature[]): string {
80+
return churches
81+
.map((church) => {
82+
const { name, address, note, website } = church.properties;
83+
// Note: The original Python script doesn't escape HTML - the note field
84+
// already contains raw HTML from the legacy data
85+
const safeNote = note || '';
86+
const safeWebsite = website || '';
87+
88+
// Match the exact format from the Python generator
89+
return `
90+
<tr>
91+
<td>
92+
<hr WIDTH="100%"></td>
93+
</tr>
94+
95+
<tr>
96+
<td><b><font face="Calibri">${name}&nbsp;</font></b>
97+
<br><font face="Calibri">${address}</font>
98+
<br><font face="Calibri">${safeNote}</font>
99+
<br><font face="Calibri"><a href="http://${safeWebsite}">${safeWebsite}</a></font></td>
100+
</tr>
101+
`;
102+
})
103+
.join('');
104+
}
105+
106+
// Generate HTML page for a region
107+
function generateRegionPage(
108+
template: string,
109+
regionCode: string,
110+
churches: ChurchFeature[],
111+
regions: RegionMapping[]
112+
): string {
113+
const displayName = getRegionDisplayName(regions, regionCode);
114+
const churchesHtml = generateChurchesHtml(churches);
115+
116+
return template
117+
.replace(/{% REGION_NAME %}/g, displayName)
118+
.replace(/{% CHURCHES %}/g, churchesHtml);
119+
}
120+
121+
// Group churches by region
122+
function groupByRegion(
123+
churches: ChurchFeature[]
124+
): Map<string, ChurchFeature[]> {
125+
const grouped = new Map<string, ChurchFeature[]>();
126+
127+
for (const church of churches) {
128+
const region = church.properties.region;
129+
if (!grouped.has(region)) {
130+
grouped.set(region, []);
131+
}
132+
grouped.get(region)!.push(church);
133+
}
134+
135+
return grouped;
136+
}
137+
138+
// Copy static legacy files (excluding Python script and generated rbcd/*.htm)
139+
function copyStaticLegacyFiles(): void {
140+
const rbcdDir = join(LEGACY_DIR, 'rbcd');
141+
const outputRbcdDir = join(OUTPUT_DIR, 'rbcd');
142+
143+
// Ensure output directories exist
144+
mkdirSync(OUTPUT_DIR, { recursive: true });
145+
mkdirSync(outputRbcdDir, { recursive: true });
146+
147+
function copyDir(src: string, dest: string, skipPatterns: RegExp[] = []) {
148+
mkdirSync(dest, { recursive: true });
149+
150+
for (const entry of readdirSync(src)) {
151+
const srcPath = join(src, entry);
152+
const destPath = join(dest, entry);
153+
154+
// Skip patterns
155+
if (skipPatterns.some((p) => p.test(entry))) {
156+
continue;
157+
}
158+
159+
const stat = statSync(srcPath);
160+
if (stat.isDirectory()) {
161+
// Skip rbcd directory - we'll handle it specially
162+
if (entry !== 'rbcd') {
163+
copyDir(srcPath, destPath, skipPatterns);
164+
}
165+
} else {
166+
copyFileSync(srcPath, destPath);
167+
}
168+
}
169+
}
170+
171+
// Copy legacy root files (skip .py files and rbcd directory)
172+
copyDir(LEGACY_DIR, OUTPUT_DIR, [/\.py$/, /^rbcd$/]);
173+
174+
// Copy ALL files from rbcd (including static HTML index pages like usa.htm, foreign.htm)
175+
// Generated region pages will overwrite old ones later
176+
if (existsSync(rbcdDir)) {
177+
for (const entry of readdirSync(rbcdDir)) {
178+
const srcPath = join(rbcdDir, entry);
179+
const destPath = join(outputRbcdDir, entry);
180+
const stat = statSync(srcPath);
181+
if (!stat.isDirectory()) {
182+
copyFileSync(srcPath, destPath);
183+
}
184+
}
185+
}
186+
}
187+
188+
// Main function
189+
function main(): void {
190+
console.log('generate-legacy: Starting legacy site generation...');
191+
192+
// Check if data file exists
193+
if (!existsSync(DATA_PATH)) {
194+
console.error(`Error: Church data not found at ${DATA_PATH}`);
195+
process.exit(1);
196+
}
197+
198+
// Load data
199+
const churchData: ChurchData = JSON.parse(readFileSync(DATA_PATH, 'utf-8'));
200+
const regions = loadRegionData();
201+
const template = readFileSync(TEMPLATE_PATH, 'utf-8');
202+
203+
console.log(` Loaded ${churchData.features.length} churches`);
204+
console.log(` Loaded ${regions.length} region mappings`);
205+
206+
// Copy static files first
207+
console.log(' Copying static legacy files...');
208+
copyStaticLegacyFiles();
209+
210+
// Group churches by region
211+
const grouped = groupByRegion(churchData.features);
212+
console.log(` Found ${grouped.size} regions with churches`);
213+
214+
// Ensure output directory exists
215+
const rbcdOutputDir = join(OUTPUT_DIR, 'rbcd');
216+
mkdirSync(rbcdOutputDir, { recursive: true });
217+
218+
// Generate pages for each region
219+
let generated = 0;
220+
for (const [regionCode, churches] of grouped) {
221+
const fileName = getRegionFileName(regions, regionCode);
222+
const displayName = getRegionDisplayName(regions, regionCode);
223+
const html = generateRegionPage(template, regionCode, churches, regions);
224+
const outputPath = join(rbcdOutputDir, `${fileName}.htm`);
225+
226+
writeFileSync(outputPath, html, 'utf-8');
227+
console.log(` Generated ${fileName}.htm (${displayName}: ${churches.length} churches)`);
228+
generated++;
229+
}
230+
231+
console.log(`generate-legacy: Done! Generated ${generated} region pages to public/legacy/rbcd/`);
232+
}
233+
234+
main();

0 commit comments

Comments
 (0)