1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
//! # rs-web - Static Site Generator
//!
//! A fast, opinionated static site generator built in Rust with support for:
//!
//! - **Markdown processing** with syntax highlighting, external link handling
//! - **Content encryption** via Lua (`rs.crypt` module)
//! - **Link graph** with backlinks and visualization (Obsidian-style)
//! - **RSS feed** generation with section filtering
//! - **Parallel processing** for fast builds
//! - **Live reload** with automatic browser refresh during watch mode
//! - **Asset minification** for CSS, JS (with dead code elimination), and HTML
//!
//! ## Quick Start
//!
//! ```bash
//! # Build the site
//! rs-web build
//!
//! # Build to custom output directory
//! rs-web build --output public
//!
//! # Watch for changes and rebuild incrementally with live reload
//! rs-web build --watch
//!
//! # Watch mode with custom port
//! rs-web build --watch --port 8080
//!
//! # Enable debug logging
//! rs-web --debug build
//!
//! # Set specific log level (trace, debug, info, warning, error)
//! rs-web --log-level trace build
//!
//! # Or use environment variable
//! RS_WEB_LOG_LEVEL=debug rs-web build
//! ```
//!
//! ## Configuration
//!
//! Configure via `config.lua`:
//!
//! ```lua
//! return {
//! site = {
//! title = "My Site",
//! description = "Site description",
//! base_url = "https://example.com",
//! author = "Your Name",
//! },
//!
//! build = {
//! output_dir = "dist",
//! },
//!
//! -- Generate pages via Lua
//! pages = function()
//! return {
//! { path = "/", template = "home.html", title = "Home" },
//! { path = "/about/", template = "page.html", title = "About", minify = true },
//! }
//! end,
//!
//! -- Build hooks
//! hooks = {
//! before_build = function() print("Starting...") end,
//! after_build = function() print("Done!") end,
//! },
//! }
//! ```
//!
//! ### Configuration Sections
//!
//! - `site` - Required: title, description, base_url, author
//! - `seo` - twitter_handle, default_og_image
//! - `build` - output_dir
//! - `paths` - templates
//!
//! ### Lua Sandbox
//!
//! By default, file operations are sandboxed to the project directory.
//! To disable (use with caution):
//!
//! ```lua
//! return {
//! lua = { sandbox = false },
//! site = { ... },
//! }
//! ```
//!
//! ### Lua API Functions
//!
//! **File Operations:**
//! - `read_file(path)` - Read file contents
//! - `write_file(path, content)` - Write content to file
//! - `copy_file(src, dest)` - Copy file (binary-safe)
//! - `file_exists(path)` - Check if file exists
//! - `list_files(path, pattern?)` - List files matching pattern
//! - `list_dirs(path)` - List subdirectories
//! - `load_json(path)` - Load and parse JSON file
//! - `load_yaml(path)` - Load and parse YAML file
//! - `load_toml(path)` - Load and parse TOML file
//! - `read_frontmatter(path)` - Extract frontmatter and content from markdown
//!
//! **Content Processing:**
//! - `html_to_text(html)` - Convert HTML to plain text
//!
//! **Date & Time (rs.date):**
//! - `now()` - Get current Unix timestamp
//! - `from_timestamp(ts)` - Convert Unix timestamp to DateTime table
//! - `to_timestamp(date)` - Convert date to Unix timestamp
//! - `format(date, format)` - Format date using strftime
//! - `parse(str, format?)` - Parse date string (auto-detects or custom format)
//! - `rss_format(date)` - Format for RSS (RFC 2822)
//! - `iso_format(date)` - Format as ISO 8601
//! - `add(date, delta)` - Add time to date
//! - `diff(date1, date2)` - Difference in seconds
//!
//! **Markdown (rs.markdown):**
//! - `render(content, opts?)` - Render markdown to HTML with optional plugins
//! - `plugins(...)` - Combine/flatten plugins
//! - `plugins.default(opts?)` - Get default plugins (lazy_images, heading_anchors, external_links)
//! - `plugins.lazy_images(opts?)` - Plugin: add loading="lazy" decoding="async" to images
//! - `plugins.heading_anchors(opts?)` - Plugin: add id="slug" to headings
//! - `plugins.external_links(opts?)` - Plugin: add target="_blank" rel="noopener" to external links
//!
//! **Image Processing:**
//! - `image_dimensions(path)` - Get image width and height
//! - `image_resize(input, output, options)` - Resize image
//! - `image_convert(input, output, options?)` - Convert image format
//! - `image_optimize(input, output, options?)` - Optimize/compress image
//!
//! **Fonts (rs.fonts):**
//! - `download_google_font(family, options)` - Download Google Font with optional CSS minification (async)
//!
//! **JS Module (rs.js):**
//! - `concat(paths, output, options?)` - Concatenate JS files with optional minification (async)
//! - `bundle(entry, output, options?)` - Bundle JS with imports via Rolldown (async)
//! - `bundle_many(entries, output_dir, options?)` - Bundle multiple JS entries to separate files (async)
//!
//! **CSS Module (rs.css):**
//! - `concat(paths, output, options?)` - Concatenate CSS files with optional minification (async)
//! - `bundle(paths, output, options?)` - Bundle CSS with @import resolution via LightningCSS (async)
//! - `bundle_many(paths, output_dir, options?)` - Bundle multiple CSS entries to separate files (async)
//! - `purge(css_path, options?)` - Remove unused CSS rules based on HTML/JS output (async, call in after_build)
//! - `critical(html, css_path, options?)` - Extract critical CSS for a specific HTML page (async)
//! - `inline_critical(html_path, css_path, options?)` - Inline critical CSS into HTML with async loading (async)
//!
//! **Asset Hashing (rs.assets):**
//! - `hash(content, length?)` - Compute SHA256 hash of content (async)
//! - `hash_sync(content, length?)` - Compute hash synchronously
//! - `write_hashed(content, path, options?)` - Write file with hashed filename (async)
//! - `register(original, hashed)` - Register asset path mapping
//! - `get_path(path)` - Get hashed path for original
//! - `manifest()` - Get all path mappings
//! - `clear()` - Clear the manifest
//!
//! **PWA (rs.pwa):**
//! - `manifest(options)` - Generate web app manifest.json (async)
//! - `service_worker(options)` - Generate service worker sw.js (async)
//!
//! **SEO (rs.seo):**
//! - `sitemap(options)` - Generate XML sitemap (async)
//! - `robots(options)` - Generate robots.txt (async)
//!
//! **Text Processing:**
//! - `slugify(text)` - Convert text to URL-friendly slug
//! - `word_count(text)` - Count words in text
//! - `reading_time(text, wpm?)` - Calculate reading time in minutes
//! - `truncate(text, len, suffix?)` - Truncate text with optional suffix
//! - `strip_tags(html)` - Remove HTML tags
//! - `hash(content)` - Hash content (xxHash64)
//! - `hash_file(path)` - Hash file contents
//! - `url_encode(str)` - URL encode a string
//! - `url_decode(str)` - URL decode a string
//!
//! **Path Utilities:**
//! - `join_path(...)` - Join path segments
//! - `basename(path)` - Get file name from path
//! - `dirname(path)` - Get directory from path
//! - `extension(path)` - Get file extension
//!
//! **Collections:**
//! - `filter(items, fn)` - Filter items where fn returns true
//! - `sort(items, fn)` - Sort items using comparator
//! - `map(items, fn)` - Transform each item
//! - `find(items, fn)` - Find first item where fn returns true
//! - `group_by(items, key_fn)` - Group items by key
//! - `unique(items)` - Remove duplicates
//! - `reverse(items)` - Reverse array order
//! - `take(items, n)` - Take first n items
//! - `skip(items, n)` - Skip first n items
//! - `keys(table)` - Get all keys from a table
//! - `values(table)` - Get all values from a table
//!
//! **Environment:**
//! - `env(name, default?)` - Get environment variable with optional default
//! - `print(...)` - Log output to build log
//! - `git_info(path?)` - Get git info (hash, branch, author, timestamp, dirty)
//!
//! **Parallel (rs.parallel):** Rayon-backed true parallel operations
//! - `load_json(paths)` / `load_yaml(paths)` - Load multiple files in parallel
//! - `read_files(paths)` / `read_frontmatter(paths)` - Read multiple files
//! - `create_dirs(paths)` / `copy_files(sources, dests)` - Parallel file operations
//! - `image_convert(sources, dests, opts?)` - Convert images in parallel
//! - `map(items, fn, ctx?)` / `filter(items, fn, ctx?)` - Parallel map/filter with explicit context
//! - `map_seq(items, fn)` / `filter_seq(items, fn)` - Sequential fallbacks for non-serializable items
//!
//! **Async I/O (rs.async):** All return handles, await with `rs.async.await(task)` or `rs.async.await_all(tasks)`
//! - `fetch(url, opts?)` / `fetch_bytes(url, opts?)` - HTTP fetch (text/binary)
//! - `fetch_sync(url, opts?)` - Blocking fetch (returns response directly)
//! - `write_file(path, content)` / `write(path, bytes)` - Write text/binary
//! - `copy_file(src, dst)` / `create_dir(path)` - File/dir operations
//! - `exists(path)` / `read_file(path)` - Check existence / read file
//!
//! **Encryption (rs.crypt):**
//! - `encrypt(content, password?)` - Encrypt content (AES-256-GCM)
//! - `decrypt(data, password?)` - Decrypt content
//! - `encrypt_html(content, options?)` - Generate encrypted HTML block for browser decryption
//!
//! All file operations respect the sandbox setting and are tracked for incremental builds.
//! Encryption uses SITE_PASSWORD environment variable if password is not provided.
//!
//! ## Frontmatter
//!
//! Post frontmatter options (YAML or TOML):
//!
//! ```yaml
//! ---
//! title: "Post Title" # Required
//! description: "Description" # Optional
//! date: 2024-01-15 # Optional (YAML date or string)
//! tags: ["tag1", "tag2"] # Optional
//! draft: false # Optional (default: false, excluded from build)
//! image: "/static/post.png" # Optional: OG image
//! template: "custom.html" # Optional: Override template
//! slug: "custom-slug" # Optional: Override URL slug
//! permalink: "/custom/url/" # Optional: Full URL override
//! ---
//! ```
//!
//! ## Encryption
//!
//! Encryption is handled via the `rs.crypt` module in Lua. Use `SITE_PASSWORD`
//! environment variable or pass password explicitly:
//!
//! ```lua
//! -- Encrypt content
//! local encrypted = rs.crypt.encrypt("secret content")
//! -- Returns: { ciphertext, salt, nonce }
//!
//! -- Generate encrypted HTML block for browser decryption
//! local html = rs.crypt.encrypt_html("secret content", {
//! slug = "post-slug",
//! block_id = "secret-1",
//! })
//!
//! -- Decrypt content
//! local plaintext = rs.crypt.decrypt(encrypted)
//! ```
//!
//! ## Template Variables
//!
//! ### Home Template (`home.html`)
//! - `site` - Site config (title, description, base_url, author)
//! - `page` - Page info (title, description, url, image)
//! - `sections` - All sections with posts (`sections.blog.posts`)
//! - `content` - Rendered markdown content
//!
//! ### Post Template (`post.html`)
//! - `site` - Site config
//! - `post` - Post info (title, url, date, tags, reading_time, etc.)
//! - `page` - Page info for head.html compatibility
//! - `content` - Rendered markdown content
//! - `backlinks` - Posts linking to this post (url, title, section)
//! - `graph` - Local graph data (nodes, edges) for visualization
//!
//! ### Graph Template (`graph.html`)
//! - `site` - Site config
//! - `page` - Page info
//! - `graph` - Full graph data (nodes, edges)
//!
//! ## Modules
//!
//! - [`config`] - Configuration loading and structures
//! - [`lua`] - Lua API including `rs.markdown` for markdown processing
//! - [`templates`] - Tera template rendering
//! - [`encryption`] - AES-256-GCM encryption utilities (used by `rs.crypt` Lua module)
//! - [`build`] - Main build orchestrator
/// Print output at info level (always shown regardless of log level)