From cf18b0d534e6e1069f4f2a196c5216da2ba50e06 Mon Sep 17 00:00:00 2001 From: Mc-Zen <52877387+Mc-Zen@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:10:24 +0100 Subject: [PATCH] [docs] update to 0.4.0 (#38) --- README.md | 75 ++++--- docs/migration-to-0.4.0.md | 133 +++++++++++ docs/template.typ | 27 ++- docs/tidy-guide.typ | 242 +++++++++++++-------- examples/example-demo.typ | 9 +- examples/funny-math/funny-math-complex.typ | 18 +- examples/funny-math/funny-math.typ | 21 +- examples/sincx.typ | 11 +- examples/wiggly-doc.typ | 2 +- src/helping.typ | 16 +- src/new-parser.typ | 133 ++++++----- src/old-parser.typ | 4 +- src/parse-module.typ | 28 ++- src/show-example.typ | 7 +- src/show-module.typ | 4 +- src/styles/default.typ | 11 +- src/styles/help.typ | 6 +- src/styles/minimal.typ | 11 +- src/testing.typ | 33 +-- src/utilities.typ | 34 ++- tests/helping/ref/1.png | Bin 20692 -> 20554 bytes tests/new-parser/currying.typ | 56 ++++- tests/old-parser/currying.typ | 58 +++++ tests/old-parser/parse-module.typ | 2 +- tests/old-parser/test.typ | 3 +- tests/test_parse.typ | 3 +- typst.toml | 4 +- 27 files changed, 680 insertions(+), 271 deletions(-) create mode 100644 docs/migration-to-0.4.0.md create mode 100644 tests/old-parser/currying.typ diff --git a/README.md b/README.md index 5abacbd..d142f9e 100644 --- a/README.md +++ b/README.md @@ -2,27 +2,36 @@ # Tidy *Keep it tidy.* -[![Typst Package](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FMc-Zen%2Ftidy%2Fmain%2Ftypst.toml&query=%24.package.version&prefix=v&logo=typst&label=package&color=239DAD)](https://typst.app/universe/package/tidy) +[![Typst Package](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FMc-Zen%2Ftidy%2Fv0.4.0%2Ftypst.toml&query=%24.package.version&prefix=v&logo=typst&label=package&color=239DAD)](https://typst.app/universe/package/tidy) [![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/Mc-Zen/tidy/blob/main/LICENSE) +[![Test Status](https://github.com/Mc-Zen/tidy/actions/workflows/run_tests.yml/badge.svg)](https://github.com/Mc-Zen/tidy/actions/workflows/run_tests.yml) [![User Manual](https://img.shields.io/badge/manual-.pdf-purple)][guide] -**tidy** is a package that generates documentation directly in [Typst](https://typst.app/) for your Typst modules. It parses docstring comments similar to javadoc and co. and can be used to easily build a beautiful reference section for the parsed module. Within the docstring you may use (almost) any Typst syntax − so markup, equations and even figures are no problem! + + +**tidy** is a package that generates documentation directly in [Typst](https://typst.app/) for your Typst modules. It parses doc-comments and can be used to easily build a reference section for a module. Doc-comments use Typst syntax − so markup, equations and even figures are no problem! + +> [!IMPORTANT] +> In version 0.4.0, the default documentation syntax has changed. You can take a look at the [migration guide][migration guide] or revert to the old syntax with `tidy.show-module(old-syntax: true, ...)`. +> +> You can still find the documentation for the old syntax in the [0.3.0 user guide](https://github.com/Mc-Zen/tidy/releases/download/v0.3.0/tidy-guide.pdf). Features: - **Customizable** output styles. -- Automatically [**render code examples**](#example). +- Automatically [**preview code examples**](#example). - **Annotate types** of parameters and return values. +- **Cross-references** to definitions and function parameters. - Automatically read off default values for named parameters. - [**Help** feature](#generate-a-help-command-for-you-package) for your package. -- [Docstring tests](#docstring-tests). +- [Doc-tests](#doc-tests). -The [guide][guide] fully describes the usage of this module and defines the format for the docstrings. +The [guide][guide] fully describes the usage of this module and defines documentation syntax. ## Usage -Using `tidy` is as simple as writing some docstrings and calling: +Using `tidy` is as simple as writing some doc-comments and calling: ```typ #import "@preview/tidy:0.3.0" @@ -30,7 +39,7 @@ Using `tidy` is as simple as writing some docstrings and calling: #tidy.show-module(docs, style: tidy.styles.default) ``` -The available predefined styles are currenty `tidy.styles.default` and `tidy.styles.minimal`. Custom styles can be added by hand (take a look at the [guide][guide]). +The available predefined styles are currently `tidy.styles.default` and `tidy.styles.minimal`. Custom styles can be added by hand (take a look at the [user guide][guide]). ## Example @@ -39,23 +48,28 @@ A full example on how to use this module for your own package (maybe even consis ```typ /// This function computes the cardinal sine, $sinc(x)=sin(x)/x$. /// -/// #example(`#sinc(0)`, mode: "markup") +/// ```example +/// #sinc(0) +/// ``` /// -/// - x (int, float): The argument for the cardinal sine function. /// -> float -#let sinc(x) = if x == 0 {1} else {calc.sin(x) / x} +#let sinc( + /// The argument for the cardinal sine function. + /// -> int | float + x +) = if x == 0 {1} else {calc.sin(x) / x} ``` **tidy** turns this into: -

+
Tidy example output -

+ ## Access user-defined functions and images -The code in the docstrings is evaluated via `eval()`. In order to access user-defined functions and images, you can make use of the `scope` argument of `tidy.parse-module()`: +The code in the doc-comments is evaluated through the [`eval`](https://typst.app/docs/reference/foundations/eval/) function. In order to access user-defined functions and images, you can make use of the `scope` argument of `tidy.parse-module()`: ```typ #{ @@ -69,10 +83,10 @@ The code in the docstrings is evaluated via `eval()`. In order to access user-de ) } ``` -The docstrings in `my-module.typ` may now access the image with `#img` and can call any function or variable from `my-module` in the style of `#my-module.my-function()`. This makes rendering examples right in the docstrings as easy as a breeze! +The doc-comments in `my-module.typ` may now access the image with `#img` and can call any function or variable from `my-module` in the style of `#my-module.my-function()`. This makes rendering examples right in the doc-comments as easy as a breeze! ## Generate a help command for you package -With **tidy**, you can add a help command to you package that allows users to obtain the documentation of a specific definition or parameter right in the document. This is similar to CLI-style help commands. If you have already written docstrings for your package, it is quite low-effort to add this feature. Once set up, the end-user can use it like this: +With **tidy**, you can add a help command to you package that allows users to obtain the documentation of a specific definition or parameter right in the document. This is similar to CLI-style help commands. If you have already written doc-comments for your package, it is quite low-effort to add this feature. Once set up, the end-user can use it like this: ```typ // happily coding, but how do I use this one complex function again? @@ -81,10 +95,10 @@ With **tidy**, you can add a help command to you package that allows users to ob #mypackage.help("func(param1)") // print only parameter description of param1 ``` -This will print the documentation of `func` directly into the document — no need to look it up in a manual. Read up in the [guide][guide] for setup instructions. +This will print the documentation of `func` directly into the document — no need to look it up in a manual. Read up on setup instructions in the [user guide][guide]. -## Docstring tests -It is possible to add simple docstring tests — assertions that will be run when the documentation is generated. This is useful if you want to keep small tests and documentation in one place. +## Doc-tests +It is possible to add simple doc-tests — assertions that will be run when the documentation is generated. This is useful if you want to keep small tests and documentation in one place. ```typ /// #test( /// `num.my-square(2) == 4`, @@ -92,18 +106,27 @@ It is possible to add simple docstring tests — assertions that will be run whe /// ) #let my-square(n) = n * n ``` -With the short-hand syntax, a unfulfilled assertion will even print the line number of the failed test: + +A few test assertion functions are available to improve readability, simplicity, and error messages. Currently, these are `eq(a, b)` for equality tests, `ne(a, b)` for inequality tests and `approx(a, b, eps: 1e-10)` for floating point comparisons. These assertion helper functions are always available within doc-comment tests. ## Changelog +### v0.4.0 +_Major redesign of the documentation syntax_ +- New features + - New parser for the new documentation syntax. The old parser is still available and can be activated via `tidy.show-module(old-syntax: true)`. There is a [migration guide][migration guide] for adopting the new syntax. + - Cross-references to function arguments. + - Support for detecting _curried functions_, i.e., function aliases with prepended arguments using the `.with()` function. + + ### v0.3.0 +_Adds a help feature and more options_ - New features: - Help feature. - `preamble` option for examples (e.g., to add `import` statements). @@ -118,9 +141,9 @@ A few test assertion functions are available to improve readability, simplicity, ### v0.2.0 - New features: - - Add executable examples to docstrings. + - Add executable examples to doc-comments. - Documentation for variables (as well as functions). - - Docstring tests. + - Doc-tests. - Rainbow-colored types `color` and `gradient`. - Improvements: - Allow customization of cross-references through `show-reference()`. @@ -135,6 +158,8 @@ A few test assertion functions are available to improve readability, simplicity, ### v0.1.0 -Initial Release. +_Initial Release_ + +[guide]: https://github.com/Mc-Zen/tidy/releases/download/v0.4.0/tidy-guide.pdf -[guide]: https://github.com/Mc-Zen/tidy/releases/download/v0.3.0/tidy-guide.pdf \ No newline at end of file +[migration guide]: https://github.com/Mc-Zen/tidy/tree/v0.4.0/docs/migration-to-0.4.0.md \ No newline at end of file diff --git a/docs/migration-to-0.4.0.md b/docs/migration-to-0.4.0.md new file mode 100644 index 0000000..6be2640 --- /dev/null +++ b/docs/migration-to-0.4.0.md @@ -0,0 +1,133 @@ +# Migration guide from 0.3.0 to 0.4.0 (new parser) + + +If you choose to use the new documentation parser, this guide helps you with migrating your existing documentation to the new documentation syntax. Of course, you can also keep using the old syntax which will still be around for some time. It can be activated via `tidy.show-module(old-syntax: true, ...)`. + +## Breaking changes + +Below you can find an overview over the breaking changes that the new syntax introduces. + +- [Documentation of function arguments](#documentation-of-function-arguments) +- [Cross-references](#cross-references) +- [Example previews](#example-previews) (not strictly a breaking change) +- [Doc-comment tests](#doc-comment-tests) + + +### Documentation of function arguments + +In Tidy 0.3.0 and earlier, function arguments were all documented in a dedicated block that was part of the description doc-comment for the function, see below: +```typ +/// This function computes the cardinal sine, $sinc(x)=sin(x)/x$. +/// +/// - x (int, float): The argument for the cardinal sine function. +/// -> float +#let sinc(x) = if x == 0 {1} else {calc.sin(x) / x} +``` + +With the new syntax, the parameter description is moved right in front of the parameter declaration. The name of the parameter can thus be removed from the description. The type, however, is now annotated (until Typst provides built-in support for type annotations) just like the return type of the function itself with a trailing `->` expression. Also, multiple accepted types should now be separated with a pipe `|` operator instead of a comma (this also applies to the function return type). + +The previous example thus becomes + +```typ +/// This function computes the cardinal sine, $sinc(x)=sin(x)/x$. +/// +/// -> float +#let sinc( + /// The argument for the cardinal sine function. + /// -> int | float + x +) = if x == 0 {1} else {calc.sin(x) / x} +``` +Note that the trailing type annotation does not need to be on its own line. + + +### Cross-references + +In the old documentation style, there was a dedicated syntax for cross-references: the `@@` prefix. The new style uses just plain Typst references starting with a single `@`. In the case of cross-referencing a function, parentheses are never placed after the function name. *This is a breaking change to before when these parentheses were obligatory*. + +In addition, it is now possible to reference a specific parameter of a function by appending a dot `.` and the parameter name, e.g., `sinc.x`. In order to use parameter references, the utilized template style needs to support them by creating appropriate labels for each parameter. The built-in style templates all support parameter referencing out of the box. + + +### Example previews + +A popular feature of Tidy is the example preview. A function, variable, or parameter description can contain demonstrating code examples that are automatically rendered and displayed side-by-side with the code. This used to be achieved through the `example()` function that Tidy provides since version 0.3.0. + +Although this function is still available, we now encourage users to use a raw element with the language `example` (for Typst markdown mode) or `examplec` (for Typst code mode). + +Thus, instead of +````typ +/// This function computes the cardinal sine, $sinc(x)=sin(x)/x$. +/// +/// #example(`#sinc(0)`, mode: "markup") +.. +```` +we can now simply write + +````typ +/// This function computes the cardinal sine, $sinc(x)=sin(x)/x$. +/// +/// ```example +/// #sinc(0) +/// ``` +.. +```` +or +````typ +/// This function computes the cardinal sine, $sinc(x)=sin(x)/x$. +/// +/// ```examplec +/// sinc(0) +/// ``` +.. +```` + +In all versions, you can insert _hidden_ code lines starting with `>>>` anywhere in the demo code. These lines will just be executed but not displayed. +````typ +/// ```examplec +/// >>> import my-math: sinc // just executed, not shown +/// sinc(0) +/// ``` +```` +This is useful for many scenarios like import statements, wrapping everything inside a container of a fixed size and other things. + +Look at the [default.typ](/src/styles/default.typ) template style for hints on customization of the example preview. + + +## Standalone usage of example previews + +Some people use the example preview feature to add self-compiling code examples independently of Tidy. This used to be possible via the following show rule: +````typ +#show raw: show-example.show-example + +```typ +Hello world +``` +```` +With the new version, this should be replaced with +```typ +#import "@preview/tidy:0.4.0": render-examples +#show: render-examples + +... +``` + +### Scope +It also features a `scope` argument, that can be pre-set: + +````typ +#show: render-examples.with(scope: (answer: 42)) + +```example +#answer +``` +```` + + +### Customization +The output format of the example can be customized through the parameter `layout` of `render-examples`. This parameter takes a `function` with two positional arguments: the `raw` element and the preview. +````typ +#show: render-examples.with(layout: (code, preview) => grid(code, preview)) +```` + +## Doc-comment tests +Doc-comment tests can still be used as before but the short-hand syntax with `>>>` is no longer supported with the new documentation syntax. With `old-parser: true`, it is still available. \ No newline at end of file diff --git a/docs/template.typ b/docs/template.typ index db1cbac..2b81978 100644 --- a/docs/template.typ +++ b/docs/template.typ @@ -21,10 +21,15 @@ // set heading(numbering: (..args) => if args.pos().len() == 1 { numbering("I", ..args) }) set heading(numbering: "I.a") show list: pad.with(x: 5%) + show heading.where(level: 3): set text(1.2em) // show link: set text(fill: purple.darken(30%)) - show link: set text(fill: rgb("#1e8f6f")) - show link: underline + show link: it => { + let dest = str(it.dest) + if "." in dest and not "/" in dest { return underline(it, stroke: luma(60%), offset: 1pt) } + set text(fill: rgb("#1e8f6f")) + underline(it) + } v(4em) @@ -68,8 +73,10 @@ // Main body. set par(justify: true) - v(10em) + v(7em) + pad(x: 10%, outline(depth: 2, indent: 2em)) + pagebreak() show: codly-init.with( ) @@ -97,7 +104,7 @@ stroke: rgb("#239DAE") + 1pt, radius: 3pt, { - block(align(right, text(raw(filename))), width: 100%, inset: 5pt) + block(align(right, text(raw(filename, lang: "cmd"))), width: 100%, inset: 5pt) v(1pt, weak: true) move(dx: -1pt, line(length: 100% + 2pt, stroke: 1pt + rgb("#239DAE"))) v(1pt, weak: true) @@ -106,16 +113,22 @@ )) -#let tidy-output-figure(output) = no-codly({ +#let tidy-output-figure( + output, + breakable: false, + fill: none +) = no-codly({ set heading(numbering: none) set text(size: .8em) - figure(align(left, box( + show figure: set block(breakable: breakable) + figure(align(left, block( width: 80%, + fill: fill, stroke: 0.5pt + luma(200), inset: 20pt, radius: 10pt, block( - breakable: false, + breakable: breakable, output ) ))) diff --git a/docs/tidy-guide.typ b/docs/tidy-guide.typ index 9b51286..c623d42 100644 --- a/docs/tidy-guide.typ +++ b/docs/tidy-guide.typ @@ -1,10 +1,8 @@ #import "template.typ": * #import "/src/tidy.typ" -#include "/tests/parser/test.typ" -#include "/tests/test_tidy.typ" // ensure that tests pass #let version = toml("/typst.toml").package.version -#let import-statement = "#import \"@preview/tidy:" + version + "\"" +#show "tidy:0.0.0": "tidy:" + version #show: project.with( @@ -14,7 +12,7 @@ "Mc-Zen", ), abstract: [ - *tidy* is a package that generates documentation directly in #link("https://typst.app/", [Typst]) for your Typst modules. It parses docstring comments similar to javadoc and co. and can be used to easily build a reference section for each module. + *tidy* is a package that generates documentation directly in #link("https://typst.app/", [Typst]) for your Typst modules. It parses doc-comments and can be used to easily build a reference section for each module. ], date: datetime.today().display("[month repr:long] [day], [year]"), version: version, @@ -22,24 +20,23 @@ ) -#pad(x: 10%, outline(depth: 1)) -#pagebreak() = Introduction -You can easily feed *tidy* your in-code documented source files and get beautiful documentation of all your functions and variables printed out. // Enjoy features like type annotations, links to other documented definitions and arbitrary formatting within function and parameter descriptions. Let's get started. +You can easily feed *tidy* your in-code documented source files and get beautiful documentation of all your functions and variables printed out. The main features are: - Type annotations, -- Seamless cross references, -- Rendering code examples (see @preview-examples), +- seamless cross references, +- rendering code examples (see @preview-examples), - help command generation (see @help-command), and -- Docstring testing (see @docstring-testing). +- doc-comment testing (see @doc-comment-testing). First, we import *tidy*. -#raw(block: true, lang: "typ", import-statement) +```typ +#import "@preview/tidy:0.0.0" +``` We now assume we have a Typst module called `repeater.typ`, containing a definition for a function named `repeat()`. -// *Example of some documented source code:* #let example-code = read("/examples/repeater.typ") #file-code("repeater.typ", raw(block: true, lang: "typ", example-code)) @@ -47,35 +44,13 @@ We now assume we have a Typst module called `repeater.typ`, containing a definit Tidy uses `///` doc-comments for documentation. A function or variable can be provided with a *description* by placing a doc-comment just before the definition. -Until type annotations are natively available in Typst, a return type can be annotated with the `->` syntax in the last line of the description. If more there is more than one possible return type, they can be given separated by the pipe `|` operator, e.g., `-> int, float`. +Until type annotations are natively available in Typst, a return type can be annotated with the `->` syntax in the last line of the description. If there is more than one possible return type, the types can be given separated by the pipe `|` operator, e.g., `-> int | float`. Function arguments are documented in the same way. -All descriptions are parsed as Typst markup. See @user-defined-symbols on how to add images or examples to a description. - - -// A *function* is documented vi -// similar to javadoc by prepending a block of `///` comments. Each line needs to start with three slashes `///` (whitespace is allowed at the beginning of the line). _Parameters_ of the function can be documented by listing them as -// #show raw.where(lang: "markspace"): it => { -// show " ": box(inset: (x: 0.1pt), box( -// fill: red.lighten(70%), -// width: .7em, height: .8em, -// radius: 1pt, -// outset: (bottom: 3pt, top: 1pt), -// )) -// it -// } -// ```markspace -// /// - parameter-name (type): ... -// ``` -// Following this exact form is important (see also the spaces marked in red) since this allows to distinguish the parameter list from ordinary markup lists in the function description or in parameter descriptions. For example, another space in front of the `-` could be added to markup lists if necessary. - -// The possible types for each parameter are given in parentheses and after a colon `:`, the parameter description follows. Indicating a type is mandatory (you may want to pick `any` in some cases). An optional _return type_ can be annotated by ending with a line that contains `->` followed by the return type(s). +All descriptions are parsed as Typst markup. Take a look at @user-defined-symbols on how to add images or examples to a description. -// In front of the parameter list, a _function description_ can be put. -// *Variables* are documented just in the same way (lacking the option to specify parameters). A definition is recognized as a variable if the identifier (variable/function name) is not followed by an opening parenthesis. The `->` syntax which also specifies the return type for functions can be used to define the type of a variable. - -Calling #ref-fn("parse-module()") will read out the documentation of the given string. We can then invoke #ref-fn("show-module()") on the returned docs object. The actual output depends on the utilized style template, see @customizing. +Calling #ref-fn("parse-module()") will read out the documentation of the given string (for example loaded from a file). We can then invoke #ref-fn("show-module()") on the returned docs object. The actual output depends on the utilized style template, see @customizing. ```typ #let docs = tidy.parse-module(read("docs.typ"), name: "Repeater") @@ -84,7 +59,11 @@ Calling #ref-fn("parse-module()") will read out the documentation of the given s This will produce the following output. #tidy-output-figure( - tidy.show-module(tidy.parse-module(example-code, name: "Repeater", old-parser: false), style: tidy.styles.default) + tidy.show-module( + tidy.parse-module(example-code, name: "Repeater", old-syntax: false), + style: tidy.styles.default, + first-heading-level: 3 + ) ) @@ -92,18 +71,17 @@ Cool, he? By default, an outline for all definitions is displayed at the top. This behaviour can be turned off with the parameter `show-outline` of #ref-fn("show-module()"). -There is another nice little feature: in the docstring, you can cross-reference other definitions with the extra syntax `@@repeat()` or `@@awful-pi`. This will automatically create a link that when clicked in the PDF will lead you to the documentation of that definition. +There is another nice little feature: in the doc-comment, you can cross-reference other definitions with the standard Typst syntax for referencing objects, e.g., `@repeat` or `@awful-pi`. This will automatically create a link that when clicked in the PDF will lead you to the documentation of that definition. Parameters of functions can be referenced as `@repeat.num`. -Of course, everything happens instantaneously, so you can see the live result while writing the docs for your package. Keep your code documented! +Of course, compilation happens almost instantaneously, so you can see the live result while writing the docs for your package. Keep your code documented! = More options -Sometimes you want to document "private" functions and variables but omit them in the public documentation. In order to hide all definitions starting with an underscore, you may set `omit-private-definitions` to `true` in the call to #ref-fn("show-module()"). Similarly, "implementation parameters" to otherwise public functions occur once in a while. These are then used internally by the library. In order to conceal such parameters which may lead to confusion with dedicated documentation readers, you can name them with a leading underscore and set `omit-private-parameters` to `true` as well. +Sometimes you might want to document "private" functions and variables but omit them in the public documentation. In order to hide all definitions starting with an underscore, you may set `omit-private-definitions` to `true` in the call to #ref-fn("show-module()"). Similarly, "internal" parameters of otherwise public functions can be concealed by naming them with a leading underscore and setting `omit-private-parameters` to `true` as well. -#pagebreak() = Accessing user-defined symbols @@ -127,11 +105,10 @@ It is even possible to add *entire modules* to the scope which makes rendering e #file-code("wiggly.typ", raw(lang: "typ", block: true, read("/examples/wiggly.typ"))) -#pagebreak() -Note, that we use the predefined function `example()` here to show the code as well as the rendered output of some demo usage of our function. The `example()` function is treated more in-detail in @preview-examples. +Note, that we use the predefined `example` language here to show the code as well as the rendered output of some demo usage of our function. Options for previewing code examples are treated more in-detail in @preview-examples. -We can now parse the module and pass the module `wiggly` through the `scope` parameter. Furthermore, we apply another trick: by specifying a `preamble`, we can add code to run before each example. Here we use this feature to import everything from the module `wiggly`. This way, we can directly write `draw-sine(...)` in the example (instead of `wiggly.draw-sine(...)`): +We can now parse the module and make the module `wiggly` available through the `scope` parameter. Furthermore, we apply another trick: by specifying a `preamble`, we can add code to run before each example. Here we use this feature to import everything from the module `wiggly`. This way, we can directly write `draw-sine(...)` in the example (instead of `wiggly.draw-sine(...)`): ```typ #import "wiggly.typ" // don't import something specific from the module! @@ -152,13 +129,18 @@ In the output, the preview of the code examples is shown next to it. read("/examples/wiggly.typ"), name: "wiggly", scope: (wiggly: wiggly), + label-prefix: "wiggly1-", preamble: "#import wiggly: *\n", - old-parser: false + old-syntax: false ) - tidy-output-figure(tidy.show-module(module, show-outline: false)) + tidy-output-figure(tidy.show-module( + module, + show-outline: false, + break-param-descriptions: true, + first-heading-level: 3 + ), breakable: true) } -#pagebreak() @@ -169,10 +151,34 @@ In the output, the preview of the code examples is shown next to it. = Preview examples -As we saw in the previous section, it is possible with *tidy* to add examples to a docstring and preview it along with its output. -The function `example()` is available in every docstring and has some bells and whistles which are showcased with the following `example-demo.typ` module which contains a function for highlighting text with gradients (seems not very advisable due to the poor readability): +As we already saw in the previous section, a function, variable, or parameter description can contain code examples that are automatically rendered and displayed side-by-side with the code. + +For this purpose the two #raw(lang: "typc", "raw") languages `example` (for Typst markup mode) +````typ +/// ```example +/// #sinc(0) +/// ``` +```` +and `examplec` (for Typst code mode) +````typ +/// ```examplec +/// sinc(0) +/// ``` +```` +are available in all doc-comments. +In both versions, you can insert _hidden_ code lines starting with `>>>` anywhere in the demo code. These lines will just be executed but not displayed. +````typ +/// ```examplec +/// >>> import my-math: sinc // just executed, not shown +/// sinc(0) +/// ``` +```` +This is useful for many scenarios like import statements, wrapping everything inside a container of a fixed size and other things. + +#pagebreak() + +As an alternative, the function `example()` provides some bells and whistles which are showcased with the following `example-demo.typ` module which contains a function for highlighting text with gradients #footnote[which seems not very advisable due to the poor readability.]: -// #file-code("example-demo.typ", raw(lang: "typ", block: true, read("/examples/example-demo.typ"))) #{ set text(size: .89em) @@ -181,35 +187,66 @@ The function `example()` is available in every docstring and has some bells and let module = tidy.parse-module( read("/examples/example-demo.typ"), scope: (example-demo: example-demo), - old-parser: false + old-syntax: false ) tidy-output-figure(tidy.show-module(module, show-outline: false, break-param-descriptions: true)) } +#pagebreak() + +== Standalone usage of example previews +The example preview feature can also be used to add self-compiling code examples independently of *tidy*. For this, *tidy* provides the function #ref-fn("render-examples()"). +````typ +#import "@preview/tidy:0.0.0": render-examples +#show: render-examples + +```example +# +``` +```` +It also features a `scope` argument, that can be pre-set: + +````typ +#show: render-examples.with(scope: (answer: 42)) + +```example +#answer +``` +```` + +Furthermore, the output format of the example can be customized through the parameter `layout` of `render-examples`. This parameter takes a `function` with two positional arguments: the #raw(lang: "typc", "raw") element and the preview. +````typ +#show: render-examples.with( + layout: (code, preview) => grid(code, preview) +) +```` + + +#pagebreak() = Customizing the style There are multiple ways to customize the output style. You can -- pick a different predefined style, +- pick a different predefined style template, - apply show rules before printing the module documentation or -- create an entirely new style. +- create an entirely new style template. -A different predefined style can be selected by passing a style to the `style` parameter: +A different predefined style template can be selected by passing a style to the `style` parameter: ```typ #tidy.show-module( tidy.parse-module(read("my-module.typ")), - style: tidy.styles.minimal + style: tidy.styles.minimal, ) ``` -You can use show rules to customize the document style before calling #ref-fn("show-module()"). Setting any text and paragraph attributes works just out of the box. Furthermore, heading styles can be set to affect the appearance of the module name (relative heading level 1), function or variable names (relative heading level 2) and the word *Parameters* (relative heading level 3), all relative to what is set with the parameter `first-heading-level` of #ref-fn("show-module()"). +You can use show rules to customize the document style template before calling #ref-fn("show-module()"). Setting any text and paragraph attributes works just out of the box. Furthermore, heading styles can be set to affect the appearance of the module name (relative heading level 1), function or variable names (relative heading level 2) and the word *Parameters* (relative heading level 3), all relative to what is set with the parameter `first-heading-level` of #ref-fn("show-module()"). -Finally, if that is not enough, you can design a completely new style. Examples of styles can be found in the folder `src/styles/` in the #link("https://github.com/Mc-Zen/tidy", "GitHub Repository"). +Finally, if that is not enough, you can design a completely new style template. Examples thereof can be found in the folder `src/styles/` in the #link("https://github.com/Mc-Zen/tidy", "GitHub Repository"). == Customizing Colors (mainly for the `default` style) @@ -226,7 +263,6 @@ The `default` theme defines a color scheme `colors-dark` along with the default ``` With a dark background and light text, these colors produce much better contrast than the default colors: #{ - set box(fill: luma(20)) set text(fill: luma(240)) let module = tidy.parse-module( @@ -237,9 +273,18 @@ With a dark background and light text, these colors produce much better contrast amount ) ```.text, - old-parser: false + old-syntax: false + ) + tidy-output-figure( + tidy.show-module( + module, + show-outline: false, + colors: tidy.styles.default.colors-dark, + style: tidy.styles.default, + first-heading-level: 3 + ), + fill: luma(20) ) - tidy-output-figure(tidy.show-module(module, show-outline: false, colors: tidy.styles.default.colors-dark, style: tidy.styles.default)) } #pagebreak() @@ -255,10 +300,18 @@ Currently, the two predefined styles `tidy.styles.default` and `tidy-styles.mini read("/examples/wiggly.typ"), name: "wiggly", scope: (wiggly: wiggly), + label-prefix: "wiggly2-", preamble: "#import wiggly: *\n", - old-parser: false + old-syntax: false + ) + tidy-output-figure( + tidy.show-module( + module, + show-outline: false, + style: tidy.styles.minimal, + first-heading-level: 3 + ) ) - tidy-output-figure(tidy.show-module(module, show-outline: false, style: tidy.styles.minimal)) } @@ -285,18 +338,13 @@ As a demonstration, calling #raw(lang: "typ", "#tidy.help(\"parse-module\")") pr ) } -/* -The feature that will make using *your* package attractive. -Without leaving the editor -The usage and ease of access for typst packages is about to be revolutionized! -*/ This feature supports: - function and variable definitions, - definitions defined in nested submodules, e.g., \ #raw(lang: "typ", "#your-package.help(\"sub.bar.foo\")") - asking only for the parameter description of a function, e.g., \ #raw(lang: "typ", "#your-package.help(\"foo(param1)\")") -- lazy evaluation of docstring processing (even loading `tidy` is made lazy). \ _Don't pay for what you don't use!_ +- lazy evaluation of doc-comment processing (even loading `tidy` is made lazy). \ _Don't pay for what you don't use!_ - search through the entire package documentation, e.g., \ #raw(lang: "typ", "#your-package.help(search: \"module\")") @@ -305,8 +353,10 @@ This feature supports: If you have already documented your code, adding such a help function will require only little further effort in implementation. In your root library file, add some code of the following kind: #raw(block: true, lang: "typ", ``` #let help(..args) = { -```.text + "\n " + import-statement.slice(1) + "\n" + +```.text + ``` + + import "@preview/tidy:0.0.0" let namespace = ( ".": read.with("/src/my-package.typ") ) @@ -331,7 +381,7 @@ let namespace = ( "matrix.solve": read.with("/solve.typ") ) ``` -Since the symbols from `vec.typ` are imported directly into the library (and are accessible through `heymath.vec-add()` and `heymath.vec-subtract()`), we add this file to the root together with the main library file. Both files will be internally concatenated for docstring processing. The content of `matrix.typ`, however, can only be accessed through `heymath.matrix.` (by the user) and so we place `matrix.typ` at the key `matrix`. +Since the symbols from `vec.typ` are imported directly into the library (and are accessible through `heymath.vec-add()` and `heymath.vec-subtract()`), we add this file to the root together with the main library file. Both files will be internally concatenated for doc-comment processing. The content of `matrix.typ`, however, can only be accessed through `heymath.matrix.` (by the user) and so we place `matrix.typ` at the key `matrix`. For nested submodules, write out the complete name "path" for the key. As an example, we have added `matrix.solve` -- a module that would be imported within `matrix.typ` -- to the code sample above. *It is advised not to change the signature of the help function manually in order to keep consistency between different packages using this features*. @@ -357,7 +407,7 @@ The default style for help output should work more or less for light and dark do == Notes about optimization (for package developers) -When set up in the form as shown above, the package `tidy` is only imported when a user calls `help` for the first time and not at all if the feature is not used _(don't pay for what you don't use)_. The files themselves are also only read when a definition from a specific submodule in the "namespace" is requested. In the case of _extremely_ long code files, it _could_ make sense to separate the documentation from the implementation by adding "documentation files" that only contain a _declaration_ plus docstring for each definition -- with the body left empty. +When set up in the form as shown above, the package `tidy` is only imported when a user calls `help` for the first time and not at all if the feature is not used _(don't pay for what you don't use)_. The files themselves are also only read when a definition from a specific submodule in the "namespace" is requested. In the case of _extremely_ long code files, it _could_ make sense to separate the documentation from the implementation by adding "documentation files" that only contain a _declaration_ plus doc-comment for each definition -- with the body left empty. ```typ #let my-really-long-algorithm( /// The inputs for the algorithm. -> array @@ -367,16 +417,16 @@ When set up in the form as shown above, the package `tidy` is only imported when ) = { } ``` -The advantage is that the source code is not as crowded with (sometimes very long) docstrings and that docstring parsing may get faster. On the downside, there is an increased maintenance overhead due to the need of synchronizing the actual file and the documentation file (especially when the interface of a function changes). +The advantage is that the source code is not as crowded with (sometimes very long) doc-comments and that doc-comment parsing may get faster. On the downside, there is an increased maintenance overhead due to the need of synchronizing the actual file and the documentation file (especially when the interface of a function changes). #pagebreak() -= Docstring testing += Doc-comment testing -Tidy supports small-scale docstring tests that are executed automatically and throw appropriate error messages when a test fails. +Tidy supports small-scale doc-comment tests that are executed automatically and throw appropriate error messages when a test fails. -In every docstring, the function #raw(lang: "typc", "test(..tests, scope: (:))") is available. An arbitrary number of tests can be passed in and the evaluation scope may be extended through the `scope` parameter. Any definition exposed to the docstring evaluation context through the `scope` parameter passed to #ref-fn("parse-module()") (see @user-defined-symbols) is also accessible in the tests. Let us create a module `num.typ` with the following content: +In every doc-comment, the function #raw(lang: "typc", "test(..tests, scope: (:))") is available. An arbitrary number of tests can be passed in and the evaluation scope may be extended through the `scope` parameter. Any definition exposed to the doc-comment evaluation context through the `scope` parameter passed to #ref-fn("parse-module()") (see @user-defined-symbols) is also accessible in the tests. Let us create a module `num.typ` with the following content: ```typ /// #test( @@ -386,7 +436,7 @@ In every docstring, the function #raw(lang: "typc", "test(..tests, scope: (:))") #let my-square(n) = n * n ``` -Parsing and showing the module will run the docstring tests. +Parsing and showing the module will run the doc-comment tests. ```typ #import "num.typ" @@ -398,19 +448,20 @@ Parsing and showing the module will run the docstring tests. #tidy.show-module(module) // tests are run here ``` -As alternative to using `test()`, the following dedicated shorthand syntax can be used: +// As alternative to using `test()`, the following dedicated shorthand syntax can be used: -```typ -/// >>> my-square(2) == 4 -/// >>> my-square(4) == 16 -#let my-square(n) = n * n -``` +// ```typ +// /// >>> my-square(2) == 4 +// /// >>> my-square(4) == 16 +// #let my-square(n) = n * n +// ``` -When using the shorthand syntax, the error message even shows the line number of the failed test in the corresponding module. +// When using the shorthand syntax, the error message even shows the line number of the failed test in the corresponding module. -A few test assertion functions are available to improve readability, simplicity and error messages. Currently, these are `eq(a, b)` for equality tests, `ne(a, b)` for inequality tests and `approx(a, b, eps: 1e-10)` for floating point comparisons. These assertion helper functions are always available within docstring tests (with both `test()` and `>>>` syntax). +A few test assertion functions are available to improve readability, simplicity and error messages. Currently, these are `eq(a, b)` for equality tests, `ne(a, b)` for inequality tests and `approx(a, b, eps: 1e-10)` for floating point comparisons. These assertion helper functions are always available within doc-comment tests. +// (with both `test()` and `>>>` syntax). -Docstring tests can be disabled by passing `enable-tests: false` to #ref-fn("show-module()"). +Doc-comment tests can be disabled by passing `enable-tests: false` to #ref-fn("show-module()"). @@ -418,22 +469,30 @@ Docstring tests can be disabled by passing `enable-tests: false` to #ref-fn("sho #pagebreak() = Function documentation -Let us now "self-document" this package: +Let us now _self-document_ this package: #let style = tidy.styles.default #{ - set heading(numbering: none) set text(size: 9pt) + set heading(numbering: none) + show heading.where(level: 3): set text(1.5em) + show heading.where(level: 4): it => { + set text(1.4em) + set align(center) + set block(below: 1.2em) + it + } let module = tidy.parse-module( ( read("/src/parse-module.typ"), read("/src/show-module.typ"), - read("/src/helping.typ") + read("/src/helping.typ"), + read("/src/show-example.typ") ).join("\n"), name: "tidy", require-all-parameters: true, - old-parser: false + old-syntax: false ) tidy.show-module( module, @@ -441,6 +500,7 @@ Let us now "self-document" this package: show-outline: true, sort-functions: false, omit-private-parameters: true, - omit-private-definitions: true + omit-private-definitions: true, + first-heading-level: 3 ) } diff --git a/examples/example-demo.typ b/examples/example-demo.typ index e15be00..a738f6e 100644 --- a/examples/example-demo.typ +++ b/examples/example-demo.typ @@ -22,9 +22,12 @@ /// ```/// #example(`#example-demo.flashy(map: color.map.crest)[Very extremely long examples might maybe require the need of vertical layouting]`, dir: ttb)``` /// #example(`#example-demo.flashy(map: color.map.crest)[Very extremely long examples might maybe require the need of vertical layouting]`, dir: ttb) /// -/// - body (content): -/// - map (array): /// -> content -#let flashy(body, map: color.map.spectral) = highlight( +#let flashy( + /// -> content + body, + /// -> array + map: color.map.spectral +) = highlight( body, fill: gradient.linear(..map) ) \ No newline at end of file diff --git a/examples/funny-math/funny-math-complex.typ b/examples/funny-math/funny-math-complex.typ index 5526ab5..286169b 100644 --- a/examples/funny-math/funny-math-complex.typ +++ b/examples/funny-math/funny-math-complex.typ @@ -2,10 +2,13 @@ /// Construct a complex number of the form /// $ z= a + i b in CC. $ /// -/// - real (float): Real part of the complex number. -/// - imag (float): Imaginary part of the complex number. /// -> float -#let complex(real, imag) = { +#let complex( + /// Real part of the complex number. -> float + real, + /// Imaginary part of the complex number. -> float + imag +) = { (radius * calc.cos(phi), radius * calc.sin(phi)) } @@ -13,9 +16,12 @@ /// Construct a complex number from polar coordinates: @@funny-sqrt() /// $ z= r e^(i phi) in CC. $ /// #image-polar -/// - phi (float): Angle to the real axis. -/// - radius (float): Radius (euclidian distance to the origin). /// -> float -#let polar(phi, radius: 1.0) = { +#let polar( + /// Angle to the real axis. -> float + phi, + /// Radius (euclidian distance to the origin). -> float + radius: 1.0 +) = { (radius * calc.cos(phi), radius * calc.sin(phi)) } \ No newline at end of file diff --git a/examples/funny-math/funny-math.typ b/examples/funny-math/funny-math.typ index 693e02e..790cdcc 100644 --- a/examples/funny-math/funny-math.typ +++ b/examples/funny-math/funny-math.typ @@ -5,9 +5,11 @@ /// /// See also @@funny-sqrt() for computing square roots. /// -/// - phi (float): Angle for the sine function. /// -> float -#let funny-sin(phi) = { calc.sqrt(phi) } +#let funny-sin( + /// Angle for the sine function. -> float + phi +) = { calc.sqrt(phi) } @@ -16,10 +18,17 @@ /// /// /// === Example -/// #example(`funny-math.funny-sqrt(12)`) +/// ```example +/// #funny-math.funny-sqrt(12) +/// ``` /// /// -/// - x (float, int): Argument to take the square root of. For $x=0$, the result is $0$: -/// #example(`funny-math.funny-sqrt(0)`) /// -> float -#let funny-sqrt(x) = { calc.sqrt(x) } +#let funny-sqrt( + /// Argument to take the square root of. For $x=0$, the result is $0$: + /// ```example + /// #funny-math.funny-sqrt(0) + /// ``` + /// -> float | x + x +) = { calc.sqrt(x) } diff --git a/examples/sincx.typ b/examples/sincx.typ index 1a22916..2121373 100644 --- a/examples/sincx.typ +++ b/examples/sincx.typ @@ -1,7 +1,12 @@ /// This function computes the cardinal sine, $sinc(x)=sin(x)/x$. /// -/// #example(`#sinc(0)`, mode: "markup") +/// ```example +/// #sinc(0) +/// ``` /// -/// - x (int, float): The argument for the cardinal sine function. /// -> float -#let sinc(x) = if x == 0 {1} else {calc.sin(x) / x} \ No newline at end of file +#let sinc( + /// The argument for the cardinal sine function. + /// -> int | float + x +) = if x == 0 {1} else {calc.sin(x) / x} \ No newline at end of file diff --git a/examples/wiggly-doc.typ b/examples/wiggly-doc.typ index 16ea284..c17e229 100644 --- a/examples/wiggly-doc.typ +++ b/examples/wiggly-doc.typ @@ -5,6 +5,6 @@ read("/examples/wiggly.typ"), name: "wiggly", scope: (wiggly: wiggly), - preamble: "import wiggly: *;" + preamble: "#import wiggly: *\n" ) #show-module(docs, style: styles.minimal) diff --git a/src/helping.typ b/src/helping.typ index 86d776a..28c4053 100644 --- a/src/helping.typ +++ b/src/helping.typ @@ -16,15 +16,15 @@ ) } -#let parse-namespace-modules(entry, old-parser: false) = { +#let parse-namespace-modules(entry, old-syntax: false) = { // "Module" is made up of several files if type(entry) != array { entry = (entry,) } - parse-module(entry.map(x => x()).join("\n"), old-parser: old-parser, label-prefix: "help-") + parse-module(entry.map(x => x()).join("\n"), old-syntax: old-syntax, label-prefix: "help-") } -#let search-docs(search, searching, namespace, style, old-parser: false) = { +#let search-docs(search, searching, namespace, style, old-syntax: false) = { if search == "" { return help-box(block[_empty search string_]) } let search-names = "n" in searching let search-descriptions = "d" in searching @@ -43,11 +43,11 @@ } let definitions = () - let module = parse-namespace-modules(namespace.at("."), old-parser: old-parser) + let module = parse-namespace-modules(namespace.at("."), old-syntax: old-syntax) let functions = () let variables = () for (name, modules) in namespace { - let module = parse-namespace-modules(modules, old-parser: old-parser) + let module = parse-namespace-modules(modules, old-syntax: old-syntax) functions += module.functions.filter(filter) variables += module.variables.filter(x => search in x.name or search in x.description) @@ -112,7 +112,7 @@ ) let eval-scope = ( - // Predefined functions that may be called by the user in docstring code + // Predefined functions that may be called by the user in doc-comment code example: style-functions.show-example.with( inherited-scope: module.scope ), @@ -230,7 +230,7 @@ /// Whether to use the old parser. /// -> boolean - old-parser: false + old-syntax: false ) = { let validate-namespace-tree(namespace) = { @@ -265,7 +265,7 @@ let name = args.pos().first() help-box(get-docs(name, namespace, package-name, style, onerror: onerror)) } else { - search-docs(search, searching, namespace, style, old-parser: old-parser) + search-docs(search, searching, namespace, style, old-syntax: old-syntax) } } help-function diff --git a/src/new-parser.typ b/src/new-parser.typ index 810c91f..39fdc00 100644 --- a/src/new-parser.typ +++ b/src/new-parser.typ @@ -104,8 +104,8 @@ #let parse-description-and-types(lines, label-prefix: "", first-line-number: 0) = { let description = lines - .enumerate(start: first-line-number) - .map(eval-doc-comment-test.with(label-prefix: label-prefix)) + // .enumerate(start: first-line-number) + // .map(eval-doc-comment-test.with(label-prefix: label-prefix)) .join("\n") if description == none { description = "" } @@ -113,7 +113,7 @@ let types = none if description.contains("->") { let parts = description.split("->") - types = parts.last().replace(",", "|").split("|").map(str.trim) + types = parts.last().split("|").map(str.trim) description = parts.slice(0, -1).join("->") } @@ -214,10 +214,10 @@ let (args, brace-level, processed-chars) = parse-argument-list(state.unfinished-param) if brace-level == -1 { // parentheses are already closed on this line state.state = "finished" - let curry = state.unfinished-param.slice(processed-chars).match(curry-matcher) - if curry != none { - state.curry = (name: curry.captures.first(), rest: state.unfinished-param.slice(processed-chars + curry.end)) - } + // let curry = state.unfinished-param.slice(processed-chars).match(curry-matcher) + // if curry != none { + // state.curry = (name: curry.captures.first(), rest: state.unfinished-param.slice(processed-chars + curry.end)) + // } } if args.len() > 0 and (state.unfinished-param.ends-with(",") or state.state == "finished") { state.params.push((name: args.first(), desc-lines: state.unmatched-description)) @@ -229,23 +229,37 @@ return state } -#{ - let state = ( - state: "running", - params: (), - unmatched-description: (), - unfinished-param: "" +// #{ +// let state = ( +// state: "running", +// params: (), +// unmatched-description: (), +// unfinished-param: "" +// ) +// let lines = ("/// asd", "named: 3,", "/// -> int", "pos", ") = asd.with()", "").rev() +// // let lines = ("/// asd", "named: (", "fill: white, ", "cap: \"butt\")", ")").rev() +// while state.state == "running" and lines.len() > 0 { +// state = parameter-parser(state, lines.pop()) +// } +// let kstate = state +// } + +#let process-curry-info(info) = { + let pos = info.args + .filter(x => x.name.len() == 1) + .map(x => x.name.at(0)) + let named = info.args + .filter(x => x.name.len() == 2) + .map(x => x.name).to-dict() + + ( + name: info.name, + pos: pos, + named: named ) - let lines = ("/// asd", "named: 3,", "/// -> int", "pos", ") = asd.with()", "").rev() - // let lines = ("/// asd", "named: (", "fill: white, ", "cap: \"butt\")", ")").rev() - while state.state == "running" and lines.len() > 0 { - state = parameter-parser(state, lines.pop()) - } - let kstate = state } - #let parse(src) = { let lines = (src.split("\n") + ("",)).map(str.trim) @@ -278,18 +292,19 @@ finished-definition = true curry-info.args = param-parser.params param-parser = param-parser-default + args = () } else { args = param-parser.params if "curry" in param-parser { - let curry = param-parser.curry - curry-info = (name: curry.name) - param-parser = param-parser-default - param-parser.state = "running" - param-parser = parameter-parser(param-parser, curry.rest) - if param-parser.state == "finished" { - finished-definition = true - param-parser = param-parser-default - } + // let curry = param-parser.curry + // curry-info = (name: curry.name) + // param-parser = param-parser-default + // param-parser.state = "running" + // param-parser = parameter-parser(param-parser, curry.rest) + // if param-parser.state == "finished" { + // finished-definition = true + // param-parser = param-parser-default + // } } else { finished-definition = true param-parser = param-parser-default @@ -305,7 +320,7 @@ if name != none { definitions.push((name: name, description: desc-lines, args: args)) if curry-info != none { - definitions.at(-1).curry-info = curry-info + definitions.at(-1).parent = process-curry-info(curry-info) curry-info = none } } @@ -331,9 +346,17 @@ if match.captures.at(1) != "" { // it's a function param-parser.state = "running" param-parser = parameter-parser(param-parser, line.slice(match.end)) - } else { // it's a variable + } else { // it's a variable or a function alias args = none finished-definition = true + let p = line.slice(match.end) + + let curry = line.slice(match.end).match(curry-matcher) + if curry != none { + curry-info = (name: curry.captures.first()) + param-parser = parameter-parser(param-parser, line.slice(match.end + curry.end)) + // param-parser.curry = (name: curry.captures.first(), rest: state.unfinished-param.slice(processed-chars + curry.end)) + } } } @@ -357,30 +380,30 @@ variables: definitions.filter(x => "args" not in x), ) } -#{ +// #{ - let src = ``` - ///Description - let func( - pos, // some comment +// let src = ``` +// ///Description +// let func( +// pos, // some comment - named: 2 // another comment - ) - ```.text - - assert.eq( - parse(src).functions, - ( - ( - name: "func", - description: "Description", - args: ( - pos: (description: ""), - named: (description: "", default: "2"), - ), - return-types: none - ), - ) - ) -} \ No newline at end of file +// named: 2 // another comment +// ) +// ```.text + +// assert.eq( +// parse(src).functions, +// ( +// ( +// name: "func", +// description: "Description", +// args: ( +// pos: (description: ""), +// named: (description: "", default: "2"), +// ), +// return-types: none +// ), +// ) +// ) +// } \ No newline at end of file diff --git a/src/old-parser.typ b/src/old-parser.typ index 09dee77..a42171f 100644 --- a/src/old-parser.typ +++ b/src/old-parser.typ @@ -1,6 +1,6 @@ -// Matches Typst docstring for a function declaration. Example: +// Matches Typst doc-comment for a function declaration. Example: // // // This function does something // // @@ -326,7 +326,7 @@ ) } -/// Parse a function docstring that has been located in the source code with +/// Parse a function doc-comment that has been located in the source code with /// given match. /// /// The return value is a dictionary with the keys diff --git a/src/parse-module.typ b/src/parse-module.typ index d869a91..cedb395 100644 --- a/src/parse-module.typ +++ b/src/parse-module.typ @@ -8,7 +8,7 @@ let docs = function-docs.at(i) if not "parent" in docs { continue } - let parent = docs.parent + let parent = docs.at("parent", default: none) if parent == none { continue } let parent-docs = function-docs.find(x => x.name == parent.name) @@ -40,7 +40,12 @@ } -#let old-parse(content, label-prefix: "", require-all-parameters: false, enable-curried-functions: true) = { +#let old-parse( + content, + label-prefix: "", + require-all-parameters: false, + enable-curried-functions: true +) = { let parse-info = ( label-prefix: label-prefix, @@ -63,7 +68,8 @@ variable-docs.push(doc) } else { doc.parent = parent-info - doc.remove("type") + if "type" in doc { doc.remove("type") } + doc.args = (:) function-docs.push(doc) } } else { @@ -82,7 +88,7 @@ } -/// Parse the docstrings of a typst module. This function returns a dictionary +/// Parse the doc-comments of a typst module. This function returns a dictionary /// with the keys /// - `name`: The module name as a string. /// - `functions`: A list of function documentations as dictionaries. @@ -92,7 +98,7 @@ /// /// The function documentation dictionaries contain the keys /// - `name`: The function name. -/// - `description`: The function's docstring description. +/// - `description`: The function's description. /// - `args`: A dictionary of info objects for each function argument. /// /// These again are dictionaries with the keys @@ -131,11 +137,13 @@ /// -> str preamble: "", + /// Whether to enable the detection of curried functions. /// -> boolean enable-curried-functions: true, + /// Whether to use the old documentation syntax. /// -> boolean - old-parser: true + old-syntax: false ) = { if label-prefix == auto { label-prefix = name + "-" } @@ -145,15 +153,15 @@ scope: scope, preamble: preamble ) - if old-parser { + if old-syntax { docs += old-parse(content, require-all-parameters: require-all-parameters, label-prefix: label-prefix, enable-curried-functions: enable-curried-functions) } else { docs += new-parser.parse(content) } // TODO - // if enable-curried-functions { - // function-docs = resolve-parents(function-docs) - // } + if enable-curried-functions { + docs.functions = resolve-parents(docs.functions) + } return docs diff --git a/src/show-example.typ b/src/show-example.typ index 0443e8b..8fa2105 100644 --- a/src/show-example.typ +++ b/src/show-example.typ @@ -189,6 +189,11 @@ /// Adds the two languages `example` and `examplec` to `raw` that can be used /// to render code examples side-by-side with an automatic preview. +/// +/// This function is intended to be used in a show rule +/// ```typ +/// #show: render-example +/// ``` #let render-examples( /// Body to apply the show rule to. /// -> any @@ -198,7 +203,7 @@ /// -> dictionary scope: (:), - /// Layout function which is passed to code, the preview and all other options, + /// Layout function which is passed the code, the preview and all other options, /// see @show-example.options. /// -> function layout: default-layout-example diff --git a/src/show-module.typ b/src/show-module.typ index 38d51c3..98499af 100644 --- a/src/show-module.typ +++ b/src/show-module.typ @@ -60,7 +60,7 @@ /// -> auto | none | function sort-functions: auto, - /// Whether to run docstring tests. + /// Whether to run doc-comment tests. /// -> boolean enable-tests: true, @@ -109,7 +109,7 @@ let eval-scope = ( - // Predefined functions that may be called by the user in docstring code + // Predefined functions that may be called by the user in doc-comment code example: style-functions.show-example.with( inherited-scope: module-doc.scope, preamble: module-doc.preamble diff --git a/src/styles/default.typ b/src/styles/default.typ index 057c289..4825e9c 100644 --- a/src/styles/default.typ +++ b/src/styles/default.typ @@ -96,13 +96,16 @@ if not inline-args { "\n " } let items = () let args = fn.args - for (name, description) in fn.args { + for (name, info) in fn.args { if style-args.omit-private-parameters and name.starts-with("_") { continue } let types - if "types" in description { - types = ": " + description.types.map(x => show-type(x, style-args: style-args)).join(" ") + if "types" in info { + types = ": " + info.types.map(x => show-type(x, style-args: style-args)).join(" ") + } + if style-args.enable-cross-references and not (info.at("description", default: "") == "" and style-args.omit-empty-param-descriptions) { + name = link(label(style-args.label-prefix + fn.name + "." + name.trim(".")), name) } items.push(name + types) } @@ -127,7 +130,7 @@ breakable: style-args.break-param-descriptions, [ #box(heading(level: style-args.first-heading-level + 3, name)) - #if function-name != none and style-args.enable-cross-references { label(function-name + "." + name) } + #if function-name != none and style-args.enable-cross-references { label(function-name + "." + name.trim(".")) } #h(1.2em) #types.map(x => (style-args.style.show-type)(x, style-args: style-args)).join([ #text("or",size:.6em) ]) diff --git a/src/styles/help.typ b/src/styles/help.typ index e163e5a..f87f6d3 100644 --- a/src/styles/help.typ +++ b/src/styles/help.typ @@ -33,7 +33,7 @@ #let show-parameter-list(fn, style-args) = { block(fill: rgb("#d8dbed44"), width: 100%, inset: (x: 0.5em, y: 0.7em), { - set text(font: "Cascadia Mono", size: 0.85em, weight: 340) + set text(font: "DejaVu Sans Mono", size: 0.85em, weight: 340) text(fn.name) "(" let inline-args = fn.args.len() < 5 @@ -130,11 +130,11 @@ block(breakable: style-args.break-param-descriptions, fill: rgb("#d8dbed44"), width: 100%, inset: (x: 0.5em, y: 0.7em), stack(dir: ltr, spacing: 1.2em, if style-args.enable-cross-references [ - #set text(font: "Cascadia Mono", size: 0.85em, weight: 340) + #set text(font: "DejaVu Sans Mono", size: 0.85em, weight: 340) #text(var.name) #label(style-args.label-prefix + var.name) ] else [ - #set text(font: "Cascadia Mono", size: 0.85em, weight: 340) + #set text(font: "DejaVu Sans Mono", size: 0.85em, weight: 340) #text(var.name) ], type diff --git a/src/styles/minimal.typ b/src/styles/minimal.typ index c2e4e2e..88250e6 100644 --- a/src/styles/minimal.typ +++ b/src/styles/minimal.typ @@ -43,15 +43,18 @@ let inline-args = fn.args.len() < 5 if not inline-args { "\n " } let items = () - for (arg-name, info) in fn.args { - if style-args.omit-private-parameters and arg-name.starts-with("_") { + for (name, info) in fn.args { + if style-args.omit-private-parameters and name.starts-with("_") { continue } let types if "types" in info { types = ": " + info.types.map(x => show-type(x)).join(" ") } - items.push(box(arg-name + types)) + if style-args.enable-cross-references and not (info.at("description", default: "") == "" and style-args.omit-empty-param-descriptions) { + name = link(label(style-args.label-prefix + fn.name + "." + name.trim(".")), name) + } + items.push(box(name + types)) } items.join( if inline-args {", "} else { ",\n "}) if not inline-args { "\n" } + ")" @@ -76,7 +79,7 @@ #[ #set text(fill: fn-color) #raw(name, lang: none) - #if function-name != none and style-args.enable-cross-references { label(function-name + "." + name) } + #if function-name != none and style-args.enable-cross-references { label(function-name + "." + name.trim(".")) } ] (#h(-.2em) #types.map(x => (style-args.style.show-type)(x)).join([ #text("or",size:.6em) ]) diff --git a/src/testing.typ b/src/testing.typ index 9f07b93..87a94b7 100644 --- a/src/testing.typ +++ b/src/testing.typ @@ -37,27 +37,34 @@ -/// Implementation for docstring tests. All tests are run immediately. Fails if +/// Implementation for doc-comment tests. All tests are run immediately. Fails if /// at least one test did not succeed. /// -/// This function is made available in all docstrings under the name 'test'. -/// -/// - ..tests (any): Tests to run in form of raw objects. -/// - scope (dictionary): Additional definitions to make available for the -/// evaluated test code. -/// - inherited-scope (dictionary): Definitions that are made available to the -/// entire parsed module including the test functions. This parameter -/// is only used internally. -/// - source-location (dictionary): Information about the location of the test -/// source code. Should contain values for the keys `module` and -/// `line`. This parameter is only used internally. -/// - enable (boolean): When set to `false`, the tests are ignored. +/// This function is made available in all doc-comments under the name 'test'. #let test( + + /// Tests to run in form of raw objects. + /// -> any ..tests, + + /// Additional definitions to make available for the evaluated test code. + /// -> dictionary scope: (:), + + /// Definitions that are made available to the entire parsed module including + /// the test functions. This parameter is only used internally. + /// -> dictionary inherited-scope: (:), + + /// Information about the location of the test source code. Should contain + /// values for the keys `module` and `line`. This parameter is only used internally. + /// -> dictionary source-location: none, + + /// When set to `false`, the tests are ignored. + /// -> boolean enable: true + ) = { if not enable { return } let source-info = get-source-info-str(source-location) diff --git a/src/utilities.typ b/src/utilities.typ index 738bb37..5c97946 100644 --- a/src/utilities.typ +++ b/src/utilities.typ @@ -1,15 +1,20 @@ -// Matches docstring references of the form `@@otherfunc` or `@@otherfunc()`. +// Matches doc-comment references of the form `@@otherfunc` or `@@otherfunc()`. #let reference-matcher = regex(`@@([\w\d\-_\)\(]+)`.text) /// Take a documentation string (for example a function or parameter -/// description) and process docstring cross-references (starting with `@@`), +/// description) and process doc-comment cross-references (starting with `@@`), /// turning them into links. -/// -/// - text (str): Source code. -/// - info (dictionary): -#let process-references(text, info) = { +#let process-references( + + /// Source code. -> str + text, + + /// -> dictionary + info + +) = { return text.replace(reference-matcher, match => { let target = match.captures.at(0) if info.enable-cross-references { @@ -22,14 +27,19 @@ -/// Evaluate a docstring description (i.e., a function or parameter description) +/// Evaluate a doc-comment description (i.e., a function or parameter description) /// while processing cross-references (@@...) and providing the scope to the /// evaluation context. -/// -/// - docstring (str): Docstring to evaluate. -/// - info (dictionary): Object holding information for cross-reference -/// processing and evaluation scope. -#let eval-docstring(docstring, info) = { +#let eval-docstring( + + /// Doc-comment to evaluate. -> str + docstring, + + /// Object holding information for cross-reference processing and evaluation scope. + /// -> dictionary + info + +) = { let scope = info.scope let content = process-references(docstring.trim(), info) eval(content, mode: "markup", scope: scope) diff --git a/tests/helping/ref/1.png b/tests/helping/ref/1.png index d24ab37ce61b969dc02a634fd710a9537a2fe1c7..b7ea4c5f0c9c0ea481c15418d25795041afda248 100644 GIT binary patch delta 17864 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 I=akR{06%H%H2?qr delta 17974 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 any +let my-text( + /// The weight + /// -> int + weight: 400, + body +) + +/// Doc +#let aey = my-text.with(weight: 200) +```.text + +#assert.eq( + parse-module(src).functions.at(1), + ( + name: "aey", + description: "Doc", + args: ( + body: (description: ""), + weight: (description: "The weight", types: ("int",), default: "200") + ), + parent: ( + name: "my-text", + pos: (), + named: (weight: "200") + ), + return-types: ("any",) + ), ) diff --git a/tests/old-parser/currying.typ b/tests/old-parser/currying.typ new file mode 100644 index 0000000..7b47013 --- /dev/null +++ b/tests/old-parser/currying.typ @@ -0,0 +1,58 @@ + +#import "/src/parse-module.typ": * + +#let parse-module = parse-module.with(old-syntax: true) +#let src = ``` +/// Doc +#let aey = text.with(1pt, weight: 200) +```.text + +#assert.eq( + parse-module(src).functions, + ( + ( + name: "aey", + description: " Doc\n\n", + args: (:), + parent: ( + name: "text", + pos: ("1pt",), + named: (weight: "200") + ), + ), + ) +) + + +// Parent resolving + +#let src = ``` +/// - weight (int): The weight +/// -> any +let my-text( + weight: 400, + body +) + +/// Doc +#let aey = my-text.with(weight: 200) +```.text + +#assert.eq( + parse-module(src).functions.at(1), + ( + name: "aey", + description: " Doc\n\n", + parent: ( + name: "my-text", + pos: (), + named: (weight: "200") + ), + args: ( + body: (:), + weight: (description: "The weight", types: ("int",), default: "200") + ), + return-types: ("any",) + ), +) + diff --git a/tests/old-parser/parse-module.typ b/tests/old-parser/parse-module.typ index 612ed53..1628316 100644 --- a/tests/old-parser/parse-module.typ +++ b/tests/old-parser/parse-module.typ @@ -143,7 +143,7 @@ assert.eq(result.functions.at(0).return-types, none) // } -// Ignore interrupted docstring +// Ignore interrupted doc-comment #{ let a = ``` /// Func diff --git a/tests/old-parser/test.typ b/tests/old-parser/test.typ index fee58ff..3092693 100644 --- a/tests/old-parser/test.typ +++ b/tests/old-parser/test.typ @@ -1,4 +1,5 @@ #set page(width: auto, height: auto, margin: 2pt) #include "parse-argument-list.typ" -#include "parse-module.typ" \ No newline at end of file +#include "parse-module.typ" +#include "currying.typ" \ No newline at end of file diff --git a/tests/test_parse.typ b/tests/test_parse.typ index 6da12a8..1628316 100644 --- a/tests/test_parse.typ +++ b/tests/test_parse.typ @@ -4,6 +4,7 @@ #let eval-string(string) = eval-docstring(string, (scope: (:))) +#let parse-module = parse-module.with(old-syntax: true) #{ let code = ``` @@ -142,7 +143,7 @@ assert.eq(result.functions.at(0).return-types, none) // } -// Ignore interrupted docstring +// Ignore interrupted doc-comment #{ let a = ``` /// Func diff --git a/typst.toml b/typst.toml index e846018..7d965a2 100644 --- a/typst.toml +++ b/typst.toml @@ -1,6 +1,6 @@ [package] name = "tidy" -version = "0.3.0" +version = "0.4.0" entrypoint = "src/tidy.typ" authors = ["Mc-Zen "] license = "MIT" @@ -8,5 +8,5 @@ description = "Documentation generator for Typst code in Typst." repository = "https://github.com/Mc-Zen/tidy" categories = ["utility", "scripting", "model"] -compiler = "0.6.0" +compiler = "0.11.0" exclude = ["/docs/*"] \ No newline at end of file