Commit Graph

98 Commits

Author SHA1 Message Date
Timothy DeHerrera
c8ddfb444a refactor: remove test redundancy, reduce API surface
- Remove duplicate xml_escape tests from sitemap.rs and feed.rs
  (already tested in escape.rs)
- Make html_escape_into private (only used internally)
2026-02-06 00:33:10 -07:00
Timothy DeHerrera
8e1f8cbb95 refactor(content): remove gray_matter, manual frontmatter
Implement extract_frontmatter (--- delimiters) and parse_frontmatter
(simple YAML key:value pairs). Handles taxonomies.tags for blog posts.
~70 lines replaces external dependency.
2026-02-05 23:59:53 -07:00
Timothy DeHerrera
8034e24f75 refactor(main): remove walkdir, use recursive std::fs
Add walk_dir/walk_dir_inner helpers (~20 lines) to replace walkdir
crate. Single call site in copy_static_assets didn't justify the
dependency.
2026-02-05 23:55:33 -07:00
Timothy DeHerrera
f1e3add292 chore: add .rustfmt.toml 2026-02-05 23:48:08 -07:00
Timothy DeHerrera
13e728de80 refactor(error): remove thiserror, implement Error manually
Manual Display and Error trait implementations replace derive macro.
Preserves source() chaining for all io::Error and tera::Error variants.
One less dependency, same behavior.
2026-02-05 23:46:14 -07:00
Timothy DeHerrera
8df8aa434f fix(render): fix code block rendering and quote escaping
Two issues fixed:

1. Language-less code blocks (``` without lang) were not accumulating
   text content. The guard `code_block_lang.is_some()` was false for
   them, so content fell through to regular text rendering.

   Fix: Add `in_code_block` flag to track code block state separately
   from language presence.

2. Single quotes in code blocks were being HTML-escaped as ',
   breaking CSP headers like 'self' in documentation.

   Fix: Create code_escape/code_escape_into in escape.rs that only
   escapes <, >, & (required to prevent HTML tag injection) but
   preserves quotes (safe inside <pre><code> content).

Rationale for code_escape:
- < and > MUST be escaped to prevent browser interpreting code as HTML
- & MUST be escaped to prevent HTML entity interpretation
- Quotes are safe inside element content (no attribute context)

Also:
- Add test for unlabeled code block quote preservation

All 71 tests pass.
2026-02-05 17:26:17 -07:00
Timothy DeHerrera
6638696dea fix(render): escape URLs in links and images to prevent XSS
Apply html_escape to:
- Link href and title attributes (start_tag_to_html)
- Image src attribute (Event::End TagEnd::Image handler)

Add test cases for:
- Quote-breaking URL attacks
- Link title escaping
- Image src escaping

Addresses HIGH severity finding from security audit.
All 70 tests pass.
2026-02-05 17:10:48 -07:00
Timothy DeHerrera
e4a6305a50 fix(escape): add single-quote escaping to html_escape
Add '\'' → '&#39;' case to html_escape_into for complete XSS
protection in HTML attribute contexts. Update documentation
and add test case.

Addresses LOW severity finding from security audit.
2026-02-05 17:07:54 -07:00
Timothy DeHerrera
c60c7851a8 refactor(api): restrict visibility of internal items
- content: DEFAULT_WEIGHT, DEFAULT_WEIGHT_HIGH → pub(crate)
- sitemap: SitemapEntry → pub(crate)

These items are implementation details not exposed to consumers.
2026-02-05 15:03:52 -07:00
Timothy DeHerrera
16f04eb95b refactor: consolidate escape functions and extract weight constants
- Create escape.rs with shared html_escape, html_escape_into, xml_escape
- Remove duplicate implementations from render.rs, highlight.rs, feed.rs, sitemap.rs
- Add DEFAULT_WEIGHT (50) and DEFAULT_WEIGHT_HIGH (99) constants to content.rs
- Replace all magic number weight defaults with named constants

No functional changes; all 67 tests pass.
2026-02-05 14:35:24 -07:00
Timothy DeHerrera
113b7e4a4c docs(themes): add syntax highlighting documentation and test coverage
- Update syntax-highlighting.md with tree-house integration details
- Add themes/README.md explaining copy-to-project workflow
- Add 13 tests: hierarchical scopes, injections (Nix+bash, MD, HTML)
- All 64 tests passing
2026-02-05 12:38:00 -07:00
Timothy DeHerrera
3f218ed49c chore: address clippy lints 2026-02-05 12:26:22 -07:00
Timothy DeHerrera
caf2d506a7 feat(themes): add decoupled CSS theme system with lightningcss bundling
- Add 6 syntax highlighting themes (dracula, gruvbox, nord, github)
- Rewrite css.rs to use lightningcss bundler for @import resolution
- Theme CSS is inlined at build time, producing single bundled output
2026-02-05 12:19:47 -07:00
Timothy DeHerrera
f9a978bdd6 feat(highlight): implement tree-house syntax highlighting engine
Replace tree-sitter-highlight with Helix's tree-house crate for
advanced syntax highlighting:

- Add tree-house and tree-house-bindings dependencies
- Implement SukrLoader with LanguageLoader trait
- Add hierarchical scope resolution with fallback
  (e.g., keyword.control.conditional → keyword.control → keyword)
- Create custom HTML renderer processing HighlightEvents
- Support all 14 languages with Helix queries
- Handle JS/TS inheritance chain (ecma → _javascript/_typescript)

Benefits:
- Richer semantic highlighting with ~75 scope classes
- Proper language injection support
- Hierarchical theme scopes matching Helix themes
- Foundation for converting Helix TOML themes to CSS
2026-02-05 12:04:53 -07:00
Timothy DeHerrera
ce8a9e8f00 feat(ux): add hover-reveal ¶ anchors, strip parens from nav
- Add pilcrow (¶) anchor link after heading text for deep-linking
- CSS: hidden by default, visible on heading hover, user-overridable
- Add strip_parens Tera filter for cleaner nav anchor labels
- Update test expectations for new heading format
2026-02-01 09:57:47 -07:00
Timothy DeHerrera
fc9e75d687 test(nav): add unit tests for anchor extraction and TOC fallback
- test_anchor_extraction: verify h2-h6 extracted, h1 skipped, IDs slugified
- test_slugify_edge_cases: verify special char handling, spaces, case
- test_toc_config_fallback: verify frontmatter override and config default
2026-02-01 09:48:13 -07:00
Timothy DeHerrera
c8c2506e16 feat(config): add global nav.toc setting with frontmatter override
- Add toc: bool to [nav] config section (default: false)
- Frontmatter toc: true/false overrides global config
- If frontmatter toc is not specified, falls back to config.nav.toc
- Enable nav.toc = true in docs/site.toml for global TOC
2026-02-01 09:42:00 -07:00
Timothy DeHerrera
dcc98dccef feat(nav): add anchor TOC in sidebar for toc-enabled pages
- Pass extracted anchors through template context
- Render anchor links under active page when page.toc is true
- Hierarchical indentation by heading level (h2-h6)
- CSS styling: smaller font, muted color, border-left indicator
- Add toc: true to templates.md as example
2026-02-01 09:37:01 -07:00
Timothy DeHerrera
a59b8ff2ab feat(render): add anchor extraction during markdown rendering
- Add Anchor struct with id, label, level fields
- markdown_to_html() now returns (String, Vec<Anchor>) tuple
- Headings h2-h6 are extracted with slugified IDs
- Add toc: bool frontmatter field for per-page TOC opt-in
- All heading tags now include id attributes for anchor links
2026-02-01 09:31:54 -07:00
Timothy DeHerrera
7f765b32e1 feat(nav): complete Tier 1 hierarchical navigation
Add config option [nav] nested (defaults false), update base.html
template with nested nav rendering, and add .nav-children CSS styling
for indented section children.

- Add NavConfig struct with nested: bool
- Template renders item.children in .nav-children div
- CSS: left border + indent for nested items
2026-02-01 09:09:41 -07:00
Timothy DeHerrera
b3a540651a feat(content): add nested children to NavItem for hierarchical nav
Extend NavItem struct with children field to support nested navigation.
discover_nav() now populates children for section items by collecting
section pages via Section::collect_items() and mapping them to child
NavItem entries. Children are sorted by weight then alphabetically.
2026-02-01 09:00:04 -07:00
Timothy DeHerrera
3752cc5234 fix(render): properly capture image alt text from markdown
pulldown-cmark emits alt text as Text events between Image start/end
tags. Previously, images were rendered immediately on the Start event
with empty alt text, losing the user-provided description.

Refactored to accumulate alt text using the same pattern as code block
handling: state variables track image attributes and alt content, then
the full <img> tag is rendered on the End event.

Also omits the title attribute entirely when no title is provided,
producing cleaner HTML output.

Fixes Lighthouse "Image elements have [alt] attributes" audit issue.
2026-01-31 22:50:05 -07:00
Timothy DeHerrera
922aeba834 refactor(main): use manifest.homepage in generate_homepage
Eliminate redundant Content::from_path call by using the pre-discovered
manifest.homepage content. Also uses manifest.nav directly.

- Removes dead_code warning on SiteManifest.homepage field
- Reduces I/O by reusing discovered content
- Simplifies function signature (4 args vs 5)
2026-01-31 22:18:49 -07:00
Timothy DeHerrera
759838e7f5 refactor(main): integrate SiteManifest for unified content discovery
Replace separate discover_nav + discover_sections calls with single
SiteManifest::discover() in run() pipeline:

- Update generate_feed() to accept &SiteManifest
- Update generate_sitemap_file() to accept &SiteManifest
- Simplify process_pages() to return Result<()>
- Remove redundant all_posts collection (now in manifest.posts)
- Remove unused discover_nav and discover_sections imports

All 49 tests pass, site builds identically.
2026-01-31 22:13:09 -07:00
Timothy DeHerrera
96aa60d9e4 refactor(content): add SiteManifest for unified content discovery
Introduce SiteManifest struct that aggregates all site content from a
single discovery pass:

- homepage: content/_index.md
- sections: directories with _index.md
- pages: top-level standalone .md files
- posts: blog section items (sorted by date for feed)
- nav: navigation menu items

Add discover_pages() helper and 5 unit tests covering homepage,
sections, pages, posts, and nav discovery.

Not yet integrated into main.rs pipeline.
2026-01-31 22:08:49 -07:00
Timothy DeHerrera
d24e4a0246 feat(sitemap): integrate sitemap generation into build pipeline
Wire generate_sitemap() into main.rs pipeline:

- Refactor process_pages() to return discovered pages
- Add generate_sitemap_file() helper function
- Generate sitemap.xml after homepage (Step 5 in pipeline)
- All 44 tests pass, sitemap contains 12 URLs

The sitemap includes homepage, section indices, section items,
and standalone pages following XML Sitemap 1.0 protocol.
2026-01-31 22:03:48 -07:00
Timothy DeHerrera
4d869a85f7 feat(sitemap): add XML sitemap generation module
Implement src/sitemap.rs with generate_sitemap() for SEO-compliant
XML sitemap generation. Follows the feed.rs pattern:

- SitemapEntry struct for URL metadata
- build_sitemap_xml() for XML construction
- xml_escape() for special character handling
- 5 unit tests covering single/multiple entries, lastmod, escaping

Module declared in main.rs but not yet integrated into pipeline.
2026-01-31 22:00:20 -07:00
Timothy DeHerrera
eaf09c1c7d fix: nrd.sh -> sukr 2026-01-31 21:50:14 -07:00
Timothy DeHerrera
ea9830f04b feat(highlight): add TOML syntax highlighting
Use tree-sitter-toml-ng v0.7.0 from tree-sitter-grammars, which is
compatible with tree-sitter 0.26 (unlike the older tree-sitter-toml).

- Add tree-sitter-toml-ng dependency
- Add Toml variant to Language enum
- Add TOML_CONFIG with crate's HIGHLIGHTS_QUERY
2026-01-31 17:27:53 -07:00
Timothy DeHerrera
905897b3c4 fix(highlight): complete markdown syntax highlighting with injections
Fix markdown code block highlighting to properly support both markdown
structure (headings, frontmatter) and language injection (rust, bash).

The key fix uses `#set! injection.include-children` in the injection
query to override tree-sitter-md's internal tokenization, enabling
proper highlighting of embedded languages within code fences.

Changes:
- Use crate's HIGHLIGHT_QUERY_BLOCK for base markdown highlighting
- Add custom injection query with include-children directive
- Add YAML/TOML frontmatter and HTML block injection rules
- Add text.* highlight names (title, literal, uri, reference)
- Add string.escape highlight name
- Add CSS styles for new highlight classes
- Remove unused custom md-highlights.scm
2026-01-31 17:24:27 -07:00
Timothy DeHerrera
8c54882118 feat(highlight): add markdown syntax highlighting with injection
Add tree-sitter-md for markdown parsing with injection support for
fenced code blocks. Code inside markdown code fences (```rust, ```bash,
etc.) is now fully syntax highlighted.

Key fix: Use `#set! injection.include-children` directive in the
injection query to override tree-sitter-md's internal tokenization
of code_fence_content, allowing proper language injection.

- Add tree-sitter-md v0.5.2 dependency
- Add Markdown variant to Language enum (md, markdown aliases)
- Create queries/md-highlights.scm (minimal markdown highlights)
- Create queries/md-injections.scm (with include-children directive)
- Add test: test_markdown_injection_rust
2026-01-31 16:41:11 -07:00
Timothy DeHerrera
69cd81621f feat(docs): create sukr documentation site with fixes
Self-documenting docs site built with sukr itself (dogfooding):

Core changes:
- Rename package from nrd-sh to sukr
- Move personal site to sites/nrd.sh/
- Update AGENTS.md and README.md

Documentation site (docs/):
- Add site.toml with sukr.io base URL
- Create docs-specific templates with sidebar navigation
- Add dark theme CSS with syntax highlighting colors
- Document all features: templates, sections, syntax highlighting,
  mermaid diagrams, and LaTeX math rendering

Bug fixes:
- Render individual pages for all sections (not just blog type)
- Add #[source] error chaining for Tera template errors
- Print full error chain in main() for better debugging
2026-01-31 16:14:04 -07:00
Timothy DeHerrera
8c806d1654 feat(docs): create sukr documentation site
Self-documenting docs site built with sukr itself (dogfooding):
- docs/site.toml with sukr.io base URL
- docs-specific templates with sidebar navigation
- Dark theme CSS, responsive layout
- Documentation: getting-started, configuration, features

Also: improved error chaining for better template debugging
2026-01-31 15:58:37 -07:00
Timothy DeHerrera
e1ee18ca8a feat: rename project from nrd-sh to sukr
sukr = suckless + Rust. Minimal static site compiler.

- Update Cargo.toml name and description
- Update main.rs USAGE and doc comments
- Update AGENTS.md with new name, Tera architecture
- Create README.md with quick start guide
2026-01-31 15:46:26 -07:00
Timothy DeHerrera
4c2c3d5495 feat(cli): add configurable paths and --config flag
Enable monorepo support with CLI configuration:

- Add PathsConfig struct with serde defaults for content, output,
  static, and templates directories
- Add optional [paths] table to site.toml (backward compatible)
- Add -c/--config <FILE> flag to specify config file path
- Add -h/--help flag with usage information
- Resolve all paths relative to config file location

Users can now run multiple sites from a single repo:

  nrd-sh                             # uses ./site.toml
  nrd-sh -c sites/blog/site.toml     # looks in sites/blog/

Includes 2 new unit tests for path configuration parsing.
2026-01-31 15:26:22 -07:00
Timothy DeHerrera
e200e94583 feat(templates): complete Tera migration, remove maud
Fully migrate from compile-time maud templates to runtime Tera:
- Rewrote main.rs to use TemplateEngine and discover_sections()
- Replaced hardcoded blog/projects with generic section loop
- Added Clone derive to Frontmatter and Content
- Fixed section_type dispatch via Section struct
- Deleted src/templates.rs, removed maud dependency

Users can now add sections without code changes.
2026-01-31 15:10:39 -07:00
Timothy DeHerrera
244b0ce85b feat(content): add Section struct and discover_sections()
Enable generic section processing by adding:
- Section struct with index, name, section_type, path
- Section::collect_items() to gather content in a section
- discover_sections() to find all directories with _index.md

Section type is determined from frontmatter `section_type` field,
falling back to directory name. Includes 5 unit tests.
2026-01-31 15:02:20 -07:00
Timothy DeHerrera
3df7fda26a feat(templates): add Tera runtime template engine
Lay groundwork for user-editable templates by adding Tera as a
runtime template engine alongside the existing maud templates.

Changes:
- Add tera dependency
- Create TemplateEngine struct with render methods
- Add TemplateLoad/TemplateRender error variants
- Add section_type/template fields to Frontmatter
- Create templates/ directory with base, page, section, and content templates

Dead code warnings are expected; TemplateEngine will be wired
in to replace maud in subsequent commits.
2026-01-31 14:59:49 -07:00
Timothy DeHerrera
1bf265f14b fix(feed): derive URLs from content paths
Replace hardcoded "/blog/{slug}.html" URL pattern with dynamic
path derivation using Content.output_path(). This ensures feed
URLs work correctly for any content location, not just blog posts.
2026-01-31 13:59:05 -07:00
Timothy DeHerrera
a5c56c2b2f feat(templates,main): wire dynamic nav through pipeline
Update all template functions to accept nav parameter and iterate
over discovered NavItem slice instead of hardcoded links.

Refactor main.rs:
- Call discover_nav() early in run()
- Thread nav to all template renders
- Replace hardcoded page list with dynamic discovery

Navigation is now fully driven by filesystem structure.
2026-01-31 13:56:16 -07:00
Timothy DeHerrera
b978edf4f2 feat(content): add filesystem-driven nav discovery
Add NavItem struct and discover_nav() function to scan content
directory and automatically build navigation from:
- Top-level .md files (pages)
- Directories with _index.md (sections)

Navigation is sorted by frontmatter weight, then alphabetically.
Custom nav_label field allows overriding title in nav menu.

Includes 5 unit tests covering page/section discovery, weight
ordering, and nav_label support.
2026-01-31 13:52:21 -07:00
Timothy DeHerrera
d417e1c535 feat(mermaid): integrate diagram rendering into markdown pipeline
Intercept 'mermaid' code blocks in render.rs and call
mermaid::render_diagram() to convert to inline SVG.

- Use catch_unwind to handle upstream dagre_rust panics gracefully
- Graceful fallback: show raw code with mermaid-error class on failure
- Flowcharts render correctly; some diagram types hit upstream bugs
2026-01-28 20:36:45 -07:00
Timothy DeHerrera
abe465723c feat(mermaid): add mermaid-rs-renderer diagram module
Add src/mermaid.rs with render_diagram() wrapper around
mermaid-rs-renderer for build-time Mermaid-to-SVG conversion.

- Use mermaid-rs-renderer git dependency (SVG-only, no PNG)
- Configure with RenderOptions::modern() theme
- Include unit tests for flowchart and sequence diagrams
2026-01-28 20:31:40 -07:00
Timothy DeHerrera
eeec4a999b feat(render): integrate katex-rs math rendering
Enable ENABLE_MATH in pulldown-cmark options and implement
InlineMath/DisplayMath event handlers in the render pipeline.

- Inline math ($...$) renders via render_math(_, false)
- Display math ($$...$$) wrapped in <div class="math-display">
- Graceful degradation: errors logged to stderr, raw LaTeX shown
  with class="math-error" for styling
2026-01-28 20:18:36 -07:00
Timothy DeHerrera
ebe1fd3b6e feat(math): add katex-rs math rendering module
Add src/math.rs with render_math() wrapper around katex-rs for
build-time LaTeX to HTML conversion. Wire module into main.rs.

- Use KatexContext::default() for standard LaTeX function registry
- Settings: display_mode toggle, throw_on_error=false for graceful
  degradation
- Include unit tests for inline/display math rendering
2026-01-28 20:16:25 -07:00
Timothy DeHerrera
a7338f5418 feat: add Nix injection queries for embedded language highlighting
Adapted Helix nix/injections.scm for tree-sitter-highlight:
- Removed Helix-specific predicates
- Recognizes bash in buildPhase/installPhase, python in testScript,
  json in fromJSON, etc.
2026-01-26 23:54:59 -07:00
Timothy DeHerrera
b16246fba6 feat: use custom Nix highlight queries for richer syntax colors
- queries/nix-highlights.scm: Adapted from Helix editor queries.
  Properly identifies functions, properties, variable parameters,
  escape sequences, and string interpolation. Much richer than
  the bundled tree-sitter-nix query (99 lines vs 113 lines).

- src/highlight.rs: Load custom Nix query via include_str!
  instead of using bundled HIGHLIGHTS_QUERY.

- static/style.css: Fix variable (#e36209) and punctuation (#586069)
  colors to be distinct from text color.

Results: hl-function now appears (6 occurrences), hl-property
visible (17), hl-variable reduced from 43 to 17. Functions like
mkShell now render in purple/blue instead of orange.
2026-01-25 20:18:48 -07:00
Timothy DeHerrera
5b7805d753 fix: expand highlight captures for better Nix support
- src/highlight.rs: Add 7 new captures to HIGHLIGHT_NAMES:
  embedded, escape, function, punctuation.special, string.special,
  string.special.path, string.special.uri. Now 27 total captures.

- static/style.css: Add CSS variables and class rules for new
  captures in both light and dark themes. escape gets bold
  styling, string-special uses distinct color.

These captures are used by tree-sitter-nix but were previously
missing, causing sparse highlighting. Now functions, escapes,
and paths are properly highlighted.
2026-01-25 17:29:18 -07:00
Timothy DeHerrera
acb0ff3e15 feat: add syntax highlighting for 9 additional languages
- Cargo.toml: Add tree-sitter grammars for Nix, Python, JavaScript,
  TypeScript, Go, C, CSS, HTML, YAML. Upgrade tree-sitter-highlight
  to 0.26 for language version 15 compatibility.

- src/highlight.rs: Add Language enum variants and get_config()
  match arms for all new languages. Update render() callback for
  0.26 API (writes attributes to buffer). Add tests for Nix and
  Python highlighting.

TOML excluded due to incompatible API (tree-sitter 0.20 vs 0.26).
2026-01-25 17:20:00 -07:00
Timothy DeHerrera
51b947256d feat: add Atom feed generation
- src/feed.rs: New module with generate_atom_feed() that produces
  Atom 1.0 XML from blog posts. Uses config.base_url for absolute
  entry URLs. Includes xml_escape() helper.

- src/main.rs: Wire mod feed and call generate_feed() after blog
  index generation. Outputs to public/feed.xml.

- src/templates.rs: Add <link rel="alternate" type="application/atom+xml">
  autodiscovery link to page head using config.base_url.

Feed includes title, author, updated timestamp, and entries with
title, link, id, updated, and summary for each blog post.
2026-01-24 22:01:59 -07:00