{"id":35216690,"url":"https://github.com/santosr2/luma","last_synced_at":"2026-01-13T21:39:14.563Z","repository":{"id":327979312,"uuid":"1109883975","full_name":"santosr2/luma","owner":"santosr2","description":"A language-agnostic, Lua-powered templating engine with clean, directive-based syntax.","archived":false,"fork":false,"pushed_at":"2026-01-06T21:13:27.000Z","size":1062,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-08T22:27:35.244Z","etag":null,"topics":["golang","helm","html","jinja2","json","lua","nodejs","python","template","template-engine","wasm","yaml"],"latest_commit_sha":null,"homepage":"https://santosr2.github.io/luma/","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/santosr2.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-04T12:17:54.000Z","updated_at":"2026-01-06T21:13:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/santosr2/luma","commit_stats":null,"previous_names":["santosr2/luma"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/santosr2/luma","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santosr2%2Fluma","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santosr2%2Fluma/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santosr2%2Fluma/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santosr2%2Fluma/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/santosr2","download_url":"https://codeload.github.com/santosr2/luma/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/santosr2%2Fluma/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28401057,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","helm","html","jinja2","json","lua","nodejs","python","template","template-engine","wasm","yaml"],"created_at":"2025-12-29T22:30:10.600Z","updated_at":"2026-01-13T21:39:14.557Z","avatar_url":"https://github.com/santosr2.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Luma\n\n[![CI Status](https://github.com/santosr2/luma/workflows/CI/badge.svg)](https://github.com/santosr2/luma/actions)\n[![codecov](https://codecov.io/gh/santosr2/luma/branch/main/graph/badge.svg)](https://codecov.io/gh/santosr2/luma)\n[![LuaRocks](https://img.shields.io/luarocks/v/santosr2/luma)](https://luarocks.org/modules/santosr2/luma)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Lua](https://img.shields.io/badge/lua-5.1%20%7C%205.2%20%7C%205.3%20%7C%205.4%20%7C%20JIT-blue)](https://www.lua.org)\n\nA language-agnostic, Lua-powered templating engine with clean, directive-based syntax.\n\nLuma is designed as a modern alternative to Jinja2-style templating with:\n\n- Less punctuation noise\n- Line-based directives\n- Familiar `$variable` interpolation (like shell/JS)\n- Pure Lua implementation for maximum portability\n\n## Installation\n\n```bash\n# Using LuaRocks (coming soon)\nluarocks install luma\n\n# Or clone and use directly\ngit clone https://github.com/santosr2/luma_templates\n```\n\n## Quick Start\n\n```lua\nlocal luma = require(\"luma\")\n\n-- Simple rendering\nlocal result = luma.render(\"Hello, $name!\", { name = \"World\" })\n-- Output: \"Hello, World!\"\n\n-- Compile for reuse\nlocal template = luma.compile(\"Hello, $name!\")\nprint(template:render({ name = \"Alice\" }))\nprint(template:render({ name = \"Bob\" }))\n```\n\n## Syntax\n\n### Interpolation\n\n```text\n$var                    -- Simple variable\n$user.name              -- Member access\n${expr}                 -- Expression with full features\n${name | upper}         -- With filter\n${val | default(\"N/A\")} -- With filter and args\n$$                      -- Escaped $ (literal $)\n```\n\n### Directives\n\nDirectives start with `@` at the beginning of a line (after optional indentation):\n\n```text\n@if condition\n  Content when true\n@elif other_condition\n  Content for elif\n@else\n  Content when false\n@end\n\n@for item in items\n  - $item\n@else\n  No items found\n@end\n\n@let total = price * quantity\n\n@# This is a comment (not rendered)\n```\n\nDirectives can be indented to match your file structure (great for YAML/configs):\n\n```yaml\nspec:\n  containers:\n  @for container in containers\n    - name: $container.name\n      @if container.ports\n      ports:\n        @for port in container.ports\n        - containerPort: $port\n        @end\n      @end\n  @end\n```\n\n### Filters\n\n```lua\n-- String filters\n${name | upper}           -- Uppercase\n${name | lower}           -- Lowercase\n${name | capitalize}      -- Capitalize first letter\n${name | title}           -- Title Case\n${text | trim}            -- Remove whitespace\n\n-- Collection filters\n${items | first}          -- First element\n${items | last}           -- Last element\n${items | length}         -- Count\n${items | join(\", \")}     -- Join with separator\n${items | reverse}        -- Reverse order\n${items | sort}           -- Sort\n\n-- Number filters\n${num | abs}              -- Absolute value\n${num | round(2)}         -- Round to precision\n${num | floor}            -- Floor\n${num | ceil}             -- Ceiling\n\n-- Default value\n${missing | default(\"fallback\")}\n```\n\n### Loop Variables\n\nInside `@for` loops, you have access to `loop` metadata:\n\n```text\n@for item in items\n  ${loop.index}   -- 1-based index\n  ${loop.index0}  -- 0-based index\n  ${loop.first}   -- true if first iteration\n  ${loop.last}    -- true if last iteration\n  ${loop.length}  -- total items\n@end\n```\n\n## API Reference\n\n### Basic Usage\n\n```lua\nlocal luma = require(\"luma\")\n\n-- Render a template string\nlocal result = luma.render(template_string, context)\n\n-- Compile for reuse\nlocal compiled = luma.compile(template_string)\nlocal result = compiled:render(context)\n```\n\n### Custom Environment\n\n```lua\nlocal env = luma.create_environment()\n\n-- Add custom filter\nenv:add_filter(\"double\", function(s)\n    return s .. s\nend)\n\n-- Add global variable\nenv:add_global(\"site_name\", \"My Site\")\n\n-- Render with custom environment\nlocal result = env:render(\"Welcome to $site_name!\", {})\n```\n\n### Register Global Filters\n\n```lua\nluma.register_filter(\"exclaim\", function(s)\n    return s .. \"!\"\nend)\n\n-- Now available in all templates\nlocal result = luma.render(\"${msg | exclaim}\", { msg = \"Hello\" })\n-- Output: \"Hello!\"\n```\n\n### Inline Directives\n\nFor single-line compact output, use semicolon (`;`) to mark the end of directive expressions:\n\n```lua\n-- Inline conditional\nlocal status = luma.render(\"Status: @if active; ✓ Online @else ✗ Offline @end\", {active = true})\n-- Output: \"Status: ✓ Online\"\n\n-- Inline loop\nlocal list = luma.render(\"Items: @for i in nums; $i @end\", {nums = {1,2,3}})\n-- Output: \"Items: 1 2 3 \"\n\n-- Space before @ distinguishes directives from literals\nlocal email = luma.render(\"Contact: user@example.com\", {})  -- @ is literal\nlocal directive = luma.render(\"Result: @if x; yes @end\", {x=true})  -- @ is directive\n```\n\n**Rules:**\n\n- Use `;` after directive expressions (e.g., `@if condition;`, `@for x in list;`)\n- Directives without expressions don't need `;` (e.g., `@else`, `@end`)\n- Require space before `@` for inline directives to avoid ambiguity with emails, etc.\n\n## Whitespace \u0026 Indentation\n\n\u003e [!IMPORTANT]\n\u003e **Luma automatically preserves indentation in ALL file types** - YAML, HTML, JSON, code, configs, markdown, etc.\n\u003e\n\u003e Indentation is preserved based on where placeholders and directives appear. You rarely need to think about\n\u003e whitespace - Luma handles it intelligently by default.\n\n---\n\n\u003e [!TIP]\n\u003e While directives don't *require* indentation, we **strongly recommend** indenting them to match your document\n\u003e structure for better readability.\n\u003e\n\u003e **Inline directives**: Use semicolon (`;`) after expressions for single-line compact output: `@if x; yes @else no @end`\n\u003e\n\u003e Space required before `@` for inline mode. See [docs/WHITESPACE.md](docs/WHITESPACE.md) for details.\n\n## Example: Kubernetes Deployment\n\n### ✅ Recommended (indented directives)\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: $app.name\n  namespace: ${namespace | default(\"default\")}\nspec:\n  replicas: ${replicas | default(1)}\n  template:\n    spec:\n      containers:\n      @for container in containers\n        - name: $container.name\n          image: ${container.image}:${container.tag | default(\"latest\")}\n        @if container.ports\n          ports:\n          @for port in container.ports\n            - containerPort: $port\n          @end\n        @end\n      @end\n```\n\n#### ⚠️ Works, but harder to read\n\n```yaml\nspec:\n  containers:\n@for container in containers\n        - name: $container.name\n@if container.ports\n          ports:\n@for port in container.ports\n            - containerPort: $port\n@end\n@end\n@end\n```\n\nCompare either to equivalent Helm/Go templates — Luma is much cleaner!\n\n---\n\n## More Examples: Universal Smart Indentation\n\nLuma's smart indentation works everywhere, not just YAML:\n\n### HTML Template\n\n```html\n\u003cul class=\"items\"\u003e\n@for item in items\n  \u003cli class=\"${item.class}\"\u003e\n    \u003cstrong\u003e$item.name\u003c/strong\u003e\n    @if item.description\n    \u003cp\u003e$item.description\u003c/p\u003e\n    @end\n  \u003c/li\u003e\n@end\n\u003c/ul\u003e\n```\n\n### JSON Configuration\n\n```json\n{\n  \"services\": {\n  @for service in services\n    \"$service.name\": {\n      \"port\": $service.port,\n      \"enabled\": @if service.enabled true@else false@end\n    }@if not loop.last,@end\n  @end\n  }\n}\n```\n\n### Python Code Generation\n\n```python\nclass $class_name:\n    def __init__(self):\n    @for field in fields\n        self.$field.name = $field.default\n    @end\n    \n    @for method in methods\n    def $method.name(self):\n        \"\"\"$method.docstring\"\"\"\n        pass\n    \n    @end\n```\n\n**All of these work perfectly without any whitespace control directives!**\n\nSee [docs/WHITESPACE.md](docs/WHITESPACE.md) for comprehensive examples and advanced control options.\n\n---\n\n## Running Tests\n\n```bash\n# Install busted\nluarocks install busted\n\n# Run tests\nbusted spec/\n```\n\n## License\n\nMIT\n\n## Comparison with Jinja2\n\n| Feature | Jinja2 | Luma |\n|---------|--------|------|\n| Variable | `{{ name }}` | `$name` |\n| Expression | `{{ expr }}` | `${expr}` |\n| If block | `{% if %}...{% endif %}` | `@if...@end` |\n| For loop | `{% for %}...{% endfor %}` | `@for...@end` |\n| Comment | `{# comment #}` | `@# comment` |\n| Filters | `{{ x \\| filter }}` | `${x \\| filter}` |\n\nLuma aims for less visual noise while maintaining full expressiveness.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsantosr2%2Fluma","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsantosr2%2Fluma","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsantosr2%2Fluma/lists"}