rs_web/lib.rs
1//! # rs-web - Static Site Generator
2//!
3//! A fast, opinionated static site generator built in Rust with support for:
4//!
5//! - **Markdown processing** with syntax highlighting, external link handling
6//! - **Content encryption** via Lua (`rs.crypt` module)
7//! - **Link graph** with backlinks and visualization (Obsidian-style)
8//! - **RSS feed** generation with section filtering
9//! - **Parallel processing** for fast builds
10//! - **Live reload** with automatic browser refresh during watch mode
11//! - **Asset minification** for CSS, JS (with dead code elimination), and HTML
12//!
13//! ## Quick Start
14//!
15//! ```bash
16//! # Build the site
17//! rs-web build
18//!
19//! # Build to custom output directory
20//! rs-web build --output public
21//!
22//! # Watch for changes and rebuild incrementally with live reload
23//! rs-web build --watch
24//!
25//! # Watch mode with custom port
26//! rs-web build --watch --port 8080
27//!
28//! # Enable debug logging
29//! rs-web --debug build
30//!
31//! # Set specific log level (trace, debug, info, warning, error)
32//! rs-web --log-level trace build
33//!
34//! # Or use environment variable
35//! RS_WEB_LOG_LEVEL=debug rs-web build
36//! ```
37//!
38//! ## Configuration
39//!
40//! Configure via `config.lua`:
41//!
42//! ```lua
43//! return {
44//! site = {
45//! title = "My Site",
46//! description = "Site description",
47//! base_url = "https://example.com",
48//! author = "Your Name",
49//! },
50//!
51//! build = {
52//! output_dir = "dist",
53//! },
54//!
55//! -- Generate pages via Lua
56//! pages = function()
57//! return {
58//! { path = "/", template = "home.html", title = "Home" },
59//! { path = "/about/", template = "page.html", title = "About", minify = true },
60//! }
61//! end,
62//!
63//! -- Build hooks
64//! hooks = {
65//! before_build = function() print("Starting...") end,
66//! after_build = function() print("Done!") end,
67//! },
68//! }
69//! ```
70//!
71//! ### Configuration Sections
72//!
73//! - `site` - Required: title, description, base_url, author
74//! - `seo` - twitter_handle, default_og_image
75//! - `build` - output_dir
76//! - `paths` - templates
77//!
78//! ### Lua Sandbox
79//!
80//! By default, file operations are sandboxed to the project directory.
81//! To disable (use with caution):
82//!
83//! ```lua
84//! return {
85//! lua = { sandbox = false },
86//! site = { ... },
87//! }
88//! ```
89//!
90//! ### Lua API Functions
91//!
92//! **File Operations:**
93//! - `read_file(path)` - Read file contents
94//! - `write_file(path, content)` - Write content to file
95//! - `copy_file(src, dest)` - Copy file (binary-safe)
96//! - `file_exists(path)` - Check if file exists
97//! - `list_files(path, pattern?)` - List files matching pattern
98//! - `list_dirs(path)` - List subdirectories
99//! - `load_json(path)` - Load and parse JSON file
100//! - `load_yaml(path)` - Load and parse YAML file
101//! - `load_toml(path)` - Load and parse TOML file
102//! - `read_frontmatter(path)` - Extract frontmatter and content from markdown
103//!
104//! **Content Processing:**
105//! - `render_markdown(content, transform_fn?)` - Convert markdown to HTML
106//! - `html_to_text(html)` - Convert HTML to plain text
107//! - `rss_date(date_string)` - Format date for RSS (RFC 2822)
108//!
109//! **Image Processing:**
110//! - `image_dimensions(path)` - Get image width and height
111//! - `image_resize(input, output, options)` - Resize image
112//! - `image_convert(input, output, options?)` - Convert image format
113//! - `image_optimize(input, output, options?)` - Optimize/compress image
114//!
115//! **Asset Building:**
116//! - `build_css(pattern, output, options?)` - Build and concatenate CSS files (async)
117//! - `build_js(pattern, output, options?)` - Build and concatenate JS files with minification and DCE (async)
118//! - `download_google_font(family, options)` - Download Google Font with optional CSS minification (async)
119//!
120//! **Text Processing:**
121//! - `slugify(text)` - Convert text to URL-friendly slug
122//! - `word_count(text)` - Count words in text
123//! - `reading_time(text, wpm?)` - Calculate reading time in minutes
124//! - `truncate(text, len, suffix?)` - Truncate text with optional suffix
125//! - `strip_tags(html)` - Remove HTML tags
126//! - `format_date(date, format)` - Format a date string
127//! - `parse_date(str)` - Parse date string to table {year, month, day}
128//! - `hash(content)` - Hash content (xxHash64)
129//! - `hash_file(path)` - Hash file contents
130//! - `url_encode(str)` - URL encode a string
131//! - `url_decode(str)` - URL decode a string
132//!
133//! **Path Utilities:**
134//! - `join_path(...)` - Join path segments
135//! - `basename(path)` - Get file name from path
136//! - `dirname(path)` - Get directory from path
137//! - `extension(path)` - Get file extension
138//!
139//! **Collections:**
140//! - `filter(items, fn)` - Filter items where fn returns true
141//! - `sort(items, fn)` - Sort items using comparator
142//! - `map(items, fn)` - Transform each item
143//! - `find(items, fn)` - Find first item where fn returns true
144//! - `group_by(items, key_fn)` - Group items by key
145//! - `unique(items)` - Remove duplicates
146//! - `reverse(items)` - Reverse array order
147//! - `take(items, n)` - Take first n items
148//! - `skip(items, n)` - Skip first n items
149//! - `keys(table)` - Get all keys from a table
150//! - `values(table)` - Get all values from a table
151//!
152//! **Environment:**
153//! - `env(name)` - Get environment variable
154//! - `print(...)` - Log output to build log
155//! - `git_info(path?)` - Get git info (hash, branch, author, date, dirty)
156//!
157//! **Parallel (rs.parallel):** Rayon-backed parallel operations
158//! - `load_json(paths)` / `load_yaml(paths)` - Load multiple files in parallel
159//! - `read_files(paths)` / `read_frontmatter(paths)` - Read multiple files
160//! - `create_dirs(paths)` / `copy_files(sources, dests)` - Parallel file operations
161//! - `image_convert(sources, dests, opts?)` - Convert images in parallel
162//!
163//! **Async I/O (rs.async):** All return handles, await with `rs.async.await(task)` or `rs.async.await_all(tasks)`
164//! - `fetch(url, opts?)` / `fetch_bytes(url, opts?)` - HTTP fetch (text/binary)
165//! - `fetch_sync(url, opts?)` - Blocking fetch (returns response directly)
166//! - `write_file(path, content)` / `write(path, bytes)` - Write text/binary
167//! - `copy_file(src, dst)` / `create_dir(path)` - File/dir operations
168//! - `exists(path)` / `read_file(path)` - Check existence / read file
169//!
170//! **Encryption (rs.crypt):**
171//! - `encrypt(content, password?)` - Encrypt content (AES-256-GCM)
172//! - `decrypt(data, password?)` - Decrypt content
173//! - `encrypt_html(content, options?)` - Generate encrypted HTML block for browser decryption
174//!
175//! All file operations respect the sandbox setting and are tracked for incremental builds.
176//! Encryption uses SITE_PASSWORD environment variable if password is not provided.
177//!
178//! ## Frontmatter
179//!
180//! Post frontmatter options (YAML or TOML):
181//!
182//! ```yaml
183//! ---
184//! title: "Post Title" # Required
185//! description: "Description" # Optional
186//! date: 2024-01-15 # Optional (YAML date or string)
187//! tags: ["tag1", "tag2"] # Optional
188//! draft: false # Optional (default: false, excluded from build)
189//! image: "/static/post.png" # Optional: OG image
190//! template: "custom.html" # Optional: Override template
191//! slug: "custom-slug" # Optional: Override URL slug
192//! permalink: "/custom/url/" # Optional: Full URL override
193//! ---
194//! ```
195//!
196//! ## Encryption
197//!
198//! Encryption is handled via the `rs.crypt` module in Lua. Use `SITE_PASSWORD`
199//! environment variable or pass password explicitly:
200//!
201//! ```lua
202//! -- Encrypt content
203//! local encrypted = rs.crypt.encrypt("secret content")
204//! -- Returns: { ciphertext, salt, nonce }
205//!
206//! -- Generate encrypted HTML block for browser decryption
207//! local html = rs.crypt.encrypt_html("secret content", {
208//! slug = "post-slug",
209//! block_id = "secret-1",
210//! })
211//!
212//! -- Decrypt content
213//! local plaintext = rs.crypt.decrypt(encrypted)
214//! ```
215//!
216//! ## Template Variables
217//!
218//! ### Home Template (`home.html`)
219//! - `site` - Site config (title, description, base_url, author)
220//! - `page` - Page info (title, description, url, image)
221//! - `sections` - All sections with posts (`sections.blog.posts`)
222//! - `content` - Rendered markdown content
223//!
224//! ### Post Template (`post.html`)
225//! - `site` - Site config
226//! - `post` - Post info (title, url, date, tags, reading_time, etc.)
227//! - `page` - Page info for head.html compatibility
228//! - `content` - Rendered markdown content
229//! - `backlinks` - Posts linking to this post (url, title, section)
230//! - `graph` - Local graph data (nodes, edges) for visualization
231//!
232//! ### Graph Template (`graph.html`)
233//! - `site` - Site config
234//! - `page` - Page info
235//! - `graph` - Full graph data (nodes, edges)
236//!
237//! ## Modules
238//!
239//! - [`config`] - Configuration loading and structures
240//! - [`markdown`] - Markdown processing pipeline
241//! - [`templates`] - Tera template rendering
242//! - [`encryption`] - AES-256-GCM encryption utilities (used by `rs.crypt` Lua module)
243//! - [`build`] - Main build orchestrator
244
245/// Print output at info level (always shown regardless of log level)
246#[macro_export]
247macro_rules! rs_print {
248 ($($arg:tt)*) => {
249 log::info!(target: "rs_print", "{}", format!($($arg)*))
250 };
251}
252
253pub mod assets;
254pub mod build;
255pub mod config;
256pub mod data;
257pub mod encryption;
258pub mod git;
259pub mod lua;
260pub mod markdown;
261pub mod server;
262pub mod templates;
263pub mod text;
264pub mod tracker;
265pub mod watch;