src/html-generator/url-manager.lua

383 lines

1#!/usr/bin/env lua
2
3-- URL Structure Manager for Poetry Website Generation
4-- Handles clean URL generation, directory creation, and navigation paths
5
6local utils = require("libs.utils")
7
8local M = {}
9local DIR = "/mnt/mtwo/programming/ai-stuff/neocities-modernization"
10
11-- Default configuration
12M.config = {
13 base_output_dir = DIR .. "/generated-site",
14 poetry_section = "words-pdf-sorted",
15 main_categories = {"fediverse", "messages", "notes"},
16 special_categories = {"golden"},
17 browse_sections = {"by-similarity", "recent", "random"}
18}
19
20-- {{{ function M.set_config
21function M.set_config(new_config)
22 for key, value in pairs(new_config) do
23 M.config[key] = value
24 end
25end
26-- }}}
27
28-- {{{ function M.generate_poem_url
29function M.generate_poem_url(poem_id, category, options)
30 options = options or {}
31 category = category or "poems"
32 local use_golden = options.use_golden_category or false
33
34 -- Handle golden poem categorization
35 if use_golden and options.is_golden then
36 category = "golden"
37 end
38
39 return string.format("poems/%s/poem-%03d.html", category, tonumber(poem_id))
40end
41-- }}}
42
43-- {{{ function M.generate_category_index_url
44function M.generate_category_index_url(category)
45 return string.format("poems/%s/index.html", category)
46end
47-- }}}
48
49-- {{{ function M.generate_browse_url
50function M.generate_browse_url(browse_type)
51 return string.format("browse/%s.html", browse_type)
52end
53-- }}}
54
55-- {{{ function M.generate_absolute_path
56function M.generate_absolute_path(relative_url, base_dir)
57 base_dir = base_dir or M.config.base_output_dir
58 return string.format("%s/%s", base_dir, relative_url)
59end
60-- }}}
61
62-- {{{ function M.generate_relative_navigation_path
63function M.generate_relative_navigation_path(from_path, to_path)
64 -- Calculate relative path from one location to another
65 local from_parts = {}
66 local to_parts = {}
67
68 for part in string.gmatch(from_path, "[^/]+") do
69 table.insert(from_parts, part)
70 end
71
72 for part in string.gmatch(to_path, "[^/]+") do
73 table.insert(to_parts, part)
74 end
75
76 -- Remove filename from from_path
77 if #from_parts > 0 and string.match(from_parts[#from_parts], "%.html$") then
78 table.remove(from_parts)
79 end
80
81 -- Calculate how many directories to go up
82 local levels_up = #from_parts
83 local relative_path = ""
84
85 for i = 1, levels_up do
86 relative_path = relative_path .. "../"
87 end
88
89 relative_path = relative_path .. to_path
90
91 return relative_path
92end
93-- }}}
94
95-- {{{ function M.create_directory_structure
96function M.create_directory_structure(base_output_dir)
97 base_output_dir = base_output_dir or M.config.base_output_dir
98
99 local directories = {
100 base_output_dir,
101 base_output_dir .. "/poems",
102 base_output_dir .. "/browse"
103 }
104
105 -- Add main category directories
106 for _, category in ipairs(M.config.main_categories) do
107 table.insert(directories, base_output_dir .. "/poems/" .. category)
108 end
109
110 -- Add special category directories
111 for _, category in ipairs(M.config.special_categories) do
112 table.insert(directories, base_output_dir .. "/poems/" .. category)
113 end
114
115 utils.log_info("Creating directory structure at: " .. base_output_dir)
116
117 for _, dir in ipairs(directories) do
118 local success = os.execute("mkdir -p \"" .. dir .. "\"")
119 if not success then
120 utils.log_error("Failed to create directory: " .. dir)
121 return false
122 end
123 utils.log_info("Created directory: " .. dir)
124 end
125
126 utils.log_info("Directory structure created successfully")
127 return true
128end
129-- }}}
130
131-- {{{ function M.get_poem_category
132function M.get_poem_category(poem)
133 if not poem then return "poems" end
134
135 -- Check if it's a golden poem and should go in golden category
136 if poem.is_fediverse_golden then
137 return "golden"
138 end
139
140 -- Use the poem's existing category
141 if poem.category then
142 return poem.category
143 end
144
145 -- Default fallback
146 return "poems"
147end
148-- }}}
149
150-- {{{ function M.generate_breadcrumb_data
151function M.generate_breadcrumb_data(current_poem, category)
152 category = category or M.get_poem_category(current_poem)
153
154 local breadcrumb_data = {
155 {
156 title = "Poetry Collection",
157 url = M.generate_relative_navigation_path(
158 M.generate_poem_url(current_poem.id or 1, category),
159 "index.html"
160 ),
161 is_link = true
162 },
163 {
164 title = string.format("%s Poems", category:gsub("^%l", string.upper)),
165 url = M.generate_relative_navigation_path(
166 M.generate_poem_url(current_poem.id or 1, category),
167 M.generate_category_index_url(category)
168 ),
169 is_link = true
170 },
171 {
172 title = current_poem.title or "Current Poem",
173 url = "",
174 is_link = false
175 }
176 }
177
178 return breadcrumb_data
179end
180-- }}}
181
182-- {{{ function M.get_directory_stats
183function M.get_directory_stats(base_dir)
184 base_dir = base_dir or M.config.base_output_dir
185
186 local stats = {
187 total_dirs = 0,
188 category_dirs = 0,
189 exists = false
190 }
191
192 -- Check if base directory exists
193 if utils.file_exists(base_dir) then
194 stats.exists = true
195
196 -- Count directories
197 local categories = {}
198 table.insert(categories, M.config.main_categories)
199 table.insert(categories, M.config.special_categories)
200
201 for _, category_list in ipairs(categories) do
202 for _, category in ipairs(category_list) do
203 local category_path = base_dir .. "/poems/" .. category
204 if utils.file_exists(category_path) then
205 stats.category_dirs = stats.category_dirs + 1
206 end
207 end
208 end
209
210 stats.total_dirs = stats.category_dirs + 2 -- poems + browse dirs
211 end
212
213 return stats
214end
215-- }}}
216
217-- {{{ function M.validate_url_structure
218function M.validate_url_structure(base_dir)
219 base_dir = base_dir or M.config.base_output_dir
220
221 local validation_results = {
222 valid = true,
223 errors = {},
224 warnings = {}
225 }
226
227 -- Check base directory
228 if not utils.file_exists(base_dir) then
229 table.insert(validation_results.errors, "Base directory does not exist: " .. base_dir)
230 validation_results.valid = false
231 return validation_results
232 end
233
234 -- Check required subdirectories
235 local required_dirs = {
236 base_dir .. "/poems",
237 base_dir .. "/browse"
238 }
239
240 for _, category in ipairs(M.config.main_categories) do
241 table.insert(required_dirs, base_dir .. "/poems/" .. category)
242 end
243
244 for _, category in ipairs(M.config.special_categories) do
245 table.insert(required_dirs, base_dir .. "/poems/" .. category)
246 end
247
248 for _, dir in ipairs(required_dirs) do
249 if not utils.file_exists(dir) then
250 table.insert(validation_results.errors, "Required directory missing: " .. dir)
251 validation_results.valid = false
252 end
253 end
254
255 return validation_results
256end
257-- }}}
258
259-- {{{ function M.test_url_generation
260function M.test_url_generation()
261 utils.log_info("Testing URL generation system...")
262
263 local tests = {
264 {
265 name = "Basic poem URL generation",
266 test = function()
267 local url = M.generate_poem_url(42, "fediverse")
268 return url == "poems/fediverse/poem-042.html"
269 end
270 },
271 {
272 name = "Golden poem URL generation",
273 test = function()
274 local url = M.generate_poem_url(123, "fediverse", {use_golden_category = true, is_golden = true})
275 return url == "poems/golden/poem-123.html"
276 end
277 },
278 {
279 name = "Category index URL generation",
280 test = function()
281 local url = M.generate_category_index_url("messages")
282 return url == "poems/messages/index.html"
283 end
284 },
285 {
286 name = "Browse URL generation",
287 test = function()
288 local url = M.generate_browse_url("by-similarity")
289 return url == "browse/by-similarity.html"
290 end
291 },
292 {
293 name = "Absolute path generation",
294 test = function()
295 local url = M.generate_absolute_path("poems/test/poem-001.html", "/test/base")
296 return url == "/test/base/poems/test/poem-001.html"
297 end
298 },
299 {
300 name = "Relative navigation calculation",
301 test = function()
302 local rel_path = M.generate_relative_navigation_path("poems/fediverse/poem-001.html", "index.html")
303 return rel_path == "../../index.html"
304 end
305 }
306 }
307
308 local passed = 0
309 local total = #tests
310
311 for _, test in ipairs(tests) do
312 local success = test.test()
313 if success then
314 utils.log_info("✅ " .. test.name .. " - PASSED")
315 passed = passed + 1
316 else
317 utils.log_error("❌ " .. test.name .. " - FAILED")
318 end
319 end
320
321 utils.log_info(string.format("URL generation tests: %d/%d passed", passed, total))
322 return passed == total
323end
324-- }}}
325
326-- {{{ function M.test_directory_creation
327function M.test_directory_creation()
328 utils.log_info("Testing directory structure creation...")
329
330 local test_dir = DIR .. "/test-generated-site"
331
332 -- Clean up any existing test directory
333 os.execute("rm -rf \"" .. test_dir .. "\"")
334
335 -- Test directory creation
336 local success = M.create_directory_structure(test_dir)
337 if not success then
338 utils.log_error("Directory creation failed")
339 return false
340 end
341
342 -- Validate created structure
343 local validation = M.validate_url_structure(test_dir)
344 if not validation.valid then
345 utils.log_error("Directory structure validation failed:")
346 for _, error in ipairs(validation.errors) do
347 utils.log_error(" - " .. error)
348 end
349 return false
350 end
351
352 -- Get statistics
353 local stats = M.get_directory_stats(test_dir)
354 utils.log_info(string.format("Created %d directories (%d category dirs)", stats.total_dirs, stats.category_dirs))
355
356 -- Clean up test directory
357 os.execute("rm -rf \"" .. test_dir .. "\"")
358
359 utils.log_info("Directory creation test PASSED")
360 return true
361end
362-- }}}
363
364-- {{{ function M.run_all_tests
365function M.run_all_tests()
366 utils.log_info("Running URL manager test suite...")
367
368 local url_test_passed = M.test_url_generation()
369 local dir_test_passed = M.test_directory_creation()
370
371 local all_passed = url_test_passed and dir_test_passed
372
373 if all_passed then
374 utils.log_info("🎉 All URL manager tests PASSED")
375 else
376 utils.log_error("❌ Some URL manager tests FAILED")
377 end
378
379 return all_passed
380end
381-- }}}
382
383return M