diff --git a/README.md b/README.md index a6bba27..1ee1d10 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ALTO CodeSlicer +# ALTO Code Slicer Extract immutable source-code slices by line, text, or language structure while preserving exact byte ranges and line numbers. @@ -9,7 +9,7 @@ byte ranges and line numbers.   ![License](https://img.shields.io/github/license/altophp/code-slicer?label=License&labelColor=050608&color=00B7FF)   [![GitHub Sponsors](https://img.shields.io/github/sponsors/smnandre?logo=githubsponsors&logoColor=00B7FF&label=%20Sponsor&labelColor=050608&color=00B7FF)](https://github.com/sponsors/smnandre) -CodeSlicer loads a complete source and progressively narrows an immutable `CodeSlice`. Every slice +Code Slicer loads a complete source and progressively narrows an immutable `CodeSlice`. Every slice keeps its language, source name, and original line numbers. ```php @@ -23,20 +23,22 @@ echo $slice->content(); echo $slice->startLine(); ``` -Selection stops before presentation. CodeSlicer returns source ranges and leaves syntax tokens, +Selection stops before presentation. Code Slicer returns source ranges and leaves syntax tokens, annotations, highlighting, and rendering to downstream consumers. ## Installation -Install ALTO CodeSlicer with Composer: +This package is currently distributed from its development branch; no stable +release is published yet. Configure its VCS repository before installing it: ```bash -composer require alto/code-slicer +composer config repositories.alto-code-slicer vcs https://github.com/altophp/code-slicer +composer require alto/code-slicer:dev-main ``` -CodeSlicer requires PHP 8.4 or later, the tokenizer extension, and `alto/language`. +Code Slicer requires PHP 8.4 or later, the tokenizer extension, and `alto/language`. -## Quick Start +## Quick start Select a PHP method from an in-memory source: @@ -156,10 +158,16 @@ and annotations onto the selected range. `content()` remains the exact, unmodifi `CodeSlice` is the raw result of source extraction. Its complete source and byte range let a consumer analyze the full context before projecting the selected region into its own model. -CodeSlicer deliberately provides no HTML, SVG, Markdown, syntax tokens, themes, remote loaders, or +Code Slicer deliberately provides no HTML, SVG, Markdown, syntax tokens, themes, remote loaders, or source rewriting. -See the [documentation](docs/index.md) for installation, a guided example, and the public API. +## Documentation + +- [Documentation home](docs/index.md) +- [Installation](docs/installation.md) +- [Getting started](docs/getting-started.md) +- [Selectors](docs/selectors.md) +- [Languages](docs/languages/index.md) ## Contributing @@ -178,7 +186,7 @@ Changes to public behavior should include tests and documentation. ## Support -ALTO CodeSlicer is open source. You can support its continued development through +ALTO Code Slicer is open source. You can support its continued development through [GitHub Sponsors](https://github.com/sponsors/smnandre). Sharing this package with others or @@ -186,5 +194,5 @@ Sharing this package with others or ## License -ALTO CodeSlicer is released by [ALTO PHP](https://altophp.com) under the +ALTO Code Slicer is released by [ALTO PHP](https://altophp.com) under the [MIT License](LICENSE). diff --git a/docs/getting-started.md b/docs/getting-started.md index caa7962..fd4d686 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,64 +1,65 @@ # Getting started -Start from the complete source so every slice can retain its original context and line numbers. +Extract one method from a complete PHP source and print its original line +number. This gives you an excerpt that remains tied to its source when you +publish or highlight it. -## Select a PHP method +## Extract a PHP method + +### Prepare the script + +After [installation](installation.md), create `extract.php` beside the +`vendor` directory. This example includes its input, so no separate source +file is needed: ```php +slice() ->class('Checkout') ->method('complete'); -echo $slice->content(); -echo $slice->startLine(); -``` - -The class selector narrows the search scope. The method selector then returns the declaration, -including attached documentation and attributes. - -For an in-memory PHP fragment, pass the language explicitly. Structural selectors accept PHP with -or without an opening tag: - -```php -$slice = CodeSource::fromString( - 'final class Checkout {}', - 'php', -)->slice()->class('Checkout'); +printf("Starts at line %d\n", $slice->startLine()); +echo $slice->content(), "\n"; ``` -## Select exact lines +### Run and check the result -Line numbers are one-based, inclusive, and refer to the complete source: +Run the script from the project directory: -```php -$slice = $source->lines(12, 18); +```sh +php extract.php ``` -Line endings remain byte-for-byte identical to the source. - -## Select between markers - -Text selectors search only within the current slice: +The output is: -```php -$slice = $source->slice() - ->after("// example:start\n") - ->before("\n// example:end"); +```text +Starts at line 4 + public function complete(): string + { + return 'Order complete'; + } ``` -Each selector returns a new immutable `CodeSlice`. - -## Use the complete context downstream - -```php -$completeSource = $slice->source()->content(); -$range = $slice->range(); - -$range->start; // inclusive byte offset -$range->end; // exclusive byte offset -``` +The class selector narrows the search to `Checkout`; the method selector +returns `complete` with its closing brace. Indentation and line numbers still +refer to the complete input. Each selection returns a new immutable slice. -A parser or highlighter can analyze the complete source, then project its result onto that range. +Continue with [PHP selectors](languages/php.md) to select methods from files and include +their attached documentation. diff --git a/docs/index.md b/docs/index.md index 68209be..075d309 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,23 +1,29 @@ -# Alto CodeSlicer documentation +# Alto Code Slicer -Alto CodeSlicer extracts immutable ranges from complete source code while preserving the original -bytes, language, source name, and line numbers. +Alto Code Slicer extracts immutable ranges from source code while preserving +the original bytes, language, source name, and line numbers. It selects by +lines, literal boundaries, or supported language structures and leaves +rendering to the consuming application. -## Documentation +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromString("first\nsecond")->lines(2, 2); +echo $slice->content(); +``` -- [Installation](installation.md) covers requirements and Composer. -- [Getting started](getting-started.md) builds a slice with line, text, and structural selectors. -- [Public API](public-api.md) defines the supported entry points and selection rules. +The result is `second`, still associated with original source line 2. -## Mental model +## Documentation -`CodeSource` owns the complete input. `CodeSlice` is a half-open byte range over that source. -Selectors return narrower slices without modifying either object. +- [Installation](installation.md): install the package and verify a first selection. +- [Getting started](getting-started.md): extract a complete PHP method with its original line number. +- [Selectors](selectors.md): create sources, chain selections, inspect ranges, and handle failures. +- [Languages](languages/index.md): choose text or structural selectors for each supported source. -```text -CodeSource - -> slice() - -> lines(), after(), before(), or a structural selector - -> CodeSlice - -> content(), source(), range(), and original line numbers -``` +## Boundaries + +A `CodeSource` owns the complete input. A `CodeSlice` identifies a half-open +byte range within that source, and every selector returns a new narrower slice. +Code Slicer does not highlight, render, rewrite, compile, or execute the +selected source. diff --git a/docs/installation.md b/docs/installation.md index 97c1a55..f73e090 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,32 +1,42 @@ # Installation -## Requirements +This package is currently distributed from its development branch; no stable +release is published yet. Use an explicit VCS repository for evaluation. -Alto CodeSlicer requires PHP 8.4 or later, the tokenizer extension, Composer, and -`alto/language`. +Code Slicer requires PHP 8.4 or later and the Tokenizer extension. Composer +installs its `alto/language` dependency automatically. -## Install with Composer +## Install -```bash -composer require alto/code-slicer +Run these commands in your project directory: + +```sh +composer config repositories.alto-code-slicer vcs https://github.com/altophp/code-slicer +composer require alto/code-slicer:dev-main ``` -Framework applications normally load Composer's autoloader. A standalone script can load it -directly: +## Verify the installation + +Create `check.php` beside the `vendor` directory: ```php -require __DIR__.'/vendor/autoload.php'; -``` +lines(2, 2); +$slice = CodeSource::fromString("first\nsecond")->lines(2, 2); +echo $slice->content(), "\n"; +``` + +Run `php check.php`. The result should be: -echo $slice->content(); +```text +second ``` -The script prints `second`. +Framework applications usually load Composer's autoloader already. Standalone +scripts need the `require` statement shown above. + +Continue with [Getting started](getting-started.md) to extract a PHP method. diff --git a/docs/languages/css.md b/docs/languages/css.md new file mode 100644 index 0000000..34f4ce3 --- /dev/null +++ b/docs/languages/css.md @@ -0,0 +1,103 @@ +# CSS selectors + +Select a complete rule with `rule()`, or an at-rule with `atRule()`. Closing braces are +matched automatically, including nested rule bodies. Use these selectors on a +CSS source when line numbers may change but the rule you want has a stable +selector or at-rule name. + +The examples below assume [Code Slicer is installed](../installation.md) and +Composer's autoloader is loaded. Save the input as `checkout.css` in the working +directory before running the PHP selections. The `.css` extension identifies +the language automatically. + +## Select a rule + +**Source: `checkout.css`** + +```css +.checkout { + display: grid; + gap: 1rem; +} + +@media (width >= 48rem) { + .checkout { + grid-template-columns: 2fr 1fr; + } +} +``` + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('checkout.css')->slice()->rule('.checkout'); +echo $slice->content(); +``` + +**Result** + +```css +.checkout { + display: grid; + gap: 1rem; +} +``` + +The first matching rule in the current slice is selected. Attached CSS comments are included +when present. Selector lists must be supplied in full: `.button, .button--ghost` selects that +list; `.button` alone does not. Whitespace in the selector is normalized for matching. + +## Select a rule inside a media query + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('checkout.css')->slice() + ->atRule('media', '(width >= 48rem)') + ->rule('.checkout'); +echo $slice->content(); +``` + +**Result** + +```css + .checkout { + grid-template-columns: 2fr 1fr; + } +``` + +Narrowing to the media query selects the second `.checkout` rule without relying on line numbers. + +## Select the whole media query + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('checkout.css')->slice()->atRule('media'); +echo $slice->content(); +``` + +**Result** + +```css +@media (width >= 48rem) { + .checkout { + grid-template-columns: 2fr 1fr; + } +} +``` + +The at-rule name omits `@`. Its second argument is optional: `atRule('media')` finds the first +media query; supplying `(width >= 48rem)` also matches its condition. `atRule('keyframes', 'pulse')` +selects named keyframes. At-rules without bodies, such as `@import`, end at their semicolon. + +A missing rule or at-rule raises `SourceSymbolNotFound`. Check the full selector +list and any containing media query; narrowing the slice may exclude a match. + +See [selection rules](../selectors.md) or choose another [language](index.md). diff --git a/docs/languages/index.md b/docs/languages/index.md new file mode 100644 index 0000000..ca41e08 --- /dev/null +++ b/docs/languages/index.md @@ -0,0 +1,66 @@ +# Languages + +Choose literal selectors for any text source or structural selectors for the +languages Code Slicer understands. Every selection preserves original bytes, +source identity, and line numbers. + +`CodeSource::fromFile()` identifies a language from the filename. For an +in-memory source, pass the language explicitly, such as +`CodeSource::fromString($code, 'css')`. A missing language still permits line +and literal-text selection. + +## Text and lines + +HTML, SVG, YAML, Markdown, environment files, and unknown source types remain +ordinary text. Select an inclusive range of original lines with `lines()`, or +chain `after()` and `before()` around known literal boundaries. These selectors +do not balance nested tags, infer YAML indentation, or understand Markdown +sections. + +```php +use Alto\Code\Slicer\CodeSource; + +$source = CodeSource::fromString("Customer: Ada\nTotal: 42 EUR\nStatus: paid"); +echo $source->slice() + ->after('Total: ') + ->before(' EUR') + ->content(); +``` + +Both delimiters are excluded. Each call searches for the first case-sensitive +match inside the current slice and returns a new narrower slice. The original +source and earlier slices remain unchanged. The output is `42`, from original +source line 2. + +A missing literal raises `SourceTextNotFound`. Check spelling, whitespace, +line endings, and earlier selections before retrying. Empty search text raises +`InvalidArgumentException`. Invalid or expanding line ranges raise +`InvalidSourceRange`. + +## Structural selectors + +Use a structural selector when an excerpt should follow a named declaration. +It includes the matching closing boundary and preserves indentation. + +| Source | Identifiers | Selectors | Guide | +| --- | --- | --- | --- | +| PHP | `php` | Classes and methods | [PHP](php.md) | +| JavaScript | `javascript`, `js` | Functions, classes, and methods | [JavaScript](js.md) | +| TypeScript | `typescript`, `ts` | Typed functions, classes, and methods | [TypeScript](ts.md) | +| CSS | `css` | Rules and at-rules | [CSS](css.md) | +| Twig | `twig` | Blocks and macros | [Twig](twig.md) | + +Start with the complete source, then narrow to a containing class, rule, +at-rule, or block when a name is repeated. + +## Selection behavior + +Structural selectors return the first matching declaration fully contained in +the current slice. A missing symbol raises `SourceSymbolNotFound`; an unknown +or unsupported structural language raises `UnsupportedSourceLanguage`. Code +Slicer does not silently return the whole source. + +These selectors locate supported source structures. They are not compilers, +formatters, or type checkers, and malformed input may not expose the expected +declaration. Read [Selectors](../selectors.md) for shared range, chaining, and +failure rules. diff --git a/docs/languages/js.md b/docs/languages/js.md new file mode 100644 index 0000000..8cf6859 --- /dev/null +++ b/docs/languages/js.md @@ -0,0 +1,78 @@ +# JavaScript + +Select named functions, classes, and methods while preserving their original source. +After [installation](../installation.md), load Composer's autoloader and save the input under the filename shown. + +## JavaScript functions + +**Source: `checkout.js`** + +```javascript +export function formatPrice(amount) { + return `${amount.toFixed(2)} EUR`; +} + +export class Checkout { + total(prices) { + return prices.reduce((sum, price) => sum + price, 0); + } + + reset() { + this.prices = []; + } +} +``` + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('checkout.js')->slice()->function('formatPrice'); +echo $slice->content(); +``` + +**Result** + +```javascript +export function formatPrice(amount) { + return `${amount.toFixed(2)} EUR`; +} +``` + +Attached JSDoc is included when present. Braces inside strings and template literals do not +close the function early. + +## JavaScript methods + +Using the same file, narrow to the class before selecting its method: + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('checkout.js')->slice()->class('Checkout')->method('total'); +echo $slice->content(); +``` + +**Result** + +```javascript + total(prices) { + return prices.reduce((sum, price) => sum + price, 0); + } +``` + +`function()` selects functions outside classes. Use `method()` for class members, including +async methods, getters, generators, and private methods. A private method written `#reset()` +is selected with `method('reset')`. + +`beforeMethod()`, `afterMethod()`, `beforeNextMethod()`, and `beforeNextClass()` also work +with JavaScript and TypeScript. See the [PHP boundary example](php.md#continue-after-a-method). + +Selection uses the first matching name fully inside the current slice. Names +are case-sensitive. A missing declaration raises `SourceSymbolNotFound`; +check its name and the containing class or block before retrying. + +See [selection rules](../selectors.md) or return to [Languages](index.md). diff --git a/docs/languages/php.md b/docs/languages/php.md new file mode 100644 index 0000000..3cc27fc --- /dev/null +++ b/docs/languages/php.md @@ -0,0 +1,120 @@ +# PHP selectors + +Select classes and methods by name. The range includes the declaration, its attached docblock +and attributes, and its complete body. Indentation remains exactly as written in the file. + +The PHP selections below assume [installation](../installation.md) is complete +and Composer's autoloader is loaded. Save the input as `Checkout.php` in the working directory before running the +PHP selections. The `.php` filename identifies the source language. + +## Select a method + +**Source: `Checkout.php`** + +```php +prices = []; + } + + private array $prices = []; +} +``` + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('Checkout.php')->slice()->class('Checkout')->method('total'); +echo $slice->content(); +``` + +**Result** + +```php partial + /** + * Apply VAT to the order subtotal. + */ + public function total(array $prices): float + { + return array_sum($prices) * 1.2; + } +``` + +`class('Checkout')` limits the search when multiple classes have a method named `total`. +The returned range starts on source line 5 and ends on line 11. + +## Exclude this method's docblock + +For the same file, select the method and skip its docblock's closing line: + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('Checkout.php')->slice() + ->method('total') + ->after("*/\n"); +echo $slice->content(); +``` + +**Result** + +```php partial + public function total(array $prices): float + { + return array_sum($prices) * 1.2; + } +``` + +`after()` searches literal text, including the LF line ending used in this input. This works for the docblock shown here; it is not a +universal docblock-removal option, and missing text throws `SourceTextNotFound`. + +## Continue after a method + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('Checkout.php')->slice()->class('Checkout') + ->afterMethod('total') + ->method('reset'); +echo $slice->content(); +``` + +**Result** + +```php partial + public function reset(): void + { + $this->prices = []; + } +``` + +`beforeMethod('total')` keeps the range before that declaration. `beforeNextMethod()` +stops at the first method in the current slice; `beforeNextClass()` does the same for a class. +These boundary selectors exclude the declaration they find. + +PHP structural selection supports classes and methods. The `function()` selector is currently +provided for JavaScript and TypeScript only. + +Selection uses the first matching name fully inside the current slice. Names +are case-sensitive. A missing declaration raises `SourceSymbolNotFound`; +check its name and the containing class or block before retrying. + +See [selection rules](../selectors.md) or return to [Languages](index.md). diff --git a/docs/languages/ts.md b/docs/languages/ts.md new file mode 100644 index 0000000..063256e --- /dev/null +++ b/docs/languages/ts.md @@ -0,0 +1,69 @@ +# TypeScript + +Select named functions, classes, and methods while preserving their original source. +After [installation](../installation.md), load Composer's autoloader and save the input under the filename shown. + +## TypeScript methods + +**Source: `Checkout.ts`** + +```typescript +export class Checkout { + total(prices: number[]): number { + return prices.reduce((sum, price) => sum + price, 0); + } +} + +export function parse(raw: string): T { + return JSON.parse(raw) as T; +} +``` + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('Checkout.ts')->slice()->class('Checkout')->method('total'); +echo $slice->content(); +``` + +**Result** + +```typescript + total(prices: number[]): number { + return prices.reduce((sum, price) => sum + price, 0); + } +``` + +## TypeScript functions + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('Checkout.ts')->slice()->function('parse'); +echo $slice->content(); +``` + +**Result** + +```typescript +export function parse(raw: string): T { + return JSON.parse(raw) as T; +} +``` + +The selected code retains generics, parameter types, and return types. Abstract methods, +interface members, and overload signatures without bodies are not selectable methods. +This is source selection; Code Slicer does not type-check or execute TypeScript. + +`beforeMethod()`, `afterMethod()`, `beforeNextMethod()`, and `beforeNextClass()` also work +with JavaScript and TypeScript. See the [PHP boundary example](php.md#continue-after-a-method). + +Selection uses the first matching name fully inside the current slice. Names +are case-sensitive. A missing declaration raises `SourceSymbolNotFound`; +check its name and the containing class or block before retrying. + +See [selection rules](../selectors.md) or return to [Languages](index.md). diff --git a/docs/languages/twig.md b/docs/languages/twig.md new file mode 100644 index 0000000..f57fec8 --- /dev/null +++ b/docs/languages/twig.md @@ -0,0 +1,166 @@ +# Twig selectors + +Select a named block with `block()` or a macro with `macro()`. Code Slicer finds the matching +closing tag and returns the complete declaration, including multiline content. + +The PHP selections below assume [installation](../installation.md) is complete +and Composer's autoloader is loaded. Save each input under the filename shown before running its PHP selection. +The examples pass `twig` explicitly so the template language is unambiguous. + +## Select a multiline block + +**Source: `checkout.html.twig`** + +```twig +{% extends 'base.html.twig' %} + +{% block title %} + {% if cart.items is not empty %} + Your order ({{ cart.items|length }} items) + {% else %} + Your cart is empty + {% endif %} +{% endblock title %} + +{% block body %} +
+ {% block summary %} + {{ cart.total|number_format(2) }} EUR + {% endblock %} +

Review your order before paying.

+
+{% endblock body %} + +{% macro price(amount) %} + {{ amount|number_format(2) }} EUR +{% endmacro %} +``` + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('checkout.html.twig', 'twig')->slice()->block('title'); +echo $slice->content(); +``` + +**Result** + +```twig +{% block title %} + {% if cart.items is not empty %} + Your order ({{ cart.items|length }} items) + {% else %} + Your cart is empty + {% endif %} +{% endblock title %} +``` + +Both `{% endblock %}` and `{% endblock title %}` close the block. Its range starts on source +line 3 and ends on line 9. The caller only supplies `title`. + +## Keep nested blocks inside their parent + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('checkout.html.twig', 'twig')->slice()->block('body'); +echo $slice->content(); +``` + +**Result** + +```twig +{% block body %} +
+ {% block summary %} + {{ cart.total|number_format(2) }} EUR + {% endblock %} +

Review your order before paying.

+
+{% endblock body %} +``` + +The inner `endblock` closes `summary`. The parent selection continues through `endblock body`. +To select just the nested block, narrow again: + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('checkout.html.twig', 'twig')->slice()->block('body')->block('summary'); +echo $slice->content(); +``` + +**Result** + +```twig + {% block summary %} + {{ cart.total|number_format(2) }} EUR + {% endblock %} +``` + +## Select a macro + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('checkout.html.twig', 'twig')->slice()->macro('price'); +echo $slice->content(); +``` + +**Result** + +```twig +{% macro price(amount) %} + {{ amount|number_format(2) }} EUR +{% endmacro %} +``` + +## Shorthand blocks + +A block with its value on the opening tag has no separate `endblock`. + +**Source: `title.html.twig`** + +```twig +{% extends 'base.html.twig' %} + +{% block title 'Your order' %} + +{% block body %}Review your order.{% endblock %} +``` + +**Selection** + +```php +use Alto\Code\Slicer\CodeSource; + +$slice = CodeSource::fromFile('title.html.twig', 'twig')->slice()->block('title'); +echo $slice->content(); +``` + +**Result** + +```twig +{% block title 'Your order' %} +``` + +Twig comments attached immediately above a block or macro are included. Closing tags inside +comments or quoted expressions do not close the surrounding block. + +`block('title')` addresses Twig `{% block title %}` syntax. Symfony UX `` +and component tags currently use the [text and line selectors](index.md#text-and-lines), which do not +match nested elements structurally. + +Selection uses the first matching name fully inside the current slice. Names +are case-sensitive. A missing declaration raises `SourceSymbolNotFound`; +check its name and the containing class or block before retrying. + +See [selection rules](../selectors.md) or return to [Languages](index.md). diff --git a/docs/public-api.md b/docs/public-api.md deleted file mode 100644 index cfda275..0000000 --- a/docs/public-api.md +++ /dev/null @@ -1,46 +0,0 @@ -# Public API - -## `CodeSource` - -Create a source with `CodeSource::fromFile()` or `CodeSource::fromString()`. Read its complete -content, language, name, and line count with `content()`, `language()`, `name()`, and `lineCount()`. -Pass a language slug such as `php`, or a `Language` object, to declare the language explicitly. - -Create selections with `slice()` or `lines()`. - -An explicitly declared PHP source can contain a complete tagged document or PHP code without an -opening tag. - -## `CodeSlice` - -Every selector returns a new immutable slice. - -Text and line selectors: - -- `lines()` -- `after()` -- `before()` - -PHP selectors: - -- `class()` -- `method()` -- `beforeNextClass()` -- `beforeNextMethod()` -- `beforeMethod()` -- `afterMethod()` - -JavaScript and TypeScript also provide `function()`. CSS provides `rule()` and `atRule()`. Twig -provides `block()` and `macro()`. - -Read the result with `content()`, `language()`, `sourceName()`, `startLine()`, `endLine()`, and -`lineCount()`. Use `source()` and `range()` when a downstream consumer needs the complete parsing -context and selected byte offsets. - -## Selection rules - -- Line numbers are one-based and inclusive. -- Byte ranges are zero-based, start-inclusive, and end-exclusive. -- Searches stay inside the current slice. -- Structural selectors use the first matching symbol in the current slice. -- Missing text, symbols, capabilities, and invalid ranges throw dedicated exceptions. diff --git a/docs/selectors.md b/docs/selectors.md new file mode 100644 index 0000000..12c5cef --- /dev/null +++ b/docs/selectors.md @@ -0,0 +1,142 @@ +# Selectors + +Use `CodeSource` to hold complete input and `CodeSlice` to select an immutable +range within it. Both belong to `Alto\Code\Slicer`. `Language` below means +`Alto\Language\Language`; exceptions belong to `Alto\Code\Slicer\Exception`. + +All offsets count bytes, not Unicode characters. Line numbers are one-based +and refer to the complete source, even after several selections. No selector +writes a file or executes the selected code. + +## Create a source + +The factories return a `CodeSource` containing the complete input: + +| Signature | Parameters and behavior | Exceptions | +| --- | --- | --- | +| `CodeSource::fromFile(string $path, Language\|string\|null $language = null): CodeSource` | Read `$path` immediately. With `null`, detect the language from its filename; detection may return `null`. An explicit language is a registered slug or `Language` object. | `SourceFileNotReadable` if the file cannot be read; `UnknownSourceLanguage` for an unknown explicit slug. | +| `CodeSource::fromString(string $code, Language\|string\|null $language = null, ?string $name = null): CodeSource` | Keep `$code` verbatim. `$name` is an optional label, not a file to read. With `null`, no language is inferred from the content or name. | `UnknownSourceLanguage` for an unknown explicit slug. | + +A missing language still permits line and text selection. Structural selection +requires a supported language. Explicit PHP input accepts code with or without +an opening `content()` and then project +its result onto `range()`, preserving context around the excerpt. + +## Source ranges + +`SourceRange` is a readonly value with constructor +`__construct(int $start, int $end)`. + +The public integer properties `start` and `end` are respectively inclusive +and exclusive. `start` must be non-negative and `end` must be at least `start`; +otherwise construction throws `InvalidSourceRange`. Equality represents an +empty range. A standalone range does not validate against a source length. + +`contains(SourceRange $range): bool` returns whether both boundaries fit +inside the receiver, including an empty range at either boundary. Obtain +source-validated ranges from slices. Direct `CodeSlice` construction and +methods marked `@internal` are not public entry points. + +## Handle failures + +Use the exception category to decide whether to fix the request or select a +fallback: + +| Exception | Base class | Caller action | +| --- | --- | --- | +| `SourceFileNotReadable` | `RuntimeException` | Check the input path and read permissions. | +| `UnknownSourceLanguage` | `InvalidArgumentException` | Correct the explicit language slug. | +| `UnsupportedSourceLanguage` | `LogicException` | Choose a supported structural selector or use text selection. | +| `InvalidSourceRange` | `InvalidArgumentException` | Check line numbers and current slice boundaries. | +| `SourceTextNotFound` | `RuntimeException` | Check the literal text and current range. | +| `SourceSymbolNotFound` | `RuntimeException` | Check the name, language, and containing scope. | +| `InvalidArgumentException` | PHP built-in | Supply non-empty search text. | + +Return to [Getting started](getting-started.md) for a complete first example, +or [Languages](languages/index.md) to choose a structural selector.