docs: finalize documentation and project milestones; add export-ignore rules
This commit is contained in:
parent
9697400c0c
commit
ef58018697
8
.gitattributes
vendored
Normal file
8
.gitattributes
vendored
Normal 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
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -10,5 +10,6 @@ crashlytics-build.properties
|
|||
fabric.properties
|
||||
composer.phar
|
||||
/vendor/
|
||||
.junie/
|
||||
.phpunit.cache/
|
||||
.junie
|
||||
.phpunit.cache/
|
||||
.scape
|
||||
|
|
@ -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).
|
||||
|
|
|
|||
93
README.md
93
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 )}
|
||||
<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
|
||||
|
|
|
|||
42
SPECS.md
42
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.
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.2"
|
||||
"php": "^8.2",
|
||||
"ext-intl": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.0"
|
||||
|
|
|
|||
31
composer.lock
generated
31
composer.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue