From 5d4278a8a3f4bb4adf51de6e253b669e4a23cc22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sat, 19 Sep 2026 23:01:34 +0200 Subject: [PATCH] docs: update structure --- README.md | 25 +++- docs/api/documents.md | 39 ------ docs/api/editing.md | 38 ------ docs/api/extensions.md | 31 ----- docs/api/index.md | 32 ----- docs/api/queries.md | 37 ----- docs/{engine => }/compliance.md | 2 +- docs/conversion.md | 21 +++ docs/conversion/html.md | 2 +- docs/documents.md | 40 ++++++ docs/documents/editing.md | 7 +- docs/{api => documents}/nodes.md | 4 +- docs/{api/exceptions.md => errors.md} | 4 +- docs/extensions.md | 51 +++++++ docs/extensions/compatibility.md | 24 ++-- docs/extensions/index.md | 53 -------- docs/getting-started.md | 13 +- docs/index.md | 97 +++----------- docs/installation.md | 2 +- docs/{engine => }/performance.md | 0 docs/quality.md | 18 +++ docs/rendering.md | 19 +++ .../code-block-titles.md | 0 .../content-slicer.md | 0 .../default-attributes.md | 0 .../external-links.md | 0 .../heading-levels.md | 0 .../heading-permalinks.md | 0 .../link-rewriting.md | 0 .../table-of-contents.md | 0 docs/resources.md | 16 +++ docs/{extensions => resources}/embeds.md | 0 docs/{extensions => resources}/import.md | 8 ++ docs/{extensions => resources}/include.md | 8 ++ docs/{extensions => resources}/source.md | 8 ++ docs/{engine => }/security.md | 4 +- docs/syntax.md | 18 +++ docs/{extensions => syntax}/attributes.md | 0 .../description-lists.md | 0 docs/{extensions => syntax}/footnotes.md | 0 docs/{extensions => syntax}/highlight.md | 0 docs/{extensions => syntax}/mentions.md | 0 .../paired-delimiters.md | 0 .../smart-punctuation.md | 0 docs/{extensions => syntax}/tabs.md | 0 .../ExtensionDocumentationTest.php | 126 ------------------ tests/Edit/ReadmeEditSuiteTest.php | 4 +- 47 files changed, 285 insertions(+), 466 deletions(-) delete mode 100644 docs/api/documents.md delete mode 100644 docs/api/editing.md delete mode 100644 docs/api/extensions.md delete mode 100644 docs/api/index.md delete mode 100644 docs/api/queries.md rename docs/{engine => }/compliance.md (99%) create mode 100644 docs/conversion.md create mode 100644 docs/documents.md rename docs/{api => documents}/nodes.md (94%) rename docs/{api/exceptions.md => errors.md} (97%) create mode 100644 docs/extensions.md delete mode 100644 docs/extensions/index.md rename docs/{engine => }/performance.md (100%) create mode 100644 docs/quality.md create mode 100644 docs/rendering.md rename docs/{extensions => rendering}/code-block-titles.md (100%) rename docs/{extensions => rendering}/content-slicer.md (100%) rename docs/{extensions => rendering}/default-attributes.md (100%) rename docs/{extensions => rendering}/external-links.md (100%) rename docs/{extensions => rendering}/heading-levels.md (100%) rename docs/{extensions => rendering}/heading-permalinks.md (100%) rename docs/{extensions => rendering}/link-rewriting.md (100%) rename docs/{extensions => rendering}/table-of-contents.md (100%) create mode 100644 docs/resources.md rename docs/{extensions => resources}/embeds.md (100%) rename docs/{extensions => resources}/import.md (82%) rename docs/{extensions => resources}/include.md (86%) rename docs/{extensions => resources}/source.md (86%) rename docs/{engine => }/security.md (98%) create mode 100644 docs/syntax.md rename docs/{extensions => syntax}/attributes.md (100%) rename docs/{extensions => syntax}/description-lists.md (100%) rename docs/{extensions => syntax}/footnotes.md (100%) rename docs/{extensions => syntax}/highlight.md (100%) rename docs/{extensions => syntax}/mentions.md (100%) rename docs/{extensions => syntax}/paired-delimiters.md (100%) rename docs/{extensions => syntax}/smart-punctuation.md (100%) rename docs/{extensions => syntax}/tabs.md (100%) delete mode 100644 tests/Documentation/ExtensionDocumentationTest.php diff --git a/README.md b/README.md index 4b2c858..a29f2ec 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ $label = Markdown::github()->toInlineHtml('Install **Alto**'); ``` See [Installation](docs/installation.md), [HTML](docs/conversion/html.md), -[Profiles](docs/documents/profiles.md), and [Security](docs/engine/security.md) to choose the +[Profiles](docs/documents/profiles.md), and [Security](docs/security.md) to choose the right language and HTML policy. ## Open a document when you need more @@ -118,7 +118,7 @@ if ($file->hasChanges()) { ``` Source ranges use original byte offsets. `toMarkdown()` preserves unchanged -bytes, line endings, and a UTF-8 BOM. Alto rejects an edit when it cannot apply +bytes, line endings, and a UTF-8 BOM. ALTO rejects an edit when it cannot apply it safely under the documented V1 contract. The documentation covers this in more depth: [Queries](docs/documents/queries.md) @@ -133,15 +133,26 @@ to enforce content and style policies. Trusted extensions add custom blocks, leaf inlines, native and link-aware HTML decoration, document render projections, lint, formatting, metrics, heading-level projection, permalinks, and generated tables of contents through -compiled contracts. Read [Extensions](docs/extensions/index.md) and +compiled contracts. Read [Extensions](docs/extensions.md) and [Compatibility](docs/extensions/compatibility.md) for the extension contracts -and migration notes from historical Alto CommonMark extensions. +and migration notes from historical ALTO CommonMark extensions. ## Documentation -- [Documentation index](docs/index.md): browse the complete guide set. -- [API reference](docs/api/index.md) and [Exceptions](docs/api/exceptions.md): the - public surface and recovery contracts. +- [Installation](docs/installation.md): install the package and verify the runtime. +- [Getting started](docs/getting-started.md): render and inspect a document. +- [Conversion](docs/conversion.md): produce HTML or Markdown output. +- [Documents](docs/documents.md): query, measure, and edit source-backed content. +- [Quality](docs/quality.md): lint, fix, and format Markdown. +- [Extensions](docs/extensions.md): choose bundled capabilities or create one. +- [Syntax](docs/syntax.md): add authoring syntax. +- [Rendering](docs/rendering.md): control document and HTML output. +- [Resources](docs/resources.md): resolve imported, included, and embedded content. +- [Security](docs/security.md): define HTML, input, and resource boundaries. +- [Errors](docs/errors.md): recover from parse, edit, resource, and file failures. +- [Performance](docs/performance.md): select and measure the processing path. +- [Compliance](docs/compliance.md): review supported Markdown specifications. +- [Documentation index](docs/index.md): browse every guide. ## Contributing diff --git a/docs/api/documents.md b/docs/api/documents.md deleted file mode 100644 index edeb978..0000000 --- a/docs/api/documents.md +++ /dev/null @@ -1,39 +0,0 @@ -# Documents API - -Factories create one of two document contracts: an in-memory -`MarkdownDocument` or a path-backed `MarkdownFile`. - -## MarkdownFactory - -| Method | Return | Purpose | -| --- | --- | --- | -| `profile()` | `Profile` | Return the compiled language profile. | -| `with(ExtensionInterface ...$extensions)` | `self` | Return a factory with trusted extensions. | -| `fromString(string $source, ?ParseOptions $options = null)` | `MarkdownDocument` | Parse an in-memory document. | -| `open(string $path, ?ParseOptions $options = null)` | `MarkdownFile` | Read and parse a file. | -| `toHtml(...)` | `string` | Render block Markdown directly. | -| `toInlineHtml(...)` | `string` | Render an inline fragment directly. | -| `builder()` | `MarkdownBuilder` | Start a document builder. | -| `fragment()` | `MarkdownFragmentBuilder` | Start a fragment builder. | - -## MarkdownDocument - -The document exposes its `profile()` and `model()`, typed accessors, `query()`, -`ensure()`, `lint()`, `fix()`, `format()`, and `stats()`. Output methods are -`toMarkdown()` and `toHtml()`. `hasChanges()` and `diff()` inspect pending -source edits. - -```php -$document = $factory->fromString($source); - -if ($document->hasChanges()) { - echo $document->diff()->toUnifiedString(); -} -``` - -## MarkdownFile - -`MarkdownFile` adds `path()`, `save()`, and `saveAs()`. Saving may reject a -changed, missing, symbolic-link, or unauthorized target. See -[Editing](editing.md) for persistence contracts and [Exceptions](exceptions.md) -for recoverable failures. diff --git a/docs/api/editing.md b/docs/api/editing.md deleted file mode 100644 index 5753b75..0000000 --- a/docs/api/editing.md +++ /dev/null @@ -1,38 +0,0 @@ -# Editing API - -Editing APIs either build new Markdown or record validated patches against an -existing source. - -## Builders - -`MarkdownBuilder` provides `heading()`, `h1()`, `h2()`, `paragraph()`, -`codeBlock()`, `unorderedList()`, `orderedList()`, `blockquote()`, and -`thematicBreak()`. Finish with `document()` or `toMarkdown()`. - -```php -$markdown = $factory->builder() - ->h1('Guide') - ->paragraph('Start here.') - ->toMarkdown(); -``` - -`MarkdownFragmentBuilder` creates content intended for insertion rather than a -standalone document. - -## Pending patches - -Typed handle mutations update the in-memory model immediately and add source -operations to the document journal. `toMarkdown()` lowers pending operations; -`diff()` returns a `Diff` whose `toUnifiedString()` method is suitable for a -preview. - -## SaveOptions - -`MarkdownFile::save()` replaces the opened file. `saveAs()` targets another -path. `SaveOptions` controls compare-before-write and related persistence -policies. Atomic replacement, permission preservation, line-ending retention, -and conflict checks remain available through the file contract. - -Catch the file exceptions described in [Exceptions](exceptions.md) at the -application boundary. See [Editing](../documents/editing.md) for complete -source-preserving workflows. diff --git a/docs/api/extensions.md b/docs/api/extensions.md deleted file mode 100644 index ec4373b..0000000 --- a/docs/api/extensions.md +++ /dev/null @@ -1,31 +0,0 @@ -# Extensions API - -An extension has a stable name and implements only the public capability -interfaces required by its behavior. - -| Capability | Public contract | -| --- | --- | -| Syntax | `BlockExtensionInterface`, `InlineExtensionInterface` | -| HTML | `HtmlDecoratorExtensionInterface` | -| Projection | `DocumentTransformExtensionInterface` | -| Lint | `LintExtensionInterface` | -| Format | `FormatterExtensionInterface` | -| Statistics | `StatsExtensionInterface` | -| Resources | `ResourceResolver` | - -Definitions, contexts, and value objects under `Alto\Markdown\Extension` are -public unless marked `@internal`. Parser tapes, compact node IDs, compiler -caches, and internal dispatch objects are not extension contracts. - -```php -$extended = $factory->with($firstExtension, $secondExtension); -``` - -The returned factory is immutable. Extension compilation rejects invalid -names, duplicate or incompatible definitions, and unavailable parser triggers -before processing a document. - -Read [Extension points](../extensions/extension-points.md) to choose a contract, -[Custom extension](../extensions/custom.md) for an implementation, and -[Compatibility](../extensions/compatibility.md) before declaring a package -version range. diff --git a/docs/api/index.md b/docs/api/index.md deleted file mode 100644 index 95eec51..0000000 --- a/docs/api/index.md +++ /dev/null @@ -1,32 +0,0 @@ -# API reference - -The public API is organized by application task. Types marked `@internal` in -source are not part of the package contract. - -## Entry points - -`Markdown::commonmark()`, `Markdown::gfm()`, and `Markdown::github()` return a -`MarkdownFactory` for the selected syntax profile. A factory can render a -string directly, create a document, open a file, start a builder, or install -trusted extensions. - -```php -use Alto\Markdown\Markdown; - -$factory = Markdown::github(); -$document = $factory->fromString("# Guide\n"); -``` - -## Reference domains - -- [Documents](documents.md): `MarkdownFactory`, `MarkdownDocument`, and - `MarkdownFile`. -- [Nodes](nodes.md): headings, sections, links, images, code, and front matter. -- [Queries](queries.md): collections, generic selectors, and statistics. -- [Editing](editing.md): builders, source patches, diffs, and persistence. -- [Extensions](extensions.md): profiles and public extension capabilities. -- [Exceptions](exceptions.md): argument, parse, render, resource, and file - failures. - -Use the guide pages for decisions and workflows. These reference pages define -callable contracts and observable boundaries. diff --git a/docs/api/queries.md b/docs/api/queries.md deleted file mode 100644 index 6703b65..0000000 --- a/docs/api/queries.md +++ /dev/null @@ -1,37 +0,0 @@ -# Queries API - -Documents provide typed accessors for common questions and `MarkdownQuery` for -core or extension node kinds. - -## Typed accessors - -`title()`, `headings()`, `section()`, `sections()`, `links()`, `images()`, -`codeBlocks()`, and `frontMatter()` return typed handles or lazy collections. - -## Collection - -`Collection` implements `Countable`, `IteratorAggregate`, and lazy operations: - -| Method | Result | -| --- | --- | -| `first()` | First item or `null`. | -| `count()` | Number of matching items. | -| `all()` | Materialized result array. | -| `filter()` | Derived lazy collection. | - -## MarkdownQuery - -`kind()` adds an OR node-kind selector. `where()` adds an AND predicate. -`get()` returns matching handles in source order. - -```php -$tablesAndParagraphs = $document->query() - ->kind('paragraph') - ->kind('gfm:table') - ->get(); -``` - -`stats()` returns `DocumentStats`, including headings, sections, links, images, -code, words, reading time, tables, and extension metrics. See -[Queries](../documents/queries.md) and [Statistics](../documents/statistics.md) -for selection and counting semantics. diff --git a/docs/engine/compliance.md b/docs/compliance.md similarity index 99% rename from docs/engine/compliance.md rename to docs/compliance.md index ddfb7be..cc0f144 100644 --- a/docs/engine/compliance.md +++ b/docs/compliance.md @@ -25,7 +25,7 @@ The published package carries the conformance fixtures and runs them with the rest of the suite: ```bash -composer tests +composer test ``` The full quality gate also runs static analysis and style checks on top of the diff --git a/docs/conversion.md b/docs/conversion.md new file mode 100644 index 0000000..3d61bd0 --- /dev/null +++ b/docs/conversion.md @@ -0,0 +1,21 @@ +# Conversion + +Convert Markdown directly when the application only needs one output. Open a +document first when the same parsed structure will also be queried, edited, +linted, or rendered more than once. + +```php +use Alto\Markdown\Markdown; + +$html = Markdown::github()->toHtml("# Guide\n\nRead **carefully**.\n"); +``` + +## Output + +- [HTML](conversion/html.md): render blocks or inline fragments with an explicit + HTML policy. +- [Markdown](conversion/markdown.md): preserve original source or produce + normalized Markdown. + +Direct conversion avoids retaining a document object. Document output preserves +unchanged source bytes until an operation explicitly normalizes them. diff --git a/docs/conversion/html.md b/docs/conversion/html.md index d500ccf..f7bfb35 100644 --- a/docs/conversion/html.md +++ b/docs/conversion/html.md @@ -46,5 +46,5 @@ The default policy escapes raw HTML and filters unsafe URL schemes. Pass a `RenderOptions` value only when the application has an explicit reason to use another policy. -Read [Security](../engine/security.md) before enabling authored HTML. Continue +Read [Security](../security.md) before enabling authored HTML. Continue with [Markdown](markdown.md) when output must remain Markdown. diff --git a/docs/documents.md b/docs/documents.md new file mode 100644 index 0000000..0c0c51b --- /dev/null +++ b/docs/documents.md @@ -0,0 +1,40 @@ +# Documents + +A document retains the parsed model and original source. Use it when an +application needs to inspect, reuse, measure, or change Markdown without +rewriting unrelated content. + +```php +use Alto\Markdown\Markdown; + +$document = Markdown::github()->fromString("# Guide\n\nRead the docs.\n"); +$title = $document->title()?->text(); +``` + +`$title` is `Guide`. + +## Guides + +- [Profiles](documents/profiles.md): choose CommonMark, GFM, or GitHub syntax. +- [Queries](documents/queries.md): find headings, sections, links, and code. +- [Statistics](documents/statistics.md): compute an immutable document summary. +- [Editing](documents/editing.md): make source-preserving changes and save files. +- [Nodes](documents/nodes.md): inspect and mutate typed handles. + +## Entry points + +| Method | Return | Purpose | +| --- | --- | --- | +| `profile()` | `Profile` | Return the compiled language profile. | +| `with(ExtensionInterface ...$extensions)` | `self` | Return a factory with trusted extensions. | +| `fromString(string $source, ?ParseOptions $options = null)` | `MarkdownDocument` | Parse an in-memory document. | +| `open(string $path, ?ParseOptions $options = null)` | `MarkdownFile` | Read and parse a file. | +| `toHtml(...)` | `string` | Render block Markdown directly. | +| `toInlineHtml(...)` | `string` | Render an inline fragment directly. | +| `builder()` | `MarkdownBuilder` | Start a document builder. | +| `fragment()` | `MarkdownFragmentBuilder` | Start a fragment builder. | + +`MarkdownDocument` exposes typed accessors, `query()`, `ensure()`, `lint()`, +`fix()`, `format()`, `stats()`, `toMarkdown()`, and `toHtml()`. +`MarkdownFile` adds `path()`, `save()`, and `saveAs()`. See +[Errors](errors.md) for recoverable parse, edit, resource, and file failures. diff --git a/docs/documents/editing.md b/docs/documents/editing.md index 1c7f837..447251e 100644 --- a/docs/documents/editing.md +++ b/docs/documents/editing.md @@ -27,6 +27,7 @@ The result changes only the section body: ## Install Run Composer. + ``` Available typed edits: @@ -218,6 +219,10 @@ Compare-before-write protects against silently replacing bytes changed by another editor or process. It is optimistic conflict detection, not a filesystem lock. +After a conflict, keep the pending diff for review, reopen the current file, +and reapply the intended change against its updated content. Do not disable +comparison merely to force a stale write over another editor's changes. + Protected `saveAs()` can create a new target but refuses to replace an existing file that this document did not open. The default comparison option remains `false`, so enable it explicitly when concurrent changes are possible. @@ -252,5 +257,5 @@ mode handling is platform-limited. Use `atomic: false` only when retaining the existing inode and its metadata is more important than all-or-nothing replacement. A non-atomic write can leave partial content after an I/O failure. -See [Exceptions](../api/exceptions.md) for conflicts, stale handles, missing sections, and +See [Errors](../errors.md) for conflicts, stale handles, missing sections, and unsupported edits. diff --git a/docs/api/nodes.md b/docs/documents/nodes.md similarity index 94% rename from docs/api/nodes.md rename to docs/documents/nodes.md index 7676c66..337c3d5 100644 --- a/docs/api/nodes.md +++ b/docs/documents/nodes.md @@ -1,4 +1,4 @@ -# Nodes API +# Nodes Typed handles expose semantic values and the mutations Alto can lower safely to source patches. @@ -27,4 +27,4 @@ query the current document again. Generic block movement and insertion accept only direct children of the document root. Typed mutations retain their own documented boundaries. See -[Editing](../documents/editing.md) for worked operations. +[Editing](editing.md) for worked operations. diff --git a/docs/api/exceptions.md b/docs/errors.md similarity index 97% rename from docs/api/exceptions.md rename to docs/errors.md index 0bf15d1..6bf9527 100644 --- a/docs/api/exceptions.md +++ b/docs/errors.md @@ -63,7 +63,7 @@ Use the precise subtype when its recovery is different: Parser limit subclasses expose the configured and attempted values. File exceptions expose the original application path through `path`. Resource limits and their exact counting rules are documented in -[Security](../engine/security.md#bound-parsing-and-file-access). +[Security](security.md#bound-parsing-and-file-access). Unknown generic node kinds also raise an Alto domain exception. Construct factories with custom extensions during application startup rather than after @@ -122,5 +122,5 @@ own symlink policy. Existing non-regular targets are rejected. A relative path is anchored to its canonical parent when opened or adopted by `saveAs()`, but that does not replace application authorization. Permission inspection or restoration failures abort atomic replacement and preserve the pending diff. -See [Editing](../documents/editing.md#preview-and-save) for the complete +See [Editing](documents/editing.md#preview-and-save) for the complete persistence contract. diff --git a/docs/extensions.md b/docs/extensions.md new file mode 100644 index 0000000..8e171c6 --- /dev/null +++ b/docs/extensions.md @@ -0,0 +1,51 @@ +# Extensions + +Extensions add trusted syntax, rendering, transformations, analysis, or +resource-backed behavior to an immutable factory profile. Install only the +capabilities required by the document format. + +```php +use Alto\Markdown\Extension\Footnote\FootnoteExtension; +use Alto\Markdown\Extension\Highlight\HighlightExtension; +use Alto\Markdown\Markdown; + +$factory = Markdown::github()->with( + new FootnoteExtension(), + new HighlightExtension(), +); +``` + +Choose bundled extensions by outcome: + +- [Syntax](syntax.md) adds author-facing Markdown constructs. +- [Rendering](rendering.md) projects or decorates document output. +- [Resources](resources.md) resolves bounded external content. + +## Development + +- [Compatibility](extensions/compatibility.md): understand extension versioning. +- [Extension points](extensions/extension-points.md): select the correct contract. +- [Custom extension](extensions/custom.md): register trusted application behavior. + +## Capabilities + +An extension has a stable name and implements only the public capabilities its +behavior requires. + +| Capability | Public contract | +| --- | --- | +| Syntax | `BlockExtensionInterface`, `InlineExtensionInterface` | +| HTML | `HtmlDecoratorExtensionInterface` | +| Projection | `DocumentTransformExtensionInterface` | +| Lint | `LintExtensionInterface` | +| Format | `FormatterExtensionInterface` | +| Statistics | `StatsExtensionInterface` | +| Resources | `ResourceResolver` | + +Definitions, contexts, and value objects under `Alto\Markdown\Extension` are +public unless marked `@internal`. Parser tapes, compact node IDs, compiler +caches, and internal dispatch objects are not extension contracts. + +The returned factory is immutable. Extension compilation rejects invalid +names, duplicate or incompatible definitions, and unavailable parser triggers +before processing a document. Extensions execute as trusted application code. diff --git a/docs/extensions/compatibility.md b/docs/extensions/compatibility.md index 2d87ea7..70ae6b3 100644 --- a/docs/extensions/compatibility.md +++ b/docs/extensions/compatibility.md @@ -1,6 +1,6 @@ # Compatibility -Alto extensions depend only on Alto's public contracts. Extensions written for +ALTO extensions depend only on ALTO's public contracts. Extensions written for League CommonMark, Parsedown, or the historical `alto/commonmark` packages use different parser and renderer APIs. They cannot be passed directly to `MarkdownFactory::with()`. @@ -62,7 +62,7 @@ Alto does not emit deprecation warnings from parser or renderer hot paths. Security fixes may narrow unsafe behavior in a patch release when preserving it would keep users exposed. Such changes require an explicit security notice. -Package authors should test the lowest and newest Alto versions allowed by +Package authors should test the lowest and newest ALTO versions allowed by their Composer constraint. Invalid definitions and callback results raise `InvalidExtensionException`; arbitrary exceptions thrown by application callbacks remain unchanged. @@ -90,19 +90,19 @@ Map new block syntax to `BlockExtensionInterface`, leaf inline syntax to `HtmlDecoratorExtensionInterface`, and document-wide output decisions to `DocumentTransformExtensionInterface`. Do not adapt or expose the foreign AST. -Every historical Alto CommonMark feature has a native replacement: +Every historical ALTO CommonMark feature has a native replacement: | Historical package | Native Alto replacement | Migration note | | --- | --- | --- | -| `alto/commonmark-code-block-title` | [`CodeBlockTitleExtension`](index.md) | Use the typed title policy | -| `alto/commonmark-content-slicer` | [`ContentSlicerExtension`](index.md) | `minLevel` is the first heading level that opens a section | -| `alto/commonmark-heading-level` | [`HeadingLevelExtension`](index.md) | Use `HeadingLevelPolicy::map()`, `shift()`, or `using()` | -| `alto/commonmark-import` | [`ImportExtension`](index.md) | Inject a bounded `ResourceResolver` | -| `alto/commonmark-include` | [`IncludeExtension`](index.md) | Inject a resolver and explicit recursion policy | -| `alto/commonmark-link-rewriter` | [`LinkRewriterExtension`](index.md) | Build rules with `LinkRewriter` | -| `alto/commonmark-source` | [`SourceExtension`](index.md) | Inject a bounded resolver | -| `alto/commonmark-table-of-contents` | [`TableOfContentsExtension`](index.md) | Use the typed TOC policy | -| `alto/commonmark-tabs` | [`TabsExtension`](index.md) | Review generated HTML and progressive behavior | +| `alto/commonmark-code-block-title` | [`CodeBlockTitleExtension`](../rendering/code-block-titles.md) | Use the typed title policy | +| `alto/commonmark-content-slicer` | [`ContentSlicerExtension`](../rendering/content-slicer.md) | `minLevel` is the first heading level that opens a section | +| `alto/commonmark-heading-level` | [`HeadingLevelExtension`](../rendering/heading-levels.md) | Use `HeadingLevelPolicy::map()`, `shift()`, or `using()` | +| `alto/commonmark-import` | [`ImportExtension`](../resources/import.md) | Inject a bounded `ResourceResolver` | +| `alto/commonmark-include` | [`IncludeExtension`](../resources/include.md) | Inject a resolver and explicit recursion policy | +| `alto/commonmark-link-rewriter` | [`LinkRewriterExtension`](../rendering/link-rewriting.md) | Build rules with `LinkRewriter` | +| `alto/commonmark-source` | [`SourceExtension`](../resources/source.md) | Inject a bounded resolver | +| `alto/commonmark-table-of-contents` | [`TableOfContentsExtension`](../rendering/table-of-contents.md) | Use the typed TOC policy | +| `alto/commonmark-tabs` | [`TabsExtension`](../syntax/tabs.md) | Review generated HTML and progressive behavior | These are feature migrations, not drop-in class aliases. Re-run output, security, and source-preservation tests when moving an application. diff --git a/docs/extensions/index.md b/docs/extensions/index.md deleted file mode 100644 index e75d7c7..0000000 --- a/docs/extensions/index.md +++ /dev/null @@ -1,53 +0,0 @@ -# Extensions - -Extensions add trusted syntax, rendering, transformations, analysis, or -resource-backed behavior to an immutable factory profile. Install only the -capabilities required by the document format. - -```php -use Alto\Markdown\Extension\Footnote\FootnoteExtension; -use Alto\Markdown\Extension\Highlight\HighlightExtension; -use Alto\Markdown\Markdown; - -$factory = Markdown::github()->with( - new FootnoteExtension(), - new HighlightExtension(), -); -``` - -## Authoring syntax - -- [Paired delimiters](paired-delimiters.md): configurable leaf delimiter pairs. -- [Smart punctuation](smart-punctuation.md): quotes, dashes, and ellipses. -- [Highlight](highlight.md): highlighted inline text. -- [Description lists](description-lists.md): terms and definitions. -- [Footnotes](footnotes.md): definitions and references. -- [Tabs](tabs.md): nested tab groups. -- [Mentions](mentions.md): application-resolved references. -- [Attributes](attributes.md): constrained source attributes. - -## Documents and HTML - -- [Heading levels](heading-levels.md): project heading levels at render time. -- [Content slicer](content-slicer.md): group heading sections. -- [Heading permalinks](heading-permalinks.md): add stable heading links. -- [Table of contents](table-of-contents.md): generate a document outline. -- [Default attributes](default-attributes.md): add controlled HTML attributes. -- [Code block titles](code-block-titles.md): render code-block titles. -- [External links](external-links.md): mark external links. -- [Link rewriting](link-rewriting.md): rewrite link destinations. - -## External resources - -- [Import](import.md): insert escaped source code. -- [Include](include.md): expand bounded Markdown resources. -- [Source](source.md): display source excerpts. -- [Embeds](embeds.md): resolve allowlisted rich content. - -Resource-backed extensions require an injected `ResourceResolver`; installing -an extension never grants filesystem or network authority by itself. Read -[Security](../engine/security.md) before enabling them on untrusted input. - -Use [Extension points](extension-points.md) to choose a public contract, -[Custom extension](custom.md) for a minimal implementation, and -[Compatibility](compatibility.md) before publishing an extension package. diff --git a/docs/getting-started.md b/docs/getting-started.md index c0a135d..69b3fda 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -5,7 +5,8 @@ without parsing the source twice. ## Create a document -Start from a Composer application with `alto/markdown` installed: +Start from a Composer application with `alto/markdown` installed. Save this +as `render.php` beside `vendor/` and run `php render.php`: ```php links()->count()."\n"; echo $document->toHtml(); ``` -The first two lines are `Project` and `1`. The remaining output is the rendered -heading and paragraph. +The complete output is: + +```text +Project +1 +

Project

+

Read the guide.

+``` `github()` enables CommonMark, GitHub Flavored Markdown, GitHub alerts, and front matter. The returned document retains the parsed structure and original diff --git a/docs/index.md b/docs/index.md index 471eef6..fc03fac 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ -# Alto Markdown +# ALTO Markdown -Alto Markdown converts Markdown to safe HTML and keeps a source-backed +ALTO Markdown converts Markdown to safe HTML and keeps a source-backed document model when an application needs to inspect, lint, or edit content. Use direct conversion for one result; open a document when later work depends on its structure. @@ -11,80 +11,25 @@ use Alto\Markdown\Markdown; $html = Markdown::github()->toHtml("# Hello\n\nWelcome.\n"); ``` -## Start +The conversion returns `

Hello

` followed by a paragraph containing +`Welcome.`. Raw HTML is escaped and unsafe URL schemes are rejected by default. + +## Documentation - [Installation](installation.md): install the package and verify the runtime. - [Getting started](getting-started.md): render and inspect one document. - -## Conversion - -- [HTML](conversion/html.md): render block or inline Markdown safely. -- [Markdown](conversion/markdown.md): preserve source or normalize output. - -## Documents - -- [Profiles](documents/profiles.md): choose CommonMark, GFM, or GitHub syntax. -- [Queries](documents/queries.md): find headings, sections, links, and code. -- [Statistics](documents/statistics.md): compute one immutable document summary. -- [Editing](documents/editing.md): make source-preserving changes and save files. - -## Quality - -- [Linting](quality/linting.md): report content and policy problems. -- [Fixing](quality/fixing.md): apply enabled safe corrections. -- [Formatting](quality/formatting.md): normalize selected Markdown style. - -## Extension API - -- [All extensions](extensions/index.md): choose bundled capabilities. -- [Compatibility](extensions/compatibility.md): understand extension versioning. -- [Extension points](extensions/extension-points.md): select the correct contract. -- [Custom extension](extensions/custom.md): register trusted application behavior. - -## Syntax - -- [Paired delimiters](extensions/paired-delimiters.md): define a safe inline syntax. -- [Smart punctuation](extensions/smart-punctuation.md): replace quotes, dashes, and ellipses. -- [Highlight](extensions/highlight.md): mark inline text with paired delimiters. -- [Description lists](extensions/description-lists.md): render terms and definitions. -- [Footnotes](extensions/footnotes.md): connect references to endnotes. -- [Tabs](extensions/tabs.md): group nested content into accessible panels. -- [Mentions](extensions/mentions.md): resolve application-specific references. -- [Attributes](extensions/attributes.md): attach constrained attributes to elements. - -## Rendering - -- [Heading levels](extensions/heading-levels.md): project headings to different HTML levels. -- [Content slicer](extensions/content-slicer.md): wrap heading sections in semantic elements. -- [Permalinks](extensions/heading-permalinks.md): add stable links to headings. -- [Contents](extensions/table-of-contents.md): build a document outline. -- [Default attributes](extensions/default-attributes.md): add controlled HTML attributes. -- [Code titles](extensions/code-block-titles.md): label fenced code blocks. -- [External links](extensions/external-links.md): classify and mark outbound links. -- [Link rewriting](extensions/link-rewriting.md): transform destinations at render time. - -## Resources - -- [Import](extensions/import.md): insert escaped source code. -- [Include](extensions/include.md): expand bounded Markdown resources. -- [Source](extensions/source.md): display annotated source excerpts. -- [Embeds](extensions/embeds.md): resolve allowlisted rich content. - -## Engine - -- [Security](engine/security.md): keep untrusted input within safe policies. -- [Performance](engine/performance.md): measure the correct processing lane. -- [Compliance](engine/compliance.md): review supported Markdown specifications. - -## API - -- [Overview](api/index.md): map the public surface by task. -- [Documents](api/documents.md): factories, documents, and files. -- [Nodes](api/nodes.md): inspect and mutate typed handles. -- [Queries](api/queries.md): collections, selectors, and statistics. -- [Editing](api/editing.md): builders, patches, diffs, and saves. -- [Extensions](api/extensions.md): profile and extension contracts. -- [Exceptions](api/exceptions.md): catch recoverable failures at boundaries. - -HTML escapes authored raw HTML and rejects unsafe URL schemes by default. -Read [Security](engine/security.md) before changing those policies. +- [Conversion](conversion.md): produce HTML or Markdown output. +- [Documents](documents.md): inspect, query, measure, and edit parsed documents. +- [Quality](quality.md): lint, fix, and format Markdown. +- [Extensions](extensions.md): choose or create trusted capabilities. +- [Syntax](syntax.md): add authoring syntax. +- [Rendering](rendering.md): control document and HTML output. +- [Resources](resources.md): resolve imported, included, and embedded content. +- [Security](security.md): define input, HTML, and resource boundaries. +- [Errors](errors.md): catch recoverable failures. +- [Performance](performance.md): select and measure the correct processing path. +- [Compliance](compliance.md): review supported Markdown specifications. + +The core has no runtime Composer dependencies. It supports CommonMark, GitHub +Flavored Markdown, and GitHub-oriented documents. Extensions run as trusted +application code and receive resource access only through an injected resolver. diff --git a/docs/installation.md b/docs/installation.md index 47ce1c1..bedbca0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,6 +1,6 @@ # Installation -Install Alto Markdown in a PHP 8.4 or newer application, then run one smoke +Install ALTO Markdown in a PHP 8.4 or newer application, then run one smoke test to confirm that Composer can load the package. ## Requirements diff --git a/docs/engine/performance.md b/docs/performance.md similarity index 100% rename from docs/engine/performance.md rename to docs/performance.md diff --git a/docs/quality.md b/docs/quality.md new file mode 100644 index 0000000..7fb1f61 --- /dev/null +++ b/docs/quality.md @@ -0,0 +1,18 @@ +# Quality + +Quality tools report content problems, apply explicit safe fixes, and normalize +selected style without silently rewriting unrelated Markdown. + +```php +$report = $document->lint($config); +$document->fix($config); +$document->format(); +``` + +## Tools + +- [Linting](quality/linting.md): report content and policy problems. +- [Fixing](quality/fixing.md): apply fixes attached to enabled rules. +- [Formatting](quality/formatting.md): normalize selected Markdown style. + +Preview pending changes with `diff()` before saving a file. diff --git a/docs/rendering.md b/docs/rendering.md new file mode 100644 index 0000000..18139a8 --- /dev/null +++ b/docs/rendering.md @@ -0,0 +1,19 @@ +# Rendering + +Rendering extensions project or decorate a parsed document without changing its +stored Markdown. Use them to control generated structure, links, headings, and +attributes. + +## Extensions + +- [Heading levels](rendering/heading-levels.md): project headings to different HTML levels. +- [Content slicer](rendering/content-slicer.md): wrap heading sections in semantic elements. +- [Permalinks](rendering/heading-permalinks.md): add stable links to headings. +- [Contents](rendering/table-of-contents.md): build a document outline. +- [Default attributes](rendering/default-attributes.md): add controlled HTML attributes. +- [Code titles](rendering/code-block-titles.md): label fenced code blocks. +- [External links](rendering/external-links.md): classify and mark outbound links. +- [Link rewriting](rendering/link-rewriting.md): transform destinations at render time. + +These projections affect rendered output. `toMarkdown()` continues to preserve +the source unless a separate edit or formatting operation changes it. diff --git a/docs/extensions/code-block-titles.md b/docs/rendering/code-block-titles.md similarity index 100% rename from docs/extensions/code-block-titles.md rename to docs/rendering/code-block-titles.md diff --git a/docs/extensions/content-slicer.md b/docs/rendering/content-slicer.md similarity index 100% rename from docs/extensions/content-slicer.md rename to docs/rendering/content-slicer.md diff --git a/docs/extensions/default-attributes.md b/docs/rendering/default-attributes.md similarity index 100% rename from docs/extensions/default-attributes.md rename to docs/rendering/default-attributes.md diff --git a/docs/extensions/external-links.md b/docs/rendering/external-links.md similarity index 100% rename from docs/extensions/external-links.md rename to docs/rendering/external-links.md diff --git a/docs/extensions/heading-levels.md b/docs/rendering/heading-levels.md similarity index 100% rename from docs/extensions/heading-levels.md rename to docs/rendering/heading-levels.md diff --git a/docs/extensions/heading-permalinks.md b/docs/rendering/heading-permalinks.md similarity index 100% rename from docs/extensions/heading-permalinks.md rename to docs/rendering/heading-permalinks.md diff --git a/docs/extensions/link-rewriting.md b/docs/rendering/link-rewriting.md similarity index 100% rename from docs/extensions/link-rewriting.md rename to docs/rendering/link-rewriting.md diff --git a/docs/extensions/table-of-contents.md b/docs/rendering/table-of-contents.md similarity index 100% rename from docs/extensions/table-of-contents.md rename to docs/rendering/table-of-contents.md diff --git a/docs/resources.md b/docs/resources.md new file mode 100644 index 0000000..a8c47aa --- /dev/null +++ b/docs/resources.md @@ -0,0 +1,16 @@ +# Resources + +Resource extensions resolve external content through an application-provided +`ResourceResolver`. Installing an extension never grants filesystem or network +authority by itself. + +## Extensions + +- [Import](resources/import.md): insert escaped source code. +- [Include](resources/include.md): expand bounded Markdown resources. +- [Source](resources/source.md): display annotated source excerpts. +- [Embeds](resources/embeds.md): resolve allowlisted rich content. + +Set origin, size, recursion, and content-type limits in the resolver. Read +[Security](security.md) before enabling resource-backed behavior on untrusted +input, and [Errors](errors.md) for resolution failures. diff --git a/docs/extensions/embeds.md b/docs/resources/embeds.md similarity index 100% rename from docs/extensions/embeds.md rename to docs/resources/embeds.md diff --git a/docs/extensions/import.md b/docs/resources/import.md similarity index 82% rename from docs/extensions/import.md rename to docs/resources/import.md index 82b8cb9..ce00122 100644 --- a/docs/extensions/import.md +++ b/docs/resources/import.md @@ -70,3 +70,11 @@ Resolution happens once while parsing; later renders perform no I/O. The resolver defines the available resources. `FilesystemResourceResolver` accepts relative paths only, rejects parent traversal and symlink components, and enforces both the extension allowlist and `maxBytes`. + +## When a resource is rejected + +Check that the requested path is relative to the configured resolver root, +uses an allowed extension, and fits the byte limit. Verify the file exists and +is readable. Keep the root bounded when correcting a rejected path; do not +broaden filesystem authority to accept untrusted references. See +[Security](../security.md) and [Errors](../errors.md). diff --git a/docs/extensions/include.md b/docs/resources/include.md similarity index 86% rename from docs/extensions/include.md rename to docs/resources/include.md index 7951b69..7cebc9a 100644 --- a/docs/extensions/include.md +++ b/docs/resources/include.md @@ -89,3 +89,11 @@ raw HTML, or custom containers remain literal. The original `@include` directive is preserved when the document is rendered back to Markdown. Included HTML follows the active HTML policy. + +## When a resource is rejected + +Check that the requested path is relative to the configured resolver root, +uses an allowed extension, and fits the byte limit. Verify the file exists and +is readable. Keep the root bounded when correcting a rejected path; do not +broaden filesystem authority to accept untrusted references. See +[Security](../security.md) and [Errors](../errors.md). diff --git a/docs/extensions/source.md b/docs/resources/source.md similarity index 86% rename from docs/extensions/source.md rename to docs/resources/source.md index 6c3d49b..a3f9b18 100644 --- a/docs/extensions/source.md +++ b/docs/resources/source.md @@ -78,3 +78,11 @@ literal and perform no I/O. The resolver defines the available resources. `FilesystemResourceResolver` accepts relative paths only, rejects parent traversal and symlink components, and enforces both the extension allowlist and `maxBytes`. + +## When a resource is rejected + +Check that the requested path is relative to the configured resolver root, +uses an allowed extension, and fits the byte limit. Verify the file exists and +is readable. Keep the root bounded when correcting a rejected path; do not +broaden filesystem authority to accept untrusted references. See +[Security](../security.md) and [Errors](../errors.md). diff --git a/docs/engine/security.md b/docs/security.md similarity index 98% rename from docs/engine/security.md rename to docs/security.md index 03e8088..63075d0 100644 --- a/docs/engine/security.md +++ b/docs/security.md @@ -230,5 +230,5 @@ On POSIX, atomic replacement preserves permission and special mode bits or fails before replacement. It does not preserve ACLs, ownership, extended attributes, or other platform metadata. New files use `0666 & ~umask`. Windows replacement and mode semantics are platform-limited and may fail when -the destination exists or is open. See [Exceptions](../api/exceptions.md) for recovery and -[Editing](../documents/editing.md) for the complete save flow. +the destination exists or is open. See [Errors](errors.md) for recovery and +[Editing](documents/editing.md) for the complete save flow. diff --git a/docs/syntax.md b/docs/syntax.md new file mode 100644 index 0000000..f1c1599 --- /dev/null +++ b/docs/syntax.md @@ -0,0 +1,18 @@ +# Syntax + +Syntax extensions add author-facing Markdown constructs to a trusted factory +profile. Enable only the constructs accepted by the document format. + +## Extensions + +- [Paired delimiters](syntax/paired-delimiters.md): define safe inline delimiter pairs. +- [Smart punctuation](syntax/smart-punctuation.md): replace quotes, dashes, and ellipses. +- [Highlight](syntax/highlight.md): mark inline text with paired delimiters. +- [Description lists](syntax/description-lists.md): render terms and definitions. +- [Footnotes](syntax/footnotes.md): connect references to endnotes. +- [Tabs](syntax/tabs.md): group nested content into accessible panels. +- [Mentions](syntax/mentions.md): resolve application-specific references. +- [Attributes](syntax/attributes.md): attach constrained attributes to elements. + +Each guide shows installation, configuration, Markdown input, HTML output, +options, and its security boundary. diff --git a/docs/extensions/attributes.md b/docs/syntax/attributes.md similarity index 100% rename from docs/extensions/attributes.md rename to docs/syntax/attributes.md diff --git a/docs/extensions/description-lists.md b/docs/syntax/description-lists.md similarity index 100% rename from docs/extensions/description-lists.md rename to docs/syntax/description-lists.md diff --git a/docs/extensions/footnotes.md b/docs/syntax/footnotes.md similarity index 100% rename from docs/extensions/footnotes.md rename to docs/syntax/footnotes.md diff --git a/docs/extensions/highlight.md b/docs/syntax/highlight.md similarity index 100% rename from docs/extensions/highlight.md rename to docs/syntax/highlight.md diff --git a/docs/extensions/mentions.md b/docs/syntax/mentions.md similarity index 100% rename from docs/extensions/mentions.md rename to docs/syntax/mentions.md diff --git a/docs/extensions/paired-delimiters.md b/docs/syntax/paired-delimiters.md similarity index 100% rename from docs/extensions/paired-delimiters.md rename to docs/syntax/paired-delimiters.md diff --git a/docs/extensions/smart-punctuation.md b/docs/syntax/smart-punctuation.md similarity index 100% rename from docs/extensions/smart-punctuation.md rename to docs/syntax/smart-punctuation.md diff --git a/docs/extensions/tabs.md b/docs/syntax/tabs.md similarity index 100% rename from docs/extensions/tabs.md rename to docs/syntax/tabs.md diff --git a/tests/Documentation/ExtensionDocumentationTest.php b/tests/Documentation/ExtensionDocumentationTest.php deleted file mode 100644 index c433c96..0000000 --- a/tests/Documentation/ExtensionDocumentationTest.php +++ /dev/null @@ -1,126 +0,0 @@ - - */ - private const array EXTENSIONS = [ - 'attributes', - 'code-block-titles', - 'content-slicer', - 'default-attributes', - 'description-lists', - 'embeds', - 'external-links', - 'footnotes', - 'heading-levels', - 'heading-permalinks', - 'highlight', - 'import', - 'include', - 'link-rewriting', - 'mentions', - 'paired-delimiters', - 'smart-punctuation', - 'source', - 'table-of-contents', - 'tabs', - ]; - - #[DataProvider('provideExtensions')] - public function testEveryExtensionHasACompleteDocumentationPage(string $slug): void - { - $contents = $this->read('docs/extensions/' . $slug . '.md'); - - self::assertMatchesRegularExpression('/\A# [^\n]+\n\n\S/', $contents); - self::assertSame( - ['Install', 'Configure', 'Markdown', 'HTML', 'Options', 'Security'], - array_slice($this->levelTwoHeadings($contents), 0, 6), - ); - self::assertStringContainsString("```bash\ncomposer require alto/markdown\n```", $contents); - self::assertMatchesRegularExpression('/```markdown\n.+?\n```/s', $contents); - self::assertMatchesRegularExpression('/```html\n.+?\n```/s', $contents); - } - - #[DataProvider('provideExtensions')] - public function testEveryExtensionIsLinkedFromBothIndexes(string $slug): void - { - self::assertMatchesRegularExpression( - '/\]\(extensions\/' . $slug . '\.md\)/', - $this->read('docs/index.md'), - ); - self::assertMatchesRegularExpression( - '/\]\(' . $slug . '\.md\)/', - $this->read('docs/extensions/index.md'), - ); - } - - /** - * @return iterable - */ - public static function provideExtensions(): iterable - { - foreach (self::EXTENSIONS as $slug) { - yield $slug => [$slug]; - } - } - - private function read(string $path): string - { - $contents = file_get_contents(\dirname(__DIR__, 2) . '/' . $path); - - self::assertIsString($contents, $path); - - return $contents; - } - - /** - * @return list - */ - private function levelTwoHeadings(string $contents): array - { - $headings = []; - $fenceCharacter = null; - $fenceLength = 0; - - foreach (explode("\n", $contents) as $line) { - if (null !== $fenceCharacter) { - if (1 === preg_match('/^ {0,3}' . preg_quote($fenceCharacter, '/') . '{' . $fenceLength . ',}[ \t]*$/', $line)) { - $fenceCharacter = null; - } - - continue; - } - - if (1 === preg_match('/^ {0,3}(`{3,}|~{3,})/', $line, $matches)) { - $fenceCharacter = $matches[1][0]; - $fenceLength = \strlen($matches[1]); - - continue; - } - - if (1 === preg_match('/^## ([^#].*)$/', $line, $matches)) { - $headings[] = $matches[1]; - } - } - - return $headings; - } -} diff --git a/tests/Edit/ReadmeEditSuiteTest.php b/tests/Edit/ReadmeEditSuiteTest.php index 35a7db2..e07f6d5 100644 --- a/tests/Edit/ReadmeEditSuiteTest.php +++ b/tests/Edit/ReadmeEditSuiteTest.php @@ -235,8 +235,8 @@ private function fuzzCorpus(): array 'docs/quality/linting.md', 'docs/quality/fixing.md', 'docs/quality/formatting.md', - 'docs/extensions/index.md', - 'docs/engine/security.md', + 'docs/extensions.md', + 'docs/security.md', ]; }