docs: finalize documentation and project milestones; add export-ignore rules

This commit is contained in:
Funky Waddle 2026-02-11 23:42:28 -06:00
parent 9697400c0c
commit ef58018697
7 changed files with 192 additions and 53 deletions

8
.gitattributes vendored Normal file
View file

@ -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

3
.gitignore vendored
View file

@ -10,5 +10,6 @@ crashlytics-build.properties
fabric.properties fabric.properties
composer.phar composer.phar
/vendor/ /vendor/
.junie/ .junie
.phpunit.cache/ .phpunit.cache/
.scape

View file

@ -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. 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 ## Phase 1: Core Architecture & Environment
*Focus: Establishing the contracts, error handling, and runtime configuration.* *Focus: Establishing the contracts, error handling, and runtime configuration.*
- [ ] Define and implement `Scape\Interfaces\FilterInterface` and `Scape\Interfaces\HostProviderInterface`. - [x] Define and implement `Scape\Interfaces\FilterInterface` and `Scape\Interfaces\HostProviderInterface`.
- [ ] Implement the Exception hierarchy in `Scape\Exceptions`. - [x] Implement the Exception hierarchy in `Scape\Exceptions`.
- [ ] Implement `Scape\Config` to handle environment variables (`SCAPE_*_DIR`) and programmatic overrides. - [x] Implement `Scape\Config` to handle environment variables (`SCAPE_*_DIR`) and programmatic overrides.
- [ ] Create the `Scape\Engine` boilerplate with the `render()` method signature. - [x] Create the `Scape\Engine` boilerplate with the `render()` method signature.
## Phase 2: Lexical Analysis & Tokenization ## Phase 2: Lexical Analysis & Tokenization
*Focus: Turning template strings into a stream of tokens that the parser can understand.* *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. - [x] Implement `Scape\Parser\Lexer` to identify interpolation `{{ }}`, raw `{{{ }}}`, logic `{( )}`, and block `{[ ]}` tags.
- [ ] Support white-space independence within tags. - [x] Support white-space independence within tags.
- [ ] Implement the white-space control rule (logic tags consuming one trailing newline). - [x] Implement the white-space control rule (logic tags consuming one trailing newline).
- [ ] Comprehensive unit tests for all tag variations. - [x] Comprehensive unit tests for all tag variations.
## Phase 3: The AST & Parser ## Phase 3: The AST & Parser
*Focus: Building the Abstract Syntax Tree (AST) representing the template's structure.* *Focus: Building the Abstract Syntax Tree (AST) representing the template's structure.*
- [ ] Implement `Scape\Parser\Parser` to convert tokens into an AST. - [x] Implement `Scape\Parser\Parser` to convert tokens into an AST.
- [ ] Define AST Nodes (Text, Variable, Loop, Block, Include, Filter). - [x] Define AST Nodes (Text, Variable, Loop, Block, Include, Filter).
- [ ] Implement the `foreach` grammar (with optional keys). - [x] Implement the `foreach` grammar (with optional keys).
- [ ] Implement data access logic (dot-notation for objects, brackets for arrays). - [x] Implement data access logic (dot-notation for objects, brackets for arrays).
## Phase 4: The Interpreter (Rendering Engine) ## Phase 4: The Interpreter (Rendering Engine)
*Focus: Turning the AST and data into the final HTML output.* *Focus: Turning the AST and data into the final HTML output.*
- [ ] Implement the AST Interpreter to walk the tree and resolve variables. - [x] Implement the AST Interpreter to walk the tree and resolve variables.
- [ ] Implement standard HTML escaping (`ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5`). - [x] Implement standard HTML escaping (`ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5`).
- [ ] Implement Loop logic with `index`, `pos`, and positional rendering (`first`, `inner`, `last`). - [x] Implement Loop logic with `index`, `pos`, and positional rendering (`first`, `inner`, `last`).
- [ ] Implement `debug` vs `production` variable access modes. - [x] Implement `debug` vs `production` variable access modes.
## Phase 5: Inheritance & Reusability ## Phase 5: Inheritance & Reusability
*Focus: Blocks, Layouts, and Partials.* *Focus: Blocks, Layouts, and Partials.*
- [ ] Implement `{[ extends ]}` and the Block override system (including `{[ parent ]}`). - [x] Implement `{[ extends ]}` and the Block override system (including `{[ parent ]}`).
- [ ] Implement `{[ include ]}` with data scoping rules (`with context`, `with data_source`, inline arrays). - [x] Implement `{[ include ]}` with data scoping rules (`with context`, `with data_source`, inline arrays).
- [ ] Implement the recursion limit check (default 20). - [x] Implement the recursion limit check (default 20).
- [ ] Implement the 404 fallback mechanism. - [x] Implement the 404 fallback mechanism.
## Phase 6: Extensibility & Performance ## Phase 6: Extensibility & Performance
*Focus: Filters, Host IoC, and Caching.* *Focus: Filters, Host IoC, and Caching.*
- [ ] Implement the Filter pipeline (piping and arguments). - [x] Implement the Filter pipeline (piping and arguments).
- [ ] Implement `uses` and `load_filter` mechanisms. - [x] Implement `uses` and `load_filter` mechanisms.
- [ ] Implement the `host` namespace delegation. - [x] Implement the `host` namespace delegation.
- [ ] Implement AST Caching (local storage in `.scape/cache`) with `mtime` invalidation for dev mode. - [x] Implement AST Caching (local storage in `.scape/cache`) with `mtime` invalidation for dev mode.
## Phase 7: Final Polish & Release ## Phase 7: Final Polish & Release
- [ ] Final project-wide code style audit. - [x] Final project-wide code style audit.
- [ ] Ensure 100% test coverage for core rendering logic. - [x] Ensure 100% test coverage for core rendering logic.
- [ ] Draft the final README (Getting Started and examples). - [x] Draft the final README (Getting Started and examples).

View file

@ -1 +1,94 @@
# Scape Templates # 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 )}
<li>{{ item }}</li>
{( endforeach )}
```
#### Filtering
`{{ price | currency('USD') }}`
#### Advanced Expressions
`{( foreach key in user_data | keys )}`
`{[ include 'partial' with data | first ]}`
#### Inheritance
`layout.scape.php`:
```html
<html>
<title>{[ block 'title' ]}Default Title{[ endblock ]}</title>
<body>{[ block 'content' ]}{[ endblock ]}</body>
</html>
```
`page.scape.php`:
```html
{[ extends 'layout' ]}
{[ block 'title' ]}My Page{[ endblock ]}
{[ block 'content' ]}
<h1>Hello World</h1>
{[ 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

View file

@ -23,11 +23,17 @@ composer require getphred/scape
- **Filter Extensions**: - **Filter Extensions**:
- Custom filters must use the standard `.php` extension. - 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. - 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: - **Directory Configuration**: Template locations are managed via environment variables (with the following defaults when not provided):
- `SCAPE_TEMPLATES_DIR`: Main directory for application templates. - `SCAPE_TEMPLATES_DIR`: Main directory for application templates.
- Default: `./templates`
- `SCAPE_LAYOUTS_DIR`: Directory for base layouts and parent templates. - `SCAPE_LAYOUTS_DIR`: Directory for base layouts and parent templates.
- Default: `./templates/layouts`
- `SCAPE_PARTIALS_DIR`: Directory for reusable snippets/partials. - `SCAPE_PARTIALS_DIR`: Directory for reusable snippets/partials.
- Default: `./templates/partials`
- `SCAPE_FILTERS_DIR`: Directory for user-defined filters. - `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. - **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 ### 2. Syntax & White-space
@ -92,13 +98,29 @@ composer require getphred/scape
- **Loading**: Filters must be pre-loaded at the top of the template. - **Loading**: Filters must be pre-loaded at the top of the template.
- **Internal Libraries**: Engine-provided filters are loaded using the `uses` keyword: - **Internal Libraries**: Engine-provided filters are loaded using the `uses` keyword:
- Syntax: `{( uses namespace:library )}` (e.g., `{( uses filters:string )}`). - 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`. - **Custom Filters**: User-defined filters are loaded from `SCAPE_FILTERS_DIR` using `load_filter`.
- Syntax: `{( load_filter('path') )}` where path is dot-notated. - Syntax: `{( load_filter('path') )}` where path is dot-notated.
### 6. Security & Error Handling ### 6. Security & Error Handling
- **Contextual Escaping**: Standard `{{ }}` interpolation ensures XSS protection. - **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. - **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. - `TemplateNotFoundException`: Main template, layout, or partial missing.
- `SyntaxException`: Malformed tags or disallowed logic (e.g. `if`). - `SyntaxException`: Malformed tags or disallowed logic (e.g. `if`).
- `FilterNotFoundException`: Target of `uses` or `load_filter` missing. - `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. - **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. - **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. - **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. - **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. - **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 ### 9. Runtime API
- **The `Scape\Engine` Class**: The primary entry point for the library. - **The `Scape\Engine` Class**: The primary entry point for the library.

View file

@ -12,7 +12,8 @@
} }
], ],
"require": { "require": {
"php": "^8.2" "php": "^8.2",
"ext-intl": "*"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^10.0" "phpunit/phpunit": "^10.0"

31
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "a04d8f4360b838804edefd62253383ac", "content-hash": "4f8a56ae9cae9b8b16e124fc4ef58da9",
"packages": [], "packages": [],
"packages-dev": [ "packages-dev": [
{ {
@ -566,16 +566,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "10.5.60", "version": "10.5.63",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "f2e26f52f80ef77832e359205f216eeac00e320c" "reference": "33198268dad71e926626b618f3ec3966661e4d90"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f2e26f52f80ef77832e359205f216eeac00e320c", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90",
"reference": "f2e26f52f80ef77832e359205f216eeac00e320c", "reference": "33198268dad71e926626b618f3ec3966661e4d90",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -596,7 +596,7 @@
"phpunit/php-timer": "^6.0.0", "phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1", "sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0", "sebastian/code-unit": "^2.0.0",
"sebastian/comparator": "^5.0.4", "sebastian/comparator": "^5.0.5",
"sebastian/diff": "^5.1.1", "sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0", "sebastian/environment": "^6.1.0",
"sebastian/exporter": "^5.1.4", "sebastian/exporter": "^5.1.4",
@ -647,7 +647,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "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": [ "funding": [
{ {
@ -671,7 +671,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-12-06T07:50:42+00:00" "time": "2026-01-27T05:48:37+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",
@ -843,16 +843,16 @@
}, },
{ {
"name": "sebastian/comparator", "name": "sebastian/comparator",
"version": "5.0.4", "version": "5.0.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git", "url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e" "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e", "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
"reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e", "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -908,7 +908,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues", "issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy", "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": [ "funding": [
{ {
@ -928,7 +928,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-09-07T05:25:07+00:00" "time": "2026-01-24T09:25:16+00:00"
}, },
{ {
"name": "sebastian/complexity", "name": "sebastian/complexity",
@ -1683,7 +1683,8 @@
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": "^8.2" "php": "^8.2",
"ext-intl": "*"
}, },
"platform-dev": {}, "platform-dev": {},
"plugin-api-version": "2.6.0" "plugin-api-version": "2.6.0"