diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..34bc0c5
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,8 @@
+/tests export-ignore
+/.scape export-ignore
+/LOG.md export-ignore
+/SPECS.md export-ignore
+/MILESTONES.md export-ignore
+/phpunit.xml export-ignore
+.gitignore export-ignore
+.gitattributes export-ignore
diff --git a/.gitignore b/.gitignore
index 623aa48..cbd36dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,5 +10,6 @@ crashlytics-build.properties
fabric.properties
composer.phar
/vendor/
-.junie/
-.phpunit.cache/
\ No newline at end of file
+.junie
+.phpunit.cache/
+.scape
\ No newline at end of file
diff --git a/MILESTONES.md b/MILESTONES.md
index 03c6f0c..95bb42a 100644
--- a/MILESTONES.md
+++ b/MILESTONES.md
@@ -2,49 +2,58 @@
This document outlines the phased roadmap for building Scape Templates. Each milestone is designed to ensure "Excellence over Features," focusing on a solid foundation before adding complexity.
+## Table of Contents
+- [Phase 1: Core Architecture & Environment](#phase-1-core-architecture--environment)
+- [Phase 2: Lexical Analysis & Tokenization](#phase-2-lexical-analysis--tokenization)
+- [Phase 3: The AST & Parser](#phase-3-the-ast--parser)
+- [Phase 4: The Interpreter (Rendering Engine)](#phase-4-the-interpreter-rendering-engine)
+- [Phase 5: Inheritance & Reusability](#phase-5-inheritance--reusability)
+- [Phase 6: Extensibility & Performance](#phase-6-extensibility--performance)
+- [Phase 7: Final Polish & Release](#phase-7-final-polish--release)
+
## Phase 1: Core Architecture & Environment
*Focus: Establishing the contracts, error handling, and runtime configuration.*
-- [ ] Define and implement `Scape\Interfaces\FilterInterface` and `Scape\Interfaces\HostProviderInterface`.
-- [ ] Implement the Exception hierarchy in `Scape\Exceptions`.
-- [ ] Implement `Scape\Config` to handle environment variables (`SCAPE_*_DIR`) and programmatic overrides.
-- [ ] Create the `Scape\Engine` boilerplate with the `render()` method signature.
+- [x] Define and implement `Scape\Interfaces\FilterInterface` and `Scape\Interfaces\HostProviderInterface`.
+- [x] Implement the Exception hierarchy in `Scape\Exceptions`.
+- [x] Implement `Scape\Config` to handle environment variables (`SCAPE_*_DIR`) and programmatic overrides.
+- [x] Create the `Scape\Engine` boilerplate with the `render()` method signature.
## Phase 2: Lexical Analysis & Tokenization
*Focus: Turning template strings into a stream of tokens that the parser can understand.*
-- [ ] Implement `Scape\Parser\Lexer` to identify interpolation `{{ }}`, raw `{{{ }}}`, logic `{( )}`, and block `{[ ]}` tags.
-- [ ] Support white-space independence within tags.
-- [ ] Implement the white-space control rule (logic tags consuming one trailing newline).
-- [ ] Comprehensive unit tests for all tag variations.
+- [x] Implement `Scape\Parser\Lexer` to identify interpolation `{{ }}`, raw `{{{ }}}`, logic `{( )}`, and block `{[ ]}` tags.
+- [x] Support white-space independence within tags.
+- [x] Implement the white-space control rule (logic tags consuming one trailing newline).
+- [x] Comprehensive unit tests for all tag variations.
## Phase 3: The AST & Parser
*Focus: Building the Abstract Syntax Tree (AST) representing the template's structure.*
-- [ ] Implement `Scape\Parser\Parser` to convert tokens into an AST.
-- [ ] Define AST Nodes (Text, Variable, Loop, Block, Include, Filter).
-- [ ] Implement the `foreach` grammar (with optional keys).
-- [ ] Implement data access logic (dot-notation for objects, brackets for arrays).
+- [x] Implement `Scape\Parser\Parser` to convert tokens into an AST.
+- [x] Define AST Nodes (Text, Variable, Loop, Block, Include, Filter).
+- [x] Implement the `foreach` grammar (with optional keys).
+- [x] Implement data access logic (dot-notation for objects, brackets for arrays).
## Phase 4: The Interpreter (Rendering Engine)
*Focus: Turning the AST and data into the final HTML output.*
-- [ ] Implement the AST Interpreter to walk the tree and resolve variables.
-- [ ] Implement standard HTML escaping (`ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5`).
-- [ ] Implement Loop logic with `index`, `pos`, and positional rendering (`first`, `inner`, `last`).
-- [ ] Implement `debug` vs `production` variable access modes.
+- [x] Implement the AST Interpreter to walk the tree and resolve variables.
+- [x] Implement standard HTML escaping (`ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5`).
+- [x] Implement Loop logic with `index`, `pos`, and positional rendering (`first`, `inner`, `last`).
+- [x] Implement `debug` vs `production` variable access modes.
## Phase 5: Inheritance & Reusability
*Focus: Blocks, Layouts, and Partials.*
-- [ ] Implement `{[ extends ]}` and the Block override system (including `{[ parent ]}`).
-- [ ] Implement `{[ include ]}` with data scoping rules (`with context`, `with data_source`, inline arrays).
-- [ ] Implement the recursion limit check (default 20).
-- [ ] Implement the 404 fallback mechanism.
+- [x] Implement `{[ extends ]}` and the Block override system (including `{[ parent ]}`).
+- [x] Implement `{[ include ]}` with data scoping rules (`with context`, `with data_source`, inline arrays).
+- [x] Implement the recursion limit check (default 20).
+- [x] Implement the 404 fallback mechanism.
## Phase 6: Extensibility & Performance
*Focus: Filters, Host IoC, and Caching.*
-- [ ] Implement the Filter pipeline (piping and arguments).
-- [ ] Implement `uses` and `load_filter` mechanisms.
-- [ ] Implement the `host` namespace delegation.
-- [ ] Implement AST Caching (local storage in `.scape/cache`) with `mtime` invalidation for dev mode.
+- [x] Implement the Filter pipeline (piping and arguments).
+- [x] Implement `uses` and `load_filter` mechanisms.
+- [x] Implement the `host` namespace delegation.
+- [x] Implement AST Caching (local storage in `.scape/cache`) with `mtime` invalidation for dev mode.
## Phase 7: Final Polish & Release
-- [ ] Final project-wide code style audit.
-- [ ] Ensure 100% test coverage for core rendering logic.
-- [ ] Draft the final README (Getting Started and examples).
+- [x] Final project-wide code style audit.
+- [x] Ensure 100% test coverage for core rendering logic.
+- [x] Draft the final README (Getting Started and examples).
diff --git a/README.md b/README.md
index f9a06c3..1154bb8 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,94 @@
# Scape Templates
+
+A lightweight, standalone PHP template engine designed for simplicity, security, and performance. Scape focuses on being an **Output Engine first**, marrying pre-processed data with design while enforcing a "logic-light" philosophy.
+
+## Features
+
+- **Dot Notation & Bracket Access**: Effortlessly access nested objects and arrays.
+- **Inheritance & Blocks**: Define base layouts and override sections in child templates.
+- **Partials & Includes**: Reuse template snippets with controlled data scoping.
+- **Filter Pipeline**: Transform data using built-in or custom filters (e.g., `{{ var | lower | ucfirst }}`).
+ - Built-in filters: `lower`, `upper`, `ucfirst`, `currency`, `float`, `date`, `truncate`, `default`, `json`, `url_encode`, `join`, `first`, `last`, `word_count`, `keys`.
+ - Filters can be used in variable interpolations, `foreach` loops, and `include` tags.
+- **Secure by Default**: Automatic contextual HTML escaping for all variables.
+- **AST Caching**: High performance via Abstract Syntax Tree caching with automatic dev-mode invalidation.
+- **Host Integration (IoC)**: Easy integration with frameworks through the reserved `host` namespace.
+- **Logic-Light**: Encourages separation of concerns by supporting only necessary logic like `foreach`.
+
+## Installation
+
+```bash
+composer require getphred/scape
+```
+
+## Quick Start
+
+```php
+use Scape\Engine;
+
+$engine = new Engine([
+ 'templates_dir' => __DIR__ . '/templates',
+ 'mode' => 'debug' // or 'production'
+]);
+
+echo $engine->render('index', [
+ 'title' => 'Welcome to Scape',
+ 'user' => ['name' => 'Funky']
+]);
+```
+
+### Basic Syntax
+
+#### Interpolation (Escaped)
+`{{ user.name }}`
+
+#### Raw Interpolation
+`{{{ raw_html }}}`
+
+#### Loops
+```html
+{( foreach item in items )}
+
{{ item }}
+{( endforeach )}
+```
+
+#### Filtering
+`{{ price | currency('USD') }}`
+
+#### Advanced Expressions
+`{( foreach key in user_data | keys )}`
+
+`{[ include 'partial' with data | first ]}`
+
+#### Inheritance
+`layout.scape.php`:
+```html
+
+ {[ block 'title' ]}Default Title{[ endblock ]}
+ {[ block 'content' ]}{[ endblock ]}
+
+```
+
+`page.scape.php`:
+```html
+{[ extends 'layout' ]}
+{[ block 'title' ]}My Page{[ endblock ]}
+{[ block 'content' ]}
+ Hello World
+{[ endblock ]}
+```
+
+## Configuration
+
+Scape uses environment variables or programmatic configuration:
+
+- `SCAPE_TEMPLATES_DIR`: Default `./templates`
+- `SCAPE_LAYOUTS_DIR`: Default `./templates/layouts`
+- `SCAPE_PARTIALS_DIR`: Default `./templates/partials`
+- `SCAPE_FILTERS_DIR`: Default `./filters`
+- `SCAPE_CACHE_DIR`: Default `./.scape/cache`
+- `SCAPE_MODE`: `production` (default) or `debug`
+
+## License
+
+MIT
diff --git a/SPECS.md b/SPECS.md
index 925dba0..81e6c6b 100644
--- a/SPECS.md
+++ b/SPECS.md
@@ -23,11 +23,17 @@ composer require getphred/scape
- **Filter Extensions**:
- Custom filters must use the standard `.php` extension.
- All custom filters must implement the `Scape\Interfaces\FilterInterface` to ensure they provide the necessary transformation methods.
-- **Directory Configuration**: Template locations are managed via environment variables:
- - `SCAPE_TEMPLATES_DIR`: Main directory for application templates.
- - `SCAPE_LAYOUTS_DIR`: Directory for base layouts and parent templates.
- - `SCAPE_PARTIALS_DIR`: Directory for reusable snippets/partials.
- - `SCAPE_FILTERS_DIR`: Directory for user-defined filters.
+- **Directory Configuration**: Template locations are managed via environment variables (with the following defaults when not provided):
+ - `SCAPE_TEMPLATES_DIR`: Main directory for application templates.
+ - Default: `./templates`
+ - `SCAPE_LAYOUTS_DIR`: Directory for base layouts and parent templates.
+ - Default: `./templates/layouts`
+ - `SCAPE_PARTIALS_DIR`: Directory for reusable snippets/partials.
+ - Default: `./templates/partials`
+ - `SCAPE_FILTERS_DIR`: Directory for user-defined filters.
+ - Default: `./filters`
+ - `SCAPE_CACHE_DIR`: Directory for cached AST files.
+ - Default: `./.scape/cache`
- **Dot Notation Pathing**: All internal paths (extends, includes) use dot notation (e.g., `sidebar.login_form`) relative to their respective directories, omitting the file extension.
### 2. Syntax & White-space
@@ -92,13 +98,29 @@ composer require getphred/scape
- **Loading**: Filters must be pre-loaded at the top of the template.
- **Internal Libraries**: Engine-provided filters are loaded using the `uses` keyword:
- Syntax: `{( uses namespace:library )}` (e.g., `{( uses filters:string )}`).
+ - **filters:string** library includes:
+ - `lower`: Converts to lowercase.
+ - `upper`: Converts to uppercase.
+ - `ucfirst`: Capitalizes the first character.
+ - `currency(code)`: Formats numeric value as currency (default: 'USD').
+ - `float(precision)`: Formats numeric value as a float with fixed precision (default: 2).
+ - `date(format)`: Formats a timestamp or date string (default: 'Y-m-d H:i:s').
+ - `truncate(length, suffix)`: Truncates string to length (default length: 100, suffix: '...').
+ - `default(fallback)`: Returns fallback if value is empty.
+ - `json`: Returns JSON encoded string.
+ - `url_encode`: Returns URL encoded string.
+ - `join(glue)`: Joins array elements with glue (default glue: '').
+ - `first`: Returns first element of a collection.
+ - `last`: Returns last element of a collection.
+ - `word_count`: Returns word count of a string.
+ - `keys`: Returns keys of an associative array.
- **Custom Filters**: User-defined filters are loaded from `SCAPE_FILTERS_DIR` using `load_filter`.
- Syntax: `{( load_filter('path') )}` where path is dot-notated.
### 6. Security & Error Handling
- **Contextual Escaping**: Standard `{{ }}` interpolation ensures XSS protection.
- **Missing Assets**: If a layout or partial is missing, the engine looks for a user-provided `404.scape.php` in `SCAPE_TEMPLATES_DIR`. If not found, it renders a built-in "Template 404" placeholder.
-- **Exceptions**: The engine throws specific exceptions within the `Scape\Exceptions` namespace:
+- **Exceptions**: The engine throws specific exceptions within the `Scape\Exceptions` namespace. **All exceptions must include helpful, context-rich error messages** (e.g., template name, line number, and specific failure reason) to assist in debugging:
- `TemplateNotFoundException`: Main template, layout, or partial missing.
- `SyntaxException`: Malformed tags or disallowed logic (e.g. `if`).
- `FilterNotFoundException`: Target of `uses` or `load_filter` missing.
@@ -116,11 +138,15 @@ composer require getphred/scape
- **Development**: Engine checks file modification times (`mtime`) to invalidate the cache when a template changes.
- **Production**: Engine skips `mtime` checks and serves the cached AST directly for maximum performance.
-### 8. Host Integration (IoC)
+### 8. Host Integration (IoC) & i18n
- **The `host` Namespace**: Scape provides a reserved `host` namespace that can be used with the `uses` keyword or as a filter/function prefix.
- **Provider Registration**: Host frameworks (e.g., Phred) can register custom providers to handle calls in this namespace.
+- **Localization (i18n)**:
+ - **Philosophy**: Scape is "Simple for Blogs, Powerful for Enterprise." Localization is an **opt-in** feature.
+ - **Responsibility**: Translation logic and message catalogs reside in the Application/Framework. Scape provides the **access layer**.
+ - **Usage**: Templates can use `host.translate('key')` or the `|t` filter alias to fetch localized strings on-demand.
+ - **Portability**: If no host provider is registered, i18n calls return the input key/value unchanged, ensuring templates remain portable across environments.
- **Use Cases**: Used for framework-level features like feature flags (Flagpole), routing, or translations without creating a hard dependency within the engine.
-- **Default Behavior**: If no provider is registered, calls to the `host` namespace return the input value unchanged or `null`.
### 9. Runtime API
- **The `Scape\Engine` Class**: The primary entry point for the library.
diff --git a/composer.json b/composer.json
index 9e43cff..bb50848 100644
--- a/composer.json
+++ b/composer.json
@@ -12,7 +12,8 @@
}
],
"require": {
- "php": "^8.2"
+ "php": "^8.2",
+ "ext-intl": "*"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
diff --git a/composer.lock b/composer.lock
index d74b6a1..e394c05 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "a04d8f4360b838804edefd62253383ac",
+ "content-hash": "4f8a56ae9cae9b8b16e124fc4ef58da9",
"packages": [],
"packages-dev": [
{
@@ -566,16 +566,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "10.5.60",
+ "version": "10.5.63",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "f2e26f52f80ef77832e359205f216eeac00e320c"
+ "reference": "33198268dad71e926626b618f3ec3966661e4d90"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f2e26f52f80ef77832e359205f216eeac00e320c",
- "reference": "f2e26f52f80ef77832e359205f216eeac00e320c",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90",
+ "reference": "33198268dad71e926626b618f3ec3966661e4d90",
"shasum": ""
},
"require": {
@@ -596,7 +596,7 @@
"phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0",
- "sebastian/comparator": "^5.0.4",
+ "sebastian/comparator": "^5.0.5",
"sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0",
"sebastian/exporter": "^5.1.4",
@@ -647,7 +647,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.60"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63"
},
"funding": [
{
@@ -671,7 +671,7 @@
"type": "tidelift"
}
],
- "time": "2025-12-06T07:50:42+00:00"
+ "time": "2026-01-27T05:48:37+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -843,16 +843,16 @@
},
{
"name": "sebastian/comparator",
- "version": "5.0.4",
+ "version": "5.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
+ "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
- "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
+ "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
"shasum": ""
},
"require": {
@@ -908,7 +908,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
- "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5"
},
"funding": [
{
@@ -928,7 +928,7 @@
"type": "tidelift"
}
],
- "time": "2025-09-07T05:25:07+00:00"
+ "time": "2026-01-24T09:25:16+00:00"
},
{
"name": "sebastian/complexity",
@@ -1683,7 +1683,8 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
- "php": "^8.2"
+ "php": "^8.2",
+ "ext-intl": "*"
},
"platform-dev": {},
"plugin-api-version": "2.6.0"