diff --git a/packages/preview/mantys/1.0.0/LICENSE b/packages/preview/mantys/1.0.0/LICENSE new file mode 100644 index 0000000000..cfe6de3054 --- /dev/null +++ b/packages/preview/mantys/1.0.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Jonas Neugebauer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/preview/mantys/1.0.0/README.md b/packages/preview/mantys/1.0.0/README.md new file mode 100644 index 0000000000..fac5f202f4 --- /dev/null +++ b/packages/preview/mantys/1.0.0/README.md @@ -0,0 +1,158 @@ +# Mantys (v1.0.0) + +> **MAN**uals for **TY**p**S**t + +Template for documenting [Typst](https://github.com/typst/typst) packages and templates. + +## Usage + +Quickstart your manual using `typst init`: +```shell +typst init @preview/mantys +``` + +## Writing basics + +A basic template for a manual could looks like this: + +```typst +#import "@local/mantys:1.0.0": * + +#import "your-package.typ" + +#show: mantys.with( + name: "your-package-name", + title: [A title for the manual], + subtitle: [A subtitle for the manual], + info: [A short descriptive text for the package.], + authors: "Your Name", + url: "https://github.com/repository/url", + version: "0.0.1", + date: "date-of-release", + abstract: [ + A few paragraphs of text to describe the package. + ], + + examples-scope: ( + scope: (your-package: your-package), + imports: (your-package: "*") + ) +) + +// end of preamble + += About +#lorem(50) + += Usage +#lorem(50) + += Available commands +#lorem(50) + +``` + +Use `#command(name, ..args)[description]` to describe commands and `#argument(name, ...)[description]` for arguments: + +```typst +#command("headline", arg[color], arg(size:1.8em), sarg[other-args], barg[body])[ + Renders a prominent headline using #doc("meta/heading"). + + #argument("color", type:"color")[ + The color of the headline will be used as the background of a #doc("layout/block") element containing the headline. + ] + #argument("size", default:1.8em)[ + The text size for the headline. + ] + #argument("sarg", is-sink:true)[ + Other options will get passed directly to #doc("meta/heading"). + ] + #argument("body", type:"content")[ + The text for the headline. + ] + + The headline is shown as a prominent colored block to highlight important news articles in the newsletter: + + #example[``` + #headline(blue, size: 2em, level: 3)[ + #lorem(8) + ] + ```] +] +``` + +The result might look something like this: + +![Example for a headline command with Mantys](docs/assets/headline-example.png) + +For a full reference of available commands read [the manual](docs/manual.pdf). + +## Changelog + +### Version 1.0.0 + +Version 1.0.0 is a complete rewrite of Mantys with many breaking changes. Read the [the manual](docs/manual.pdf) for a full usage reference. + +### Version 0.1.4 + +- Fix missing links in outline (@tingerrr). +- Fixed problem when evaluating default values with Tidy. + +### Version 0.1.3 + +- Fix for some datatypes not being displayed properly (thanks to @tingerrr). +- Fix for imbalanced outline columns (thanks again to @tingerrr). + +### Version 0.1.2 + +- Added [hydra](https://typst.app/universe/package/hydra) for better detection of headings in page headers (thanks to @tingerrr for the suggestion). +- Fixed problem with multiple quotes around default string values in tidy docs. +- Fixed datatypes linking to wrong documentation urls. + +### Version 0.1.1 + +- Added template files for submission to _Typst Universe_. + +### Version 0.1.0 + +- Refactorings and some style changes +- Updated manual. +- Restructuring of package repository. + +### Version 0.0.4 + +- Added integration with [tidy](https://github.com/Mc-Zen/tidy). +- Fixed issue with types in argument boxes. +- `#lambda` now uses `#dtype` + +#### Breaking changes + +- Adapted `scope` argument for `eval` in examples. + - `#example()`, `#side-by-side()` and `#shortex()` now support the `scope` and `mode` argument. + - The option `example-imports` was replaced by `examples-scope`. + +### Version 0.0.3 + +- It is now possible to load a packages' `typst.toml` file directly into `#mantys`: + ```typst + #show: mantys.with( ..toml("typst.toml") ) + ``` +- Added some dependencies: + - [jneug/typst-tools4typst](https://github.com/jneug/typst-tools4typst) for some common utilities, + - [jneug/typst-codelst](https://github.com/jneug/typst-codelst) for rendering examples and source code, + - [Pablo-Gonzalez-Calderon/showybox-package](https://github.com/Pablo-Gonzalez-Calderon/showybox-package) for adding frames to different areas of a manual (like examples). +- Redesign of some elements: + - Argument display in command descriptions, + - Alert boxes. +- Added `#version(since:(), until:())` command to add version markers to commands. +- Styles moved to a separate `theme.typ` file to allow easy customization of colors and styles. +- Added `#func()`, `#lambda()` and `#symbol()` commands, to handle special cases for values. +- Fixes and code improvements. + +### Version 0.0.2 + +- Some major updates to the core commands and styles. + +### Version 0.0.1 + +- Initial release. diff --git a/packages/preview/mantys/1.0.0/docs/_assets.typ b/packages/preview/mantys/1.0.0/docs/_assets.typ new file mode 100644 index 0000000000..232b30d973 --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/_assets.typ @@ -0,0 +1,24 @@ +#let assets = ( + "theme-cnltx-pages": ( + src: "assets/examples/theme-cnltx-pages.typ", + dest: "assets/examples/theme-cnltx-pages/{n}.png", + ), + "assets/examples/theme-cnltx.png": "assets/examples/theme-cnltx.typ", + "theme-modern-pages": ( + src: "assets/examples/theme-modern-pages.typ", + dest: "assets/examples/theme-modern-pages/{n}.png", + ), + "assets/examples/theme-modern.png": "assets/examples/theme-modern.typ", + "theme-typst-pages": ( + src: "assets/examples/theme-typst-pages.typ", + dest: "assets/examples/theme-typst-pages/{n}.png", + ), + "assets/examples/theme-typst.png": "assets/examples/theme-typst.typ", + "theme-orly-pages": ( + src: "assets/examples/theme-orly-pages.typ", + dest: "assets/examples/theme-orly-pages/{n}.png", + ), + "assets/examples/theme-orly.png": "assets/examples/theme-orly.typ", + "assets/examples/headline.png": "assets/examples/headline.typ", + "assets/thumbnail.png": "assets/thumbnail.typ", +) diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/headline.png b/packages/preview/mantys/1.0.0/docs/assets/examples/headline.png new file mode 100644 index 0000000000..55418943f0 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/headline.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/headline.typ b/packages/preview/mantys/1.0.0/docs/assets/examples/headline.typ new file mode 100644 index 0000000000..15b07cc18d --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/assets/examples/headline.typ @@ -0,0 +1,48 @@ +#import "../../../src/core/document.typ" +#import "../../../src/core/layout.typ" +#import "../../../src/core/themes.typ" +#import "../../../src/api/commands.typ": * +#import "../../../src/api/examples.typ": example + +#show: layout.page-init( + document.create( + title: "typst-test", + ..toml("../../../typst.toml"), + ), + themes.default, +) +#set page(width: 18cm, height: auto, margin: 1em) + + +#let headline(color, size: 1.8em, body, ..other-args) = block( + fill: color, + width: 100%, + inset: .5em, + radius: 4pt, + heading(..other-args, text(size, white, body)), +) + + +#command("headline", arg("color"), arg(size: 1.8em), barg("body"), sarg("other-args"))[ + Renders a prominent headline using #builtin[heading]. + + #argument("color", types: color)[ + The color of the headline will be used as the background of a block element containing the headline. + ] + #argument("size", default: 1.8em)[ + The text size for the headline. + ] + #argument("other-args", is-sink: true, types: "any")[ + Other options will get passed directly to #builtin[heading]. + ] + #argument("body", types: (content, str))[ + The text for the headline. + ] + + The headline is shown as a prominent colored block to highlight important news articles in the newsletter: + #example(use-examples-scope: false, scope: (headline: headline))[``` + #headline(blue, size:16pt, level:3, numbering: none)[ + #lorem(4) + ] + ```] +] diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/queen-elizabeth-ll-clipart.jpg b/packages/preview/mantys/1.0.0/docs/assets/examples/queen-elizabeth-ll-clipart.jpg new file mode 100644 index 0000000000..db90b90e1b Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/queen-elizabeth-ll-clipart.jpg differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages.typ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages.typ new file mode 100644 index 0000000000..d1d4f0a906 --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages.typ @@ -0,0 +1,45 @@ +#import "../../../src/mantys.typ": * + +#show: mantys( + name: "mantys", + version: "1.0.0", + authors: ( + "Jonas Neugebauer", + ), + license: "MIT", + description: "Helpers to build manuals for Typst packages.", + repository: "https://github.com/jneug/typst-mantys", + + /// Uncomment to load the above package information + /// directly from the typst.toml file + // ..toml("../typst.toml"), + + title: "Manual title", + // subtitle: "Tagline", + date: datetime.today(), + + // url: "", + + abstract: [ + #lorem(50) + ], + + show-index: false, + + theme: themes.cnltx +) + + +#heading(depth: 1, "Heading") +#lorem(300) + + +#heading(depth: 2, "Heading") +#lorem(300) + + +#heading(depth: 2, "Heading") +#lorem(300) + +#heading(depth: 1, "Heading") +#lorem(300) diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/1.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/1.png new file mode 100644 index 0000000000..4d559a1b2c Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/1.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/2.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/2.png new file mode 100644 index 0000000000..b3d5585233 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/2.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/3.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/3.png new file mode 100644 index 0000000000..bcb33eef01 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/3.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/4.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/4.png new file mode 100644 index 0000000000..c32895ebbb Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/4.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/5.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/5.png new file mode 100644 index 0000000000..3f159d48ef Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages/5.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx.png new file mode 100644 index 0000000000..0d94e4b6ff Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx.typ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx.typ new file mode 100644 index 0000000000..01adc5b24e --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx.typ @@ -0,0 +1,7 @@ + +#set page(width: auto, height: auto, margin: 2mm, fill: luma(96%)) + +#let page-count = 2 +#grid( columns: page-count, gutter: 2mm, ..for i in range(page-count) { + (image("theme-cnltx-pages/" + str(i + 1) + ".png", width: 2cm),) + } ) diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages.typ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages.typ new file mode 100644 index 0000000000..127a3bdc97 --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages.typ @@ -0,0 +1,45 @@ +#import "../../../src/mantys.typ": * + +#show: mantys( + name: "mantys", + version: "1.0.0", + authors: ( + "Jonas Neugebauer", + ), + license: "MIT", + description: "Helpers to build manuals for Typst packages.", + repository: "https://github.com/jneug/typst-mantys", + + /// Uncomment to load the above package information + /// directly from the typst.toml file + // ..toml("../typst.toml"), + + title: "Manual title", + // subtitle: "Tagline", + date: datetime.today(), + + // url: "", + + abstract: [ + #lorem(50) + ], + + show-index: false, + + theme: themes.modern +) + + +#heading(depth: 1, "Heading") +#lorem(300) + + +#heading(depth: 2, "Heading") +#lorem(300) + + +#heading(depth: 2, "Heading") +#lorem(300) + +#heading(depth: 1, "Heading") +#lorem(300) diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/1.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/1.png new file mode 100644 index 0000000000..d651851776 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/1.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/2.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/2.png new file mode 100644 index 0000000000..62c7f4460f Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/2.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/3.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/3.png new file mode 100644 index 0000000000..03d26720dc Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/3.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/4.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/4.png new file mode 100644 index 0000000000..6e31435a67 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern-pages/4.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern.png new file mode 100644 index 0000000000..3e3e78cbfb Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern.typ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern.typ new file mode 100644 index 0000000000..a7f926e165 --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-modern.typ @@ -0,0 +1,7 @@ + +#set page(width: auto, height: auto, margin: 2mm, fill: luma(96%)) + +#let page-count = 2 +#grid( columns: page-count, gutter: 2mm, ..for i in range(page-count) { + (image("theme-modern-pages/" + str(i + 1) + ".png", width: 2cm),) + } ) diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages.typ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages.typ new file mode 100644 index 0000000000..3e6a70450f --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages.typ @@ -0,0 +1,49 @@ +#import "../../../src/mantys.typ": * + +#show: mantys( + name: "mantys", + version: "1.0.0", + authors: ( + "Jonas Neugebauer", + ), + license: "MIT", + description: "Helpers to build manuals for Typst packages.", + repository: "https://github.com/jneug/typst-mantys", + + /// Uncomment to load the above package information + /// directly from the typst.toml file + // ..toml("../typst.toml"), + + title: "Manual title", + // subtitle: "Tagline", + date: datetime.today(), + + // url: "", + + abstract: [ + #lorem(50) + ], + + show-index: false, + + theme: themes.orly, + theme-options: ( + // image source: https://www.publicdomainpictures.net/de/view-image.php?image=73646&picture=queen-elizabeth-ii-clipart + title-image: image("queen-elizabeth-ll-clipart.jpg"), + ), +) + + +#heading(depth: 1, "Heading") +#lorem(300) + + +#heading(depth: 2, "Heading") +#lorem(300) + + +#heading(depth: 2, "Heading") +#lorem(300) + +#heading(depth: 1, "Heading") +#lorem(300) diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/1.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/1.png new file mode 100644 index 0000000000..9b685e18c3 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/1.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/2.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/2.png new file mode 100644 index 0000000000..821d044db5 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/2.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/3.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/3.png new file mode 100644 index 0000000000..1149e13067 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/3.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/4.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/4.png new file mode 100644 index 0000000000..b7df4b3edc Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/4.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/5.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/5.png new file mode 100644 index 0000000000..b3029d18ef Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/5.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/6.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/6.png new file mode 100644 index 0000000000..8e2b30c1ea Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-pages/6.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly.png new file mode 100644 index 0000000000..d52dca794f Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly.typ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly.typ new file mode 100644 index 0000000000..0143bab40c --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly.typ @@ -0,0 +1,7 @@ + +#set page(width: auto, height: auto, margin: 2mm, fill: luma(96%)) + +#let page-count = 2 +#grid( columns: page-count, gutter: 2mm, ..for i in range(page-count) { + (image("theme-orly-pages/" + str(i + 1) + ".png", width: 2cm),) + } ) diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages.typ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages.typ new file mode 100644 index 0000000000..c5f3003ee3 --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages.typ @@ -0,0 +1,43 @@ +#import "../../../src/mantys.typ": * + +#show: mantys( + name: "mantys", + version: "1.0.0", + authors: ( + "Jonas Neugebauer", + ), + license: "MIT", + description: "Helpers to build manuals for Typst packages.", + repository: "https://github.com/jneug/typst-mantys", + + /// Uncomment to load the above package information + /// directly from the typst.toml file + // ..toml("../typst.toml"), + + title: "Manual title", + // subtitle: "Tagline", + date: datetime.today(), + + // url: "", + + abstract: [ + #lorem(50) + ], + + show-index: false, +) + + +#heading(depth: 1, "Heading") +#lorem(300) + + +#heading(depth: 2, "Heading") +#lorem(300) + + +#heading(depth: 2, "Heading") +#lorem(300) + +#heading(depth: 1, "Heading") +#lorem(300) diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/1.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/1.png new file mode 100644 index 0000000000..816acad9cb Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/1.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/2.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/2.png new file mode 100644 index 0000000000..0361fc6703 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/2.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/3.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/3.png new file mode 100644 index 0000000000..b56881c627 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/3.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/4.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/4.png new file mode 100644 index 0000000000..c615eac918 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/4.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/5.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/5.png new file mode 100644 index 0000000000..517e6d5923 Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst-pages/5.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst.png b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst.png new file mode 100644 index 0000000000..aa93845eaa Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst.typ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst.typ new file mode 100644 index 0000000000..a36fa0f335 --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-typst.typ @@ -0,0 +1,7 @@ + +#set page(width: auto, height: auto, margin: 2mm, fill: luma(96%)) + +#let page-count = 2 +#grid( columns: page-count, gutter: 2mm, ..for i in range(page-count) { + (image("theme-typst-pages/" + str(i + 1) + ".png", width: 2cm),) + } ) diff --git a/packages/preview/mantys/1.0.0/docs/assets/thumbnail.png b/packages/preview/mantys/1.0.0/docs/assets/thumbnail.png new file mode 100644 index 0000000000..4c806877ed Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/assets/thumbnail.png differ diff --git a/packages/preview/mantys/1.0.0/docs/assets/thumbnail.typ b/packages/preview/mantys/1.0.0/docs/assets/thumbnail.typ new file mode 100644 index 0000000000..7cfa5c7f0c --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/assets/thumbnail.typ @@ -0,0 +1,54 @@ +#import "../../src/mantys.typ": * + +#show: mantys( + name: "mantys", + version: "1.0.0", + authors: ( + "Jonas Neugebauer", + ), + license: "MIT", + description: "Helpers to build manuals for Typst packages.", + repository: "https://github.com/jneug/typst-mantys", + + /// Uncomment to load the above package information + /// directly from the typst.toml file + // ..toml("../typst.toml"), + + title: "Manual title", + // subtitle: "Tagline", + date: datetime.today(), + + // url: "", + + abstract: [ + #lorem(50) + ], + + // examples-scope: () + + show-index: false, + + theme: themes.create-theme( + title-page: (doc, theme) => { + let default-title = (themes.default.title-page)(doc, theme).child.children + // Remove pagebreak + let _ = default-title.remove(-1) + { + set align(center) + set block(spacing: 2em) + default-title.join([]) + } + } + ) +) + +/// Helper for Tidy-Support +/// Uncomment, if you are using Tidy for documentation +// #let show-module(name, scope: (:), outlined: true) = tidy-module( +// read(name + ".typ"), +// name: name, +// show-outline: outlined, +// include-examples-scope: true, +// extract-headings: 3, +// tidy: tidy +// ) diff --git a/packages/preview/mantys/1.0.0/docs/manual.pdf b/packages/preview/mantys/1.0.0/docs/manual.pdf new file mode 100644 index 0000000000..36cb5f56bc Binary files /dev/null and b/packages/preview/mantys/1.0.0/docs/manual.pdf differ diff --git a/packages/preview/mantys/1.0.0/docs/manual.typ b/packages/preview/mantys/1.0.0/docs/manual.typ new file mode 100644 index 0000000000..623ac99a10 --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/manual.typ @@ -0,0 +1,926 @@ +#import "../src/mantys.typ": * +#import "../src/_api.typ" as mantys-api +#import "../src/core/schema.typ" as s +#import "_assets.typ": assets + +#let show-module(name, scope: (:), ..tidy-args) = tidy-module( + name, + read("../src/" + name + ".typ"), + show-module-name: false, + ..tidy-args.named(), +) + +#let balanced-cols(cols, clearance: .0pt, ..column-args, body) = { + context { + let m = measure(body) + let h = calc.ceil((m.height / 1pt) / cols) * (1pt + clearance) + block(height: h, columns(cols, ..column-args, body)) + } +} + +#let theme-var = var-.with(module: "themes") + + +#import "@preview/swank-tex:0.1.0": TeX, LaTeX + +#show "CNLTX": package +#show "TIDY": _ => package("Tidy") + +#show: mantys( + ..toml("../typst.toml"), + name: "Mantys", + + subtitle: [#text(eastern, weight:600)[MAN]uals for #text(eastern, weight:600)[TY]p#text(eastern, weight:600)[S]t], + date: datetime.today(), + abstract: [ + MANTYS is a Typst template to help package and template authors write beautiful and useful manuals. It provides functionality for consistent formatting of commands, variables and source code examples. The template automatically creates a table of contents and a command index for easy reference and navigation. + + For even easier manual creation, MANTYS works well with TIDY, the Typst docstring parser. + + The main idea and design were inspired by the #LaTeX package CNLTX by #name[Clemens Niederberger]. + ], + + show-index: false, + wrap-snippets: true, + + git: git-info(file => read(file)), + + examples-scope: ( + scope: ( + mantys: mantys-api, + is-themable: () => place( + top, + dy: -5mm, + note( + styles.pill( + "emph.context", + ( + icon("paintbrush") + sym.space.nobreak + "Styled by the " + typeref("theme") + ), + ), + ), + ), + ), + imports: (mantys: "*"), + ), + + assets: assets, +) + +#pagebreak(weak: true) += About + +Mantys is a Typst package to help package and template authors write manuals. The idea is that, as many Typst users are switching over from #TeX, they are used to the way packages provide a PDF manual for reference. Though in a modern ecosystem there are other ways to write documentation (like #link("https://rust-lang.github.io/mdBook/")[mdBook] or #link("https://asciidoc.org")[AsciiDoc]), having a manual in PDF format might still be beneficial since many users of Typst will generate PDFs as their main output. + +This manual is a complete reference of all of MANTYS features. The source file of this document is a great example of the things MANTYS can do. Other than that, refer to the README file in the GitHub repository and the source code for MANTYS. + +== Acknowledgements + +Mantys was inspired by the fantastic #LaTeX package #link("https://ctan.org/pkg/cnltx")[CNLTX] by #name[Clemens Niederberger]#footnote[#link("*mailto:clemens@cnltx.de", "clemens@cnltx.de")]. + +Thanks to #github-user("tingerrr") and others for contributing to this package and giving feedback. + +Thanks to #github-user("Mc-Zen") for developing #github("Mc-Zen/tidy"). + +== Dependencies + +MANTYS is build using some of the great packages provided by the Typst community: +#balanced-cols(3, clearance: .05pt)[ + #let import-data = read("../src/_deps.typ") + #for imp in import-data.split("\n") { + if imp != "" { + let m = imp.match(regex("^#import \"@preview/([^:]+?):(.+)\"$")) + if m != none [ + - #universe(m.captures.at(0), version:m.captures.at(1)) (#m.captures.at(1)) + ] + } + } +] + +== Some Terminology + +Since MANTYS was first developed as a port of CNLTX, some terms used are derived from the original #LaTeX package. + +Functions are called "commands" and paramteres "arguments". This has the benefit of avoiding collisions with the native #typ.t.function type. + +To display formatted commands, arguments and types inline use the abbreviated command versions like @cmd:cmd[-] or @cmd:arg[-]. + +To fully document a command or argument use the block commands like @cmd:command[-] and @cmd:argument[-]. + +Some commands add an entry to the #link(, "Index"). Those commands usually have a Minus-variant that skips this step (like @cmd:cmd[-] and @cmd:cmd-[-]). + +A "custom type" is a type defined by the package usually in the form of a dictionary schema. Read @subsec:custom-types for more information. + + += Quickstart + +In your project root run `typst init`: +#codesnippet[```bash + typst init "@preview/mantys" docs + ```] + +Your project folder should look something like this: +#codesnippet(inset: 1.5pt)[``` + . + ├── docs + │   └── manual.typ + └── typst.toml + ```] + +``` +. +├── docs +│   └── manual.typ +└── typst.toml +``` + +Open `docs/manual.typ` in your editor, delete the arguments in the @cmd:mantys[-] call at the top from #arg[name] to #arg[respository]. Then uncomment the line ```..toml("../typst.toml"),```. + +The top of your manual should look like this: +#sourcecode[```typ + #show: mantys( + ..toml("../typst.toml"), + ) + ```] + +Fill in the rest of the information like #arg[subtitle] or #arg[abstract] to your liking. Select a #link(, "Theme") you like. + +All uppercase occurrences of #arg[name] will be highlighted as a package name. For example #text(hyphenate:false, "MAN\u{2060}TYS") will appear as MANTYS. + +Start writing your manual. + +If you alread use TIDY to document your functions, use @cmd:tidy-module to parse and display a module directly in MANTYS: +#sourcecode[```typ + TIDY-module("utils", read("../src/lib/utils.typ")) + ```] + +Read @subsec:using-tidy for more details about using TIDY with MANTYS. + += Usage + +Initialize your manual using `typst init`: +#codesnippet[```bash + typst init "@preview/mantys" docs + ```] + +#info-alert[We suggest to initialize the template inside a `docs` subdirectory to keep your manual separated from your packages source files.] + +If you prefer to manually setup your manual, create a `.typ` file and import MANTYS at the top: + +#show-import(name: "mantys") + +== Project structure + +You can setup your project in any way you like, but a common project structure for Typst packages looks like this: + +#codesnippet(inset: 1.5pt)[``` + . + ├── LICENSE + ├── README.md + ├── docs + │   ├── assets + │   │   └── example.typ + │   └── manual.typ + ├── src + │   └── lib.typ + ├── tests + └── typst.toml + ```] + +MANTYS' defaults are configured with this structure in mind and will let you easily setup your manual. + +== Initializing the template + +After importing MANTYS the template is initialized by applying a show rule with the @cmd:mantys command. + +@cmd:mantys[-] requires some information to setup the template with an initial title page. Most of the information can be read directly from the `typst.toml` of your package: +```typ +#show: mantys( + ..toml("../typst.toml"), + ... // other options +) +``` + +#info-alert[Change the path to the `typst.toml` file according to your project structure.] +#warning-alert[Note that since #version(1,0,0) @cmd:mantys no longer requires the use of #typ.with.] + +#command( + "mantys", + + sarg("doc"), +)[ + #argument("doc", is-sink: true)[ + MANTYS initializes the @type:document from the provided arguments. Refer to the scheme in @sec:the-document for all possible options and how to use the @type:document. + ] + + #argument("theme", types: ("theme",), command: "mantys")[ + The @type:theme to use for the manual. + ] + + All other arguments will be passed to #cmd-[titlepage]. +] + +== The MANTYS document +The arguments passed to @cmd:mantys are used to initialize the @type:document, a dictionary holding information required for the manual. + +The following keys can be passed to @cmd:mantys: + +#frame[ + #set text(.88em) + #schema( + "document", + s.document, + child-schemas: ( + package: "package", + template: "template", + examples-scope: "examples-scope", + ), + ) +] + +#argument("title", default: none, types: content, command: "mantys")[ + If no title is provided, the title is taken from the @type:package. If no package information is provided, an error is thrown. + + Will be populated from the information in #arg[package] if omitted. +] +#argument("subtitle", default: none, types: content, command: "mantys")[ + A subtitle for the manual. +] +#argument("urls", default: none, types: (array, str), command: "mantys")[ + An array of URLs associated with this package. +] +#argument("date", default: none, types: (datetime, str), command: "mantys")[ + A date for the manual or package. +] +#argument("abstract", default: none, types: content, command: "mantys")[ + An abstract to appear on the #cmd-[titlepage]. +] +#argument("package", default: (:), types: "package", command: "mantys")[ + The @type:package information (usually read from `typst.toml`). +] +#argument("template", default: none, types: "template", command: "mantys")[ + The @type:template information (usually read from `typst.toml`). +] +#argument("show-index", default: true, command: "mantys")[ + By default, an index of commands, variabes and other keywords is generated at the end of the document. Setting this to #typ.v.false will disable the index. You can manually generate an index by using @cmd:make-index. +] +#argument("show-outline", default: true, command: "mantys")[ + By default, a table of contents is generated on the title page. Setting this to #typ.v.false will disable the outline. + + #warning-alert[The title page is generated by the theme and might ignore this setting.] +] +#argument("show-urls-in-footnotes", default: true, command: "mantys")[ + By default, the URLs of links generated by #typ.link will be shown in a footnote. #typ.v.false disables this behaviour. +] +#argument("index-references", default: true, command: "mantys")[ + By default, referencing a command, argument or type will create an index entry. This can be disabled on a per reference basis. + + Setting #arg[index-references] to #typ.v.false will reverse this and disable index entries but allows you to enable them per reference. + + See @sec:referencing-commands for more information about references. +] + +#argument("examples-scope", default: (:), command: "mantys")[ + Default scope for code examples. The examples scope is a #typ.t.dict with two keys: `scope` and `imports`. The `scope` is passed to #typ.eval for evaluation. `imports` maps module names to a set of imports that should be prepended to example code as a preamble. + + *Schema*: + #schema( + "examples-scope", + ( + scope: (:), + imports: (:), + ), + ) + + For example, if your package is named `my-pkg` and you want to import everything from your package into every examples scope, you can add the following #arg[examples-scope]: + + #codesnippet[```typc + examples-scope: ( + scope: ( + pkg: my-pkg + ), + imports: ( + pkg: "*" + ) + ) + ```] + + The #arg[scope] and #arg[imports] are passed to TIDY for evaluating docstring examples. + + For further details refer to @sec:showing-examples and @cmd:example. +] +#argument("theme-options", default: (:), command: "mantys")[ + Options to be used by themes (see @sec:themes). +] +#argument("assets", default: (:), types: (array, "asset"), command: "mantys")[ + MANTYS can add #typ.metadata to the manual to be queried by external tools. See @subsec:schema-asset for more information. + + The repository at #github("jneug/typst-mantys") contains an `assets` script to query Typst assets from a MANTYS manual and compile them before compiling the manual. +] + +#argument("git", types: ("dictionary",), command: "mantys")[ + MANTYS can show information about the current commit in the manuals footer. This is useful if you compile your manual with a CI workflow like GitHub Actions. + + The git information is read with the @cmd:git-info command. To allow MANTYS to read local files from your project you need to provide a reader function to @cmd:git-info[-]. + + ```typ + #mantys( + ..toml("../typst.toml"), + + git: git-info((file) => read(file)) + ) + ``` + + The function assumes the project structure seen in @subsec:project-structure. For other layouts provide the location of the `.git` folder via the #arg[git] argument. +] + +==== Schema for package information +#frame( + schema( + "package", + s.package, + child-schemas: ( + authors: "author", + ), + expand-schemas: true, + ), +) + +The @type:package is exactly the same schema used for the `package` key in the `toml.typst` file. See #link("https://github.com/typst/packages?tab=readme-ov-file#package-format", "the official doumentation") for a full description of all keys. + +#warning-alert[ + Providing a #arg[name] for the package is mandatory. +] + +#info-alert[ + Usually the #arg[package] is loaded directly from the `typst.toml` file and passed to @cmd:mantys. +] + +==== Schema for template information +#frame( + schema( + "template", + s.template, + ), +) + +The @type:template is exactly the same schema used for the `template` key in the `toml.typst` file. See #link("https://github.com/typst/packages?tab=readme-ov-file#templates", "the official doumentation") for a full description of all keys. + +#info-alert[ + The #arg[template] is optional and may be #typ.v.none. +] + +==== Schema for asset information +#frame( + schema( + "asset", + s.asset, + ), +) + +MANTYS can add #typ.metadata about required assets to the document. External tooling may query the document for these assets at the `` label and compile these before compiling the manual itself. + +#info-alert[ + You can find a simple script in the MANTYS GitHub repository (#github-file("jneug/typst-mantys", "scripts/assets")) to automatically compile Typst assets. +] + +External tools should query the document with the input `mode=assets`. This will stop rendering of the document after setting the required metadata and thus speed up the query. + +#codesnippet[```bash + typst query --root . --input mode=assets --field 'value' docs/manual.typ '' + ```] + +Each queried asset has an `id`, a source file `src` and a description `dest`. An external tool should compile `src` to `dest`. + +Usually the order of assets is important since later assets might depend on earlier ones. For example the first two assets for this manual look like this: +#codesnippet[```json + { + "id": "theme-cnltx-pages", + "src": "assets/examples/theme-cnltx-pages.typ", + "dest": "assets/examples/theme-cnltx-pages/{n}.png" + }, + { + "id": "assets/examples/theme-cnltx.png", + "src": "assets/examples/theme-cnltx.typ", + "dest": "assets/examples/theme-cnltx.png" + } + ```] + +The first entry compiles a multipage example for the CNLTX theme into multiple `png` images and the second combines them into one. The result can be seen in @subsubsec:theme-cnltx. + +#info-alert[ + If your manual requires a lot of assets it might be a good idea to collect them into a separate file like #github-file("jneug/typst-mantys", "docs/assets.typ") and import it in your manual. +] + +==== Schema for author information +#frame(schema("author", s.author)) + +Information about the package authors can be provided in different formats. In the document they will be accessible as dictionaries with a `name` key. The other information is optional. + +If the author is provided as a #typ.t.str, MANTYS will try to find additional information like an email address. + +For example: +#codesnippet[```typ + "J. Neugebauer @jneug " + ```] + +will be parsed into +#codesnippet[```typc + { + name: "J. Neugebauer", + email: "github@neugebauer.cc", + github: "jneug", + } + ```] + +==== Loading git information + +#command("git-info", arg("reader"), arg(git-root: "../.git"))[ + Loads information about the current commit from the git repository at #arg[git-root]. + + #argument("reader", types: "function")[ + A function that reads a file and returns its content: #lambda("str", ret: "str") + + Usually this will look like this: + ```typc + (filename) => read(filename) + ``` + ] + + #example(scope: (git-info: git-info.with(git-root: "../../.git")))[``` + #git-info((filename) => read(filename)) + ```] +] + +=== Accessing document data + +There are two methods to access information from the MANTYS #dtype("document"): + +1. Using commands from the #module[document] module or +2. using @cmd:mantys-init instead of @cmd:mantys. + +==== Using the `document` module + +The usual way to access the @type:document is by calling one of the #module[document] functions. + +#show-module("core/document", module: "document") + +==== Custom initialization + +Instead of using @cmd:mantys in a #typ.show rule, you can initialize MANTYS using @cmd:mantys-init directly (@cmd:mantys[-] essentially is a shortcut for using @cmd:mantys-init[-]). + +#command("mantys-init", ret: array)[ + Calling this function will return a tuple with two elements: + + / [0]: The MANTYS #dtype("document"). + / [1]: The MANTYS template function to be used in a #typ.show rule. +] + +Calling @cmd:mantys-init directly will give you direct access to the @type:document in your manual: +#sourcecode[```typ + #let (doc, mantys) = mantys-init(..toml("../typst.toml")) + + #show: mantys + + This is the manual for #doc.package.name version #str(doc.package.version). + ```] + += Documenting commands + +#warning-alert[#icons.warning This section need to be written. Refer to @sec:commands for the documentation of all available commands.] + +== Using Tidy + +MANTYS was build with TIDY in mind and replaces the default template used by TIDY. If you already use docstrings to document your code, you can easily show your function documentation in your MANTYS manual. + +@cmd:tidy-module is the main entrypoint for using TIDY in MANTYS. The command will call #cmd(module:"tidy")[parse-module] and #cmd(module:"tidy")[show-module] for you and setup MANTYS as the template. + +Since MANTYS can't read your packages files, you need to call #typ.read and pass the result to the function (same as you would do for #cmd-(module:"tidy")[parse-module]). + +#show-module( + "api/tidy", + show-outline: false, + omit-private-parameters: true, + sort-functions: false, +) + +For easier usage it is recommended to define a custom function in the header of your manual like this: +#sourcecode[```typ + #let show-module(name, ..tidy-args) = tidy-module( + name, + read("../src/" + name + ".typ"), + // Some defaults you want to set + ..tidy-args.named(), + ) + ```] + +See @sec:commands for an example of the result of @cmd:tidy-module. + +#info-alert[ + When using TIDY, most MANTYS concepts also apply to docstrings. For example, cross-referencing commands is done with the `cmd:` prefix. All MANTYS commands like @cmd:arg or @cmd:property are available in docstring. +] + +== Documenting custom types and validation schemas + +MANTYS provides support for documentation of custom data types and validation schemas as provided by #universe("valkyrie"). + +In general a custom type is an anchor in the document that defines a structured schema for some kind of data, that is used in your package. A #typ.t.dict with some mandatory keys for example. See @type:document and other schmeas in this manual for examples. + +A custom type can appear anyplace in the manual where a data type can appear, like in argument descriptions: +#example[```typ + #argument("theme", types:("theme","module"))[ + The theme for this manual. + ] + ```] + +==== Defining custom types +#custom-type("custom-type", color: rgb("#dc41f1")) + +Place a custom type anchor with the @cmd:custom-type command. + +#command("custom-type", arg("name"), arg(color: auto), label: none)[ + Places a custom type anchor in the document. Any occurrences of the data type #arg[name] will link to this location in the manual. THe anchor itself is invisible. +] + +==== Defining a custom type schema +#custom-type("schema", color: rgb("#dc41f1")) + +If your custom type is defined by a dictionary schema, you cann simply pass an example to @cmd:schema to show a summary of the required keys and types. + +@cmd:schema also accepts a #universe("valkyrie") validation schema. + +#command("schema", arg("name"), arg("definition"), arg(color: auto), label: none)[ + +] + +#warning-alert[ + Support for #package[valkyrie] schemas is still in development. Some aspects (like optional keys) are not yet supported. +] + +See @type:document and other custom types in this manual for examples. + +== Referencing commands and types + +You can use the builtin `@` short-syntax for referencing commands, arguments and custom-types in your document. + +#grid( + columns: (1fr, 1fr), + gutter: 4%, + [Use the `cmd` prefix to reference custom types in the manual or use @cmd:cmdref.], example[`@cmd:mantys`], + [Add an argument name after a dot to reference arguments of a command or use @cmd:argref.], + example[`@cmd:mantys.theme`], + + [Use the `type` prefix to reference custom types in the manual or use @cmd:typeref.], example[`@type:custom-type`], +) + +Referencing a command will create an index entry. To prevent this, add `[-]` as a supplement. If @cmd:mantys.index-references was set to #typ.v.false, no index entries are created by default but adding `[+]` to a reference will set one. + +```side-by-side +@cmd:utils:dict-get[-] + +@cmd:mantys.index-references[+] +``` + +Referencing the builtin commands and types can be done via the @cmd:builtin and @cmd:dtype commands. For these cases MANTYS also provides shortcuts in the #var-("typ") dictionary. + +- #ex(`#typ.raw`) +- #ex(`#typ.t.dict`) +- #ex(`#typ.v.false`) + +See @sec:shortcuts for a full list of available shortcuts. + +== Displaying examples + +Showing examples is easy by using the #link(, "example commands"). Wrapping any typst code in @cmd:example or @cmd:side-by-side will show the raw code and the evaluated result in a @cmd:frame. + +#info-alert[ + @cmd:side-by-side is an alias for @cmd:example with #arg(side-by-side: true) set. +] + +By default, any #typ.raw blocks with the language set to `example` or `side-by-side` will automatically be wrapped inside the corresponding command. + +#example[````typ + ```example + Some *bold* text. + ``` + + ```side-by-side + Some *bold* text. + ``` + ````] + +#info-alert[ + To show an example with fenced #typ.raw code, use more than three backticks for the example environment: + `````typ + ````example + ```typ + #let number = 4 + ``` + ```` + ````` +] + +=== Preventing example evaluation + +Sometimes you don't want the example to be evaluated by Typst and provide the result yourself. In that case, simply add another content block after the example code: + +````example +#example[ + ```typ + Some #strong[bold] text? + ``` +][ + Some #emph[bold] text? +] +```` + +=== Setting the evaluation scope + +Examples are evaluated with the scope set by @cmd:mantys.examples-scope. + +@cmd:example takes two arguments to modify the scope of examples: @cmd:example.scope and @cmd:example.imports. + +The #arg[scope] is passed to #typ.eval as the scope argument while the #arg[imports] are prepended to the raw code as #typ.import statements. + +The #arg[scope] passed to @cmd:example[-] is merged with the `scope` from #arg[examples-scope] passed to @cmd:mantys. By passing #arg(use-examples-scope: false), the #arg[examples-scope] is ignored. + +The #arg[imports] are parsed into a preamble by @cmd:utils:build-preamble. The value is a #typ.t.dict with `(module: import)` pairs that are prepended to the raw code of the example: +#example[```typ + #utils.add-preamble( + "#rawi[Some] @cmd:command.", + ( + mantys: "cmd", + utils: "rawi", + ), + ) + ```] + +The @cmd:mantys.examples-scope is passed to TIDY for evaluating examples in docstrings. + +=== Displaying other sourcecode + +Any #typ.raw code will be passed to #universe("codly") for display. You can pass new defaults to #package[CODLY] via the #cmd(module:"codly")[codly] command. + +#example[```typ + Some `raw` code + over *multiple* + lines. + ```][ + #codly.local(```typ + Some `raw` code + over *multiple* + lines. + ```) +] + +To modify the display you can wrap the #typ.raw block inside the @cmd:codesnippet[-] or @cmd:sourcecode[-] commands. By default both commands add a @cmd:frame around the content. + +@cmd:codesnippet can be used for small snippets of code like the `typst init` line seen in @sec:usage. The command disables line numbers. Any arguments will be passed to #cmd(module:"codly", "local"). + +#info-alert[ + Previous versions of MANTYS would wrap any #typ.raw block inside @cmd:codesnippet[-]. This was changed to allow more flexibility when showing code. To enable the old behaviour, pass #arg(wrap-snippets: true) to @cmd:mantys[-]. +] + +@cmd:sourcecode will wrap the #typ.raw block in a frame for nicer display. + +#example[````typ + #sourcecode[```typ + #let a = "Hello" + #strong(a), World! + ```] + ````] + += Customizing the template + +== Themes + +MANTYS provides support for color themes and can be styled within certain boundries. The template comes with a few bundled themes but you can easily create a custom theme. + +#error-alert[ + #icons.warning Theme support is considered *experimental* and might be removed in future versions if it proves to be not stable enough. Compilation times can get somewhat slow and my guess is that themes are a major factor. +] + +=== Using themes + +To set the theme for your manual, simply provide a #arg[theme] argument to @cmd:mantys and set it to one of the bundled themes (see @sec:bundled-themes), a #typ.t.dict or a #typ.module with the required color, font and style information. + +Some themes can be further customized by options that get passed to @cmd:mantys in the #arg[theme-options] key. + +#codesnippet(```typ +#show: mantys( + ..toml-info(read), + + theme: themes.orly, + theme-options: ( + pic: image("assets/logo.png", width: 100%, ) + ) +) +```) + +#{ } + +=== Bundled themes + +==== Typst theme +#grid( + columns: (1fr, 50%), + column-gutter: 5%, + [ + The default theme for MANTYS. Based on the Typst documentation and website. + #frame(arg(theme: "default", _value: theme-var)) + #text(.88em)[Example: This manual.] + ], + image("assets/examples/theme-typst.png", width: 100%), +) + +==== Modern theme +#grid( + columns: (1fr, 50%), + column-gutter: 5%, + [ + A slightly more modern theme for the digital age. Based on the #link("https://creativecommons.org/2019/10/30/cc-style-guide/", [Creative Commons Style Guide]). + #frame(arg(theme: "modern", _value: theme-var)) + #text(.88em)[Example: The manual for #universe("finite").] + ], + image("assets/examples/theme-modern.png", width: 100%), +) + +==== CNLTX theme +#grid( + columns: (1fr, 50%), + column-gutter: 5%, + [ + This theme is based on the original CNLTX template. + #frame(arg(theme: "cnltx", _value: theme-var)) + ], + image("assets/examples/theme-cnltx.png", width: 100%), +) + +==== O'Rly#super[?] theme +#grid( + columns: (1fr, 50%), + column-gutter: 5%, + [ + This theme uses the #universe("fauxreilly") package to create a style similar to an O'Reilly book. + #frame(arg(theme: "orly", _value: theme-var)) + ], + image("assets/examples/theme-orly.png", width: 100%), +) + +===== Theme Options +#argument("title-image", types: content)[ + #typ.content to be passed to the #arg[pic] argument of #cmd-("orly", module:"fauxreilly"). +] + +=== Creating a custom theme + +A theme is a #typ.t.dict ot #typ.module with a set of predefined keys for color and font information. See the #link(, "default theme") for a full list of keys and their meaning. + +#balanced-cols(3, clearance: 0pt)[ + #schema("theme", themes.default, color: _type-colors.color) +] + +#arg[primary] and #arg[secondary] are the main color scheme of the theme. #arg[fonts] is a #typ.t.dict of the main fontsets used. + +#arg[page-init] is a #typ.t.func called during template initialization to add custom #typ.set rules and other global settings to the document. #arg[title-page] and #arg[last-page] are called once at the beginning and end of the document to add a title and final page to the manual respectively. All three are functions of #lambda(ret:"content", "document", "theme"). + +#arg[alert] is a function #lambda(str, content, ret:content) that receives an #arg[alert-type] + +#info-alert[When writing a custom theme, remember to add #typ.pagebreak at the end of #arg[title-page], if your title page is supposed to be on its own page. Same goes for #arg[last-page].] + +=== Theme helpers + +If you don't want to create a complete #dtype("theme") on your own, but want to modify the color scheme of an existing theme, you can quickly do that with one of these helper functions. + +#command("create-theme", sarg[theme-spec], arg(base-theme: "default", _value: theme-var))[ + Creates a theme from the passed in arguments. #sarg[theme-spec] should be key-value pairs from the #dtype("theme") specification. Any missing keys are copied from the theme passed in as #arg[base-theme]. +] + +#command("color-theme", arg[primary], arg[secondary], sarg[theme-spec], arg(base-theme: "default", _value: theme-var))[ + Creates a new theme from a #arg[primary] and a #arg[secondary] color. Further arguments are passed to @cmd:create-theme along with #arg[base-theme]. + + #codesnippet[```typ + #show: mantys( + ..toml-info(read), + + theme: color-theme(blue, red, muted: (fill: yellow), base: themes.cnltx), + ) + ```] +] + +== The index + +MANTYS adds an index of all commands and custom types to the end of the manual. You can modify this index in several ways. + +=== Adding entries to the index + +Using @cmd:idx you can add new entries to the index. Entries may be categorized by #arg[kind]. Commands have #arg(kind: "cmd") set and custom types #arg(kind: "type"). You may add arbitrary new types. If your package handles colors, you may want to add a "color" category like this: +```typc +idx("red", kind: "color") +``` + +=== Showing index entries by category + +The default index can be disabled by passing #arg(show-index: false) to @cmd:mantys. + +To manually show an index in the manual, use @cmd:make-index. + +// #command("make-index", arg(kind: auto))[ +// Shows an index of the specified #arg[kind]. +// ] +#show-module("core/index", show-outline: false) + +This example creates an index of hex-colors. Since they all start with `#`, the grouping function is changed to group by the red component of the color. +#example[```typ + #for c in (red, green, yellow, blue) { + idx( + c.to-hex(), + kind:"color", + display:box(inset:2pt,baseline:3pt,fill:c, text(white, c.to-hex()))) + } + + #block(height:10em, columns(2)[ + #make-index( + kind:"color", + entry-format: (term, pages) => [#term #box(width: 1fr, repeat[.]) (#pages.join(", "))\ ], + grouping: it => it.term.slice(1, count:2) + ) + ]) + ```] + +Index entries are defined by a #arg[term] and a #arg[kind] that groups terms. +#schema( + "index-entry", + ( + term: str, + kind: str, + main: bool, + display: content, + ), +) + +== Examples + += Available commands + +// #show-module("mantys") + +== API +#let apis = ( + Commands: "api/commands", + Types: "api/types", + Values: "api/values", + Links: "api/links", + Elements: "api/elements", + Examples: "api/examples", + Icons: "api/icons", +) +#for (name, file) in apis { + [=== #name] + show-module(file, omit-private-parameters: true, sort-functions: false) +} + +== Utilities +#show-module("util/utils", module: "utils") + +== Shortcut collection of builtin types + +The #var("typ") dictionary is a shortcut to the common Typst builtin functions, types (#var("typ.t")) and values (#var("typ.v")). + +=== Shortcuts for builtin commands +#balanced-cols( + 3, + { + set text(.88em) + for (k, v) in typ { + if type(v) != dictionary [ + / #var-("typ." + k): + ] + } + }, +) + +=== Shortcuts for builtin types +#balanced-cols( + 3, + { + set text(.88em) + for (k, v) in typ.t [ + / #var-("typ.t." + k): + ] + }, +) + +=== Shortcuts for builtin values +#balanced-cols( + 3, + { + set text(.88em) + for (k, v) in typ.v [ + / #var-("typ.v." + k): + ] + }, +) + += Index +#columns(3, make-index(kind: ("cmd", "arg", "type"))) diff --git a/packages/preview/mantys/1.0.0/docs/test.typ b/packages/preview/mantys/1.0.0/docs/test.typ new file mode 100644 index 0000000000..08579e36d4 --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/test.typ @@ -0,0 +1,23 @@ +#import "@preview/tidy:0.4.0": * + + +#let data = ```typ +/// Create an URL. +/// -> str +#let url( + /// Host part of the URL. + /// -> str + host, + /// URL scheme to use. + /// + /// *!! Causes tidy to fail parsing the module without an error.* + /// + /// -> str + scheme: "https://" +) = scheme + host +```.text + +#let m = parse-module(data) +#show-module(m) + +#m diff --git a/packages/preview/mantys/1.0.0/src/_api.typ b/packages/preview/mantys/1.0.0/src/_api.typ new file mode 100644 index 0000000000..e11b35e9c7 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/_api.typ @@ -0,0 +1,14 @@ + +// TODO: (jneug) cleanup imports, remove star imports +#import "util/utils.typ" + +#import "api/elements.typ": * +#import "api/commands.typ": * +#import "api/types.typ": * +#import "api/values.typ": * +#import "api/examples.typ": * +#import "api/links.typ": * +#import "api/icons.typ": * +#import "api/collections.typ": typ + +#import "core/index.typ": idx, make-index diff --git a/packages/preview/mantys/1.0.0/src/_deps.typ b/packages/preview/mantys/1.0.0/src/_deps.typ new file mode 100644 index 0000000000..ae8c5842b9 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/_deps.typ @@ -0,0 +1,12 @@ +#import "@preview/valkyrie:0.2.1" + +#import "@preview/tidy:0.4.0" + +#import "@preview/typearea:0.2.0" +#import "@preview/hydra:0.5.2" +#import "@preview/marginalia:0.1.1" + +#import "@preview/showybox:2.0.3" +#import "@preview/codly:1.2.0" + +#import "@preview/octique:0.1.0" diff --git a/packages/preview/mantys/1.0.0/src/api/collections.typ b/packages/preview/mantys/1.0.0/src/api/collections.typ new file mode 100644 index 0000000000..df80f298ae --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/collections.typ @@ -0,0 +1,38 @@ +#import "types.typ" as t +#import "values.typ" as v +#import "links.typ" as l +#import "commands.typ" as c + +#let typ = ( + l + ._builtin-map + .keys() + .fold( + (:), + (d, cmd) => { + d.insert(cmd, c.builtin(cmd)) + d + }, + ) + + ( + t: (t._type-map + t._type-aliases) + .pairs() + .fold( + (:), + (d, (n, v)) => { + d.insert(n, t.dtype(v)) + d + }, + ), + ) + + ( + v: ( + "false": v.value(false), + "true": v.value(true), + "none": v.value(none), + "auto": v.value(auto), + "dict": v.value((:)), + "arr": v.value(()), + ), + ) +) diff --git a/packages/preview/mantys/1.0.0/src/api/commands.typ b/packages/preview/mantys/1.0.0/src/api/commands.typ new file mode 100644 index 0000000000..71d5349b3f --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/commands.typ @@ -0,0 +1,686 @@ + +#import "../util/is.typ" +#import "../util/utils.typ" +#import "../core/index.typ": idx +#import "../core/themes.typ": themable +#import "../core/styles.typ" + +#import "icons.typ" +#import "values.typ" +#import "types.typ": dtype, dtypes +#import "links.typ": link-builtin +#import "elements.typ" as elements: module as _module + + +/// Highlight an argument name. +/// +/// #ex(`#meta[variable]`) +/// -> content +#let meta( + /// Name of the argument. + /// -> str | content + name, + /// Prefix to #arg[name]. + /// -> str | content | symbol + l: sym.angle.l, + /// Prefix to #arg[name]. + /// -> str | content | symbol + r: sym.angle.r, +) = styles.meta(name, l: l, r: r) + + +/// Shows an argument, either positional or named. +/// The argument name is highlighted with @cmd:meta and the value with @cmd:value. +/// +/// - #ex(`#arg[name]`) +/// - #ex(`#arg("name")`) +/// - #ex(`#arg(name: "value")`) +/// - #ex(`#arg("name", 5.2)`) +/// +/// -> content +#let arg( + /// Either an argument name (#dtype("string")) or a (`name`: `value`) pair either as a named argument or as exactly two positional arguments. + /// -> any + ..args, + _value: values.value, +) = { + let a = none + if args.pos().len() == 1 { + a = styles.arg(utils.get-text(args.pos().first())) + } else if args.named() != (:) { + a = { + styles.arg(args.named().keys().first()) + utils.rawi(sym.colon + " ") + _value(args.named().values().first()) + } + } else if args.pos().len() == 2 { + a = { + styles.arg(args.pos().first()) + utils.rawi(sym.colon + " ") + _value(args.pos().at(1)) + } + } else { + panic("Wrong argument count. Got " + repr(args.pos())) + return + } + a +} + + +/// Shows a body argument. +/// #info-alert[ +/// Body arguments are positional arguments that can be given +/// as a separat content block at the end of a command. +/// ] +/// +/// - #ex(`#barg[body]`) +/// +/// -> content +#let barg( + /// Name of the argument. + /// -> str + name, +) = styles.barg(name) + + +/// Shows a "code" argument. +/// #info-alert)[ +/// "Code" are blocks og Typst code wrapped in braces: `{ ... }`. They are not an actual argument, but evaluate to some other type. +/// ] +/// - #ex(`#carg[code]`) +/// -> content +#let carg( + /// Name of the argument. + /// -> str + name, +) = styles.carg(name) + + +/// Shows an argument sink / variadic argument. +/// - #ex(`#sarg[args]`) +/// -> content +#let sarg( + /// Name of the argument. + /// -> str + name, +) = styles.sarg(name) + + +/// Creates a list of arguments from a set of positional and/or named arguments. +/// +/// #typ.t.string;s and named arguments are passed to @cmd:arg, while #typ.t.content +/// arguments are passed to @cmd:barg. +/// The result should be unpacked as arguments to @cmd:cmd. +/// #example[``` +/// #cmd( "conditional-show", ..args(hide: false, [body]) ) +/// ```] +/// +/// -> array +#let args( + /// Either an argument name (#typ.t.string) or a (`name`: `value`) pair either as a named argument or as exactly two positional arguments. + /// -> any + ..args, +) = { + let arguments = args.pos().filter(is.str).map(arg) + arguments += args.pos().filter(is.content).map(barg) + arguments += args.named().pairs().map(v => arg(v.at(0), v.at(1))) + arguments +} + + +/// Create a lambda function argument. +/// +/// Lambda arguments may be used as an argument value with @cmd:arg. +/// #info-alert[To show a lambda function with an +/// argument sink, prefix the type with two dots.] +/// +/// - #ex(`#lambda(int, str)`) +/// - #ex(`#lambda("ratio", "length")`) +/// - #ex(`#lambda("int", int, ret:bool)`) +/// - #ex(`#lambda("int", int, ret:(int,str))`) +/// - #ex(`#lambda("int", int, ret:(name: str))`) +/// - #ex(`#lambda("int", int, ret:(str,))`) +/// +/// -> content +#let lambda( + /// Argument types of the function parameters. + /// -> str | type + ..args, + /// Type of the returned value. + /// -> str | type + ret: none, +) = { + // TODO: (jneug) improve implementation (use #styles) + args = args + .pos() + .map(v => { + if type(v) == str and v.starts-with("..") { + ".." + dtype(v.slice(2)) + } else { + dtype(v) + } + }) + if type(ret) == array and ret.len() > 0 { + ret = ( + sym.paren.l + + ret.map(dtype).join(",") + + if ret.len() == 1 { + "," + } + + sym.paren.r + ) + } else if type(ret) == dictionary and ret.len() > 0 { + ret = ( + sym.paren.l + + ret + .pairs() + .map(pair => { + pair.first() + sym.colon + dtype(pair.last()) + }) + .join(",") + + sym.paren.r + ) + } else { + ret = dtype(ret) + } + styles.lambda(args, ret) +} + + +/// #property(changed: version(1,0,0)) +/// Renders the command #arg[name] with arguments and adds an entry with +/// #arg(kind:"cmd") to the index. +/// +/// #sarg[args] is a collection of positional arguments created with @cmd:arg, +/// @cmd:barg and @cmd:sarg (or @cmd:args). +/// +/// All positional arguments will be rendered first, then named arguments +/// and all body arguments will be added after the closing paranthesis. The relative order of each argument type is kept. +/// +/// #example[``` +/// - #cmd("cmd", arg[name], sarg[args], barg[body]) +/// - #cmd("cmd", ..args("name", [body]), sarg[args], module:"mod") +/// - #cmd("clamp", arg[value], arg[min], arg[max], module:"math", ret:int, unpack:true) +/// ```] +/// +/// -> content +#let cmd( + /// Name of the command. + /// -> str + name, + /// Name of the commands module. Will be used as a prefix and appear in the index. + /// -> str + module: none, + /// Return type. + /// -> str | type + ret: none, + /// If #typ.v.false, this location is not added to the index. + /// -> bool + index: true, + /// If #typ.v.true, the arguments are shown in separate lines. + /// -> bool + unpack: false, + /// Arguments for the command, created with individual argument commands (@cmd:arg, @cmd:barg, @cmd:sarg) or @cmd:args. + /// -> content + ..args, +) = { + if index in (true, "main") { + idx( + name, + kind: "cmd", + main: index == "main", + display: styles.cmd(name, module: module), + ) + } + + // TODO: (jneug) Add module marker ? + // themable(theme => text(theme.commands.command, utils.rawi(name))) + styles.cmd(name, module: module) + + let fargs = args.pos().filter(arg => not is.barg(arg)) + let bargs = args.pos().filter(is.barg) + + if fargs == () { } else if unpack == true or (unpack == auto and fargs.len() >= 5) { + utils.rawi(sym.paren.l) + [\ #h(1em)] + fargs.join([`,`\ #h(1em)]) + [\ ] + utils.rawi(sym.paren.r) + } else { + utils.rawi(sym.paren.l) + fargs.join(`, `) + utils.rawi(sym.paren.r) + } + bargs.join() + if ret != none { + ret = (ret,).flatten() + box(inset: (x: 2pt), sym.arrow.r) //utils.rawi("->")) + ret.map(dtype).join(" | ") //dtypes(..ret) + } +} + + +/// Same as @cmd:cmd, but does not create an index entry (#arg(index: false)). +/// -> content +#let cmd- = cmd.with(index: false) + + +/// Shows the variable #arg[name] and adds an entry to the index. +/// - #ex(`#var[colors]`) +/// -> content +#let var( + /// Name of the variable. + /// -> str + name, + /// Name of the commands module. Will be used as a prefix and appear in the index. + /// -> str + module: none, + /// If #typ.v.false, this location is not added to the index. + /// -> bool + index: true, +) = { + if index in (true, "main") { + idx( + name, + kind: "var", + main: index == "main", + display: styles.cmd(name, module: module, color: "variable"), + ) + } + styles.cmd(name, module: module, color: "variable") +} + + +/// Same as var, but does not create an index entry. +/// -> content +#let var- = var.with(index: false) + + +/// Displays a built-in Typst function with a link to the documentation. +/// - #ex(`#builtin[context]`) +/// - #ex(`#builtin(module:"math")[clamp]`) +/// -> content +#let builtin( + /// Name of the function (eg. `raw`). + /// -> str, content + name, + /// Optional module name. + /// -> str + module: none, +) = { + let name = utils.get-text(name) + link-builtin(name, styles.cmd(name, color: "builtin", module: module)) +} + + +// Internal map of known command properties. +#let _properties = ( + default: (k, v) => elements.alert("none")[*#utils.rawi(k)*: #utils.rawi(v)], + // + deprecated: (..) => elements.note( + dy: -2em, + styles.pill( + "emph.deprecated", + ( + icons.icon("circle-slash") + sym.space.nobreak + "deprecated" + ), + ), + ), + // + since: (_, v) => elements.note( + dy: -2em, + styles.pill( + "emph.since", + ( + icons.icon("arrow-up") + sym.space.nobreak + "Introduced in " + str(v) + ), + ), + ), + // + until: (_, v) => elements.note( + dy: -2em, + styles.pill( + "emph.until", + ( + icons.icon("arrow-down") + sym.space.nobreak + "Available until " + str(v) + ), + ), + ), + // + requires-context: (..) => elements.note( + dy: -2em, + styles.pill( + "emph.context", + ( + icons.icon("pulse") + sym.space.nobreak + "context" + ), + ), + ), + // + compiler: (_, v) => elements.note( + dy: -2em, + styles.pill( + "emph.compiler", + ( + icons.typst + sym.space.nobreak + str(v) + ), + ), + ), + // + changed: (_, v) => elements.note( + dy: -2em, + styles.pill( + "emph.changed", + ( + icons.icon("arrow-switch") + sym.space.nobreak + "Changed in " + str(v) + ), + ), + ), + // + see: ( + _, + v, + ) => elements.info-alert[#icons.icon("link-external") see #{(v,).flatten().map(t => if is.str(t) { link(t, t) } else { ref(t) } ).join(", ")}], + // + todo: (_, v) => elements.success-alert[#text(fill: green)[#icons.icon("check") *TODO*] #v], +) + + +/// Shows a command property (annotation). +/// This should be used in the #barg[body] of @cmd:command to +/// annotate a function with some special meaning. +/// +/// Properties are provided as named arguments to the @cmd:property +/// function. +/// +/// The following properties are currently known to MANTYS: +/// #property(since: version(1,0,1)) +/// / since #dtypes(version, str): Marks this function as available since a given package version. +/// +/// #property(until: version(0,1,4)) +/// / until #dtypes(version, str): Marks this function as available until a given package version. +/// +/// #property(deprecated: version(1,0,1)) +/// / deprecated #dtypes(bool, version, str): Marks this function as deprecated. If set to a version, the function is supposed to stay availalbel until the given version. +/// +/// #property(changed: version(0,12,0)) +/// / changed #dtypes(version, str): Marks function that changed in a specific package version. +/// +/// #property(compiler: version(0,12,0)) +/// / compiler #dtypes(version, str): Marks this function as only available on a specific compiler version. +/// +/// #property(requires-context: true) +/// / requires-context #dtype(bool): Requires a function to be used inside #builtin[context]. +/// +/// #property(see: (, "https://github.vom/jneug/typst-mantys")) +/// / see #dtype(array) of #dtypes(str, label): Adds references to other commands or websites. +/// +/// #property(todo: [ +/// - Add documentation. +/// - Add #arg[foo] paramter. +/// ]) +/// / todo #dtypes(str, content): Adds a todo note to the function. +/// +/// Other named properties will be shown as given: +/// #property(module: "utilities") +#let property( + /// Property name / value pairs. + /// -> any + ..args, +) = { + for (k, v) in args.named() { + if k in _properties { + (_properties.at(k))(k, v) + } else { + (_properties.default)(k, v) + } + } +} + + +/// Displays information of a command by formatting the name, description and arguments. +/// See this commands description for an example. +/// +/// The command is formated with @cmd:cmd and an index entry is added that is marked as the +/// "main" index entry for this command. +/// -> content +#let command( + /// Name of the command. + /// -> str + name, + /// Custom label for the command. + /// -> string | auto | none + label: auto, + /// Dictionary of properties to be passed to @cmd:property. + /// -> dictionary + properties: (:), + /// List of arguments created with the argument functions (@cmd:arg, @cmd:barg, @cmd:sarg) or @cmd:args. + /// -> content + ..args, + /// Description of the command. Usually some text and a series of @cmd:argument descriptions. + /// -> content + body, +) = block()[ + #if label not in (none, false) { + if label in (auto, true) { + utils.place-reference( + utils.create-label(name, module: args.named().at("module", default: none)), + "cmd", + "command", + ) + } else { + utils.place-reference(label, "cmd", "command") + } + } + #block( + below: 0.65em, + above: 1.3em, + breakable: false, + { + property(..properties) + text(weight: 600, cmd(name, unpack: auto, index: "main", ..args)) + }, + ) + //#pad(left: 1em, body)//#cmd-label(mty.def.if-auto(name, label)) + // #v(.65em, weak:true) + #block(inset: (left: 1em), width: 100%, body) +] + + +/// Displays information for a variable definition. +/// #example[``` +/// #variable("primary", types:("color",), value:green)[ +/// Primary color. +/// ] +/// ```] +/// -> content +#let variable( + /// Name of the variable. + /// -> string + name, + /// Array of types to be passed to @cmd:dtypes. + /// -> array + types: none, + /// Default value. + /// -> any + value: none, + /// Custom label for the variable. + /// -> string | auto | none + label: auto, + /// Dictionary of properties to be passed to @cmd:property. + /// -> dictionary + properties: (:), + /// Description of the variable. + /// -> content + body, +) = [ + #set terms(hanging-indent: 0pt) + #set par(first-line-indent: 0.65pt, hanging-indent: 0pt) + #let types = (types,).flatten() + #property(..properties) + / #var(name, index: "main")#if value != none { + sym.colon + " " + values.value(value) + }#if types != (none,) { + h(1fr) + dtypes(..types) + }: #block(inset: (left: 2em), body) +] + + +/// Displays information for a command argument. +/// See the argument list below for an example. +/// +/// #example[``` +/// #argument("category", default:"utilities")[ +/// #lorem(10) +/// ] +/// +/// #argument("category", choices: ("a", "b", "c"), default:"d")[ +/// #lorem(10) +/// ] +/// +/// #argument("style-args", title:"Style Arguments", +/// is-sink:true, types:(length, ratio))[ +/// #lorem(10) +/// ] +/// ```] +/// -> content +#let argument( + /// Name of the argument. + /// -> str + name, + /// If this is a variadic argument. + /// -> bool + is-sink: false, + /// Array of types to be passed to @cmd:dtypes. + /// -> array | none + types: none, + /// Optional array of valid values for this argument. + /// -> array | none + choices: none, + /// Optional default value for this argument. Will be automatically included in #arg[choices] if it is missing. To allow #value(none) as a default value, the default is #value("__none___"). + /// -> any + default: "__none__", + /// Title in the border of the surronding #typ.block. + /// -> str | none + title: "Argument", + /// Dictionary of properties to be passed to @cmd:property. + /// -> dictionary + properties: (:), + /// Optional information about the command this argument is attached to. Setting this to the name of a command will create a label for this argument in the form of `@cmd:cmd-name.arg-name`. + /// + /// #ex(`@cmd:argument.title`) + /// + /// #package[Tidy] will automatically set this to the appropriate command. + /// -> str | dictionary + command: none, + /// Description of the argument. + /// -> content + body, + _value: values.value, +) = { + types = (types,).flatten() + + // TODO (jneug) can this be automated? + if command != none { + if type(command) == str { + command = (name: command) + } + + utils.place-reference( + utils.create-label(command.name, arg: name, module: command.at("module", default: none)), + "arg", + "argument", + ) + } + + v(.65em) + /// TODO (jneug) use styles.typ + themable(theme => block( + width: 100%, + above: .65em, + stroke: .75pt + theme.muted.fill, + inset: (top: 10pt, rest: 8pt), + radius: 2pt, + { + property(..properties) + // Place title + if title != none { + place( + top + left, + dy: -15.5pt, + dx: 5.75pt, + box( + inset: 2pt, + fill: white, + text(size: .75em, font: theme.fonts.sans, theme.muted.fill, title), + ), + ) + } + if is-sink { + sarg(name) + } else if default != "__none__" { + arg(name, default, _value: _value) + } else { + arg(name) + } + h(1fr) + if types != (none,) { + dtypes(..types) + } else if default != "__none__" { + dtype(type(default)) + } + block(width: 100%, below: .65em, inset: (x: .75em), body) + }, + )) +} + + +/// Creates a reference to the command #arg[name]. +/// This is equivalent to using `@cmd:name`. +/// - #ex(`#cmdref("cmdref")`) +/// - #ex(`@cmd:cmdref`) +/// -> content +#let cmdref( + /// Name of the command. + /// -> str + name, + /// Optional module name. + /// -> str + module: none, + // TODO (jneug) add index argument (?) +) = { + ref(utils.create-label(name, module: module)) +} + + +/// Creates a reference to the argument #arg[name]. +/// This is equivalent to using `@cmd:command.name`. +/// - #ex(`#argref("argref", "name")`) +/// - #ex(`@cmd:argref.name`) +/// -> content +#let argref( + /// Name of the command. + /// -> str + command, + /// Name of the argument. + /// -> str + name, + /// Optional module name. + /// -> str + module: none, +) = { + ref(utils.create-label(command, arg: name, module: module)) +} + + +/// Creates a reference to the custom type #arg[name]. +/// This is equivalent to using `@type:name`. +/// +/// Note that the custom type has to be declared first. See @subsec:custom-types for more information about custom types. +/// +/// //- #ex(`#typeref("author")`) +/// //- #ex(`@type:author`) +/// -> content +#let typeref( + /// Name of the custom type. + /// -> content + name, +) = ref(utils.create-label(name, prefix: "type")) diff --git a/packages/preview/mantys/1.0.0/src/api/elements.typ b/packages/preview/mantys/1.0.0/src/api/elements.typ new file mode 100644 index 0000000000..ccf8ee9ec3 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/elements.typ @@ -0,0 +1,314 @@ +#import "../_deps.typ" as deps +#import "../core/themes.typ": themable +#import "../core/styles.typ" +#import "icons.typ": icon +#import "../util/utils.typ" + + +/// #is-themable() +/// Create a frame around some content. +/// #info-alert[Uses #package("showybox") and can take any arguments the +/// #cmd-[showybox] command can take.] +/// #example[``` +/// #frame(title:"Some lorem text")[#lorem(10)] +/// ```] +/// -> content +#let frame( + /// Arguments for #package[Showybox]. + /// -> content + ..args, +) = themable(theme => deps.showybox.showybox( + frame: ( + border-color: theme.primary, + title-color: theme.primary, + thickness: .75pt, + radius: 4pt, + inset: 8pt, + ), + ..args, +)) + + +/// #is-themable() +/// An alert box to highlight some content. +/// #example[``` +/// #alert("success")[#lorem(10)] +/// ```] +/// -> content +#let alert( + /// The type of the alert. One of #value("info"), #value("warning"), #value("error") or #value("success"). + /// -> str + alert-type, + /// Content of the alert. + /// -> content + body, +) = themable( + (theme, alert-type, body) => (theme.alert)(alert-type, body), + alert-type, + body, +) + + +/// #is-themable() +/// An info alert. +/// ```example +/// #info-alert[This is an #cmd-[info-alert].] +/// ``` +/// -> content +#let info-alert = alert.with("info") + +/// #is-themable() +/// A warning alert. +/// ```example +/// #warning-alert[This is an #cmd-[warning-alert].] +/// ``` +/// -> content +#let warning-alert = alert.with("warning") + +/// #is-themable() +/// An error alert. +/// ```example +/// #error-alert[This is an #cmd-[error-alert].] +/// ``` +/// -> content +#let error-alert = alert.with("error") + +/// #is-themable() +/// A success alert. +/// ```example +/// #success-alert[This is an #cmd-[success-alert].] +/// ``` +/// -> content +#let success-alert = alert.with("success") + +/// #is-themable() +/// Show a package name. +/// - #ex(`#package("codelst")`) +/// -> content +#let package( + /// Name of the package. + /// -> str + name, +) = themable(theme => text(theme.emph.package, smallcaps(name))) + +/// #is-themable() +/// Show a module name. +/// - #ex(`#module("util")`) +/// -> content +#let module( + /// Name of the module. + /// -> str + name, +) = themable(theme => text(theme.emph.module, utils.rawi(name))) + + +/// Highlight human names (with first- and lastnames). +/// - #ex(`#name("Jonas Neugebauer")`) +/// - #ex(`#name("J.", last:"Neugebauer")`) +/// -> content +#let name( + /// First or full name. + /// -> str + name, + /// Optional last name. + /// -> str | none + last: none, +) = { + if last == none { + let parts = utils.get-text(name).split(" ") + last = parts.pop() + name = parts.join(" ") + } + [#name #smallcaps(last)] +} + + +/// #is-themable() +/// Sets the text color of #arg[body] to a color from the @type:theme. +/// #arg[color] should be a key from the @type:theme. +/// - #ex(`#colorize([Manual], color: "muted.fill")`) +/// -> content +#let colorize( + /// Content to color. + /// -> content + body, + /// Key of the color in the theme. + /// -> str + color: "primary", +) = themable(theme => text( + fill: utils.dict-get(theme, color, default: black), + body, +)) + +/// #is-themable() +/// Colors #barg[body] in the themes primary color. +/// - #ex(`#primary[Manual]`) +/// -> content +#let primary( + /// Content to color. + /// -> content + body, +) = themable(theme => text(fill: theme.primary, body)) + + +/// #is-themable() +/// Colors #barg[body] in the themes secondary color. +/// - #ex(`#secondary[Manual]`) +/// -> content +#let secondary( + /// Content to color. + /// -> content + body, +) = themable(theme => text(fill: theme.secondary, body)) + + +/// Creates a #typ.version from #sarg[args]. If the first argument is a version, it is returned as given. +/// - #ex(`#ver(1, 4, 2)`) +/// - #ex(`#ver(version(1, 4, 3))`) +/// -> version +#let ver( + /// Components of the version. + /// -> version | int + ..args, +) = { + if type(args.pos().first()) != version { + version(..args.pos()) + } else { + args.pos().first() + } +} + + +/// Show a margin note in the left margin. +/// See @cmd:since and @cmd:until for examples. +/// -> content +#let note( + /// Arguments to pass to #cmd(module: "drafting", "margin-note"). + /// -> any + ..args, + /// Body of the note. + /// -> content + body, +) = { + // deps.drafting.margin-note(..args)[ + // // #set align(right) + // #set text(.7em) + // #body + // ] + deps.marginalia.note( + reverse: true, + numbered: false, + ..args, + )[ + // #set align(right) + #set text(.7em, style: "normal") + #body + ] +} + + +/// #is-themable() +/// Show a margin-note with a minimal package version. +/// - #ex(`#since(1,2,3)`) +/// #property(see: (, )) +/// -> content +#let since( + /// Components of the version number. + /// -> int | version + ..args, +) = { + note( + styles.pill( + "emph.since", + ( + icon("arrow-up") + sym.space.nobreak + "Introduced in " + str(ver(..args)) + ), + ), + ) +} + +/// #is-themable() +/// Show a margin-note with a maximum package version. +/// - #ex(`#until(1,2,3)`) +/// #property(see: (, )) +/// -> content +#let until( + /// Components of the version number. + /// -> int | version + ..args, +) = note( + styles.pill( + "emph.until", + ( + icon("arrow-down") + sym.space.nobreak + "Available until " + str(ver(..args)) + ), + ), +) + + +/// #is-themable() +/// Show a margin-note with a version number. +/// - #ex(`#changed(1,2,3)`) +/// #property(see: (, )) +/// -> content +#let changed( + /// Components of the version number. + /// -> int | version + ..args, +) = note( + styles.pill( + "emph.changed", + ( + icon("arrow-switch") + sym.space.nobreak + "Changed in " + str(ver(..args)) + ), + ), +) + + +/// #is-themable() +/// Show a margin-note with a deprecated warning. +/// - #ex(`#deprecated()`) +/// #property(see: (, )) +/// -> content +#let deprecated() = note( + styles.pill( + "emph.deprecated", + ( + icon("circle-slash") + sym.space.nobreak + "deprecated" + ), + ), +) + + +/// #is-themable() +/// Show a margin-note with a minimal Typst compiler version. +/// - #ex(`#compiler(1,2,3)`) +/// #property(see: (, )) +/// -> content +#let compiler( + /// Components of the version number. + /// -> int | version + ..args, +) = note( + styles.pill( + "emph.compiler", + ( + deps.codly.typst-icon.typ.icon + sym.space.nobreak + str(ver(..args)) + ), + ), +) + + +/// #is-themable() +/// Show a margin-note with a context warning. +/// - #ex(`#requires-context()`) +/// #property(see: (, )) +/// -> content +#let requires-context() = note( + styles.pill( + "emph.context", + ( + icon("pulse") + sym.space.nobreak + "context" + ), + ), +) diff --git a/packages/preview/mantys/1.0.0/src/api/examples.typ b/packages/preview/mantys/1.0.0/src/api/examples.typ new file mode 100644 index 0000000000..1b26373fb3 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/examples.typ @@ -0,0 +1,335 @@ +#import "../_deps.typ" as deps +#import "../core/document.typ" +#import "../util/utils.typ" +#import "../util/is.typ" +#import "../core/themes.typ": themable + +#import "elements.typ": frame +#import "icons.typ": icon + + +/// Shows sourcecode in a @cmd:frame. +/// See @sec:examples for more information on sourcecode and examples. +/// ````example +/// #sourcecode( +/// title:"Example", +/// file:"sourcecode-example.typ" +/// )[```typ +/// #let module-name = "sourcecode-example" +/// ```] +/// ```` +/// -> content +#let sourcecode( + /// A title to show on top of the frame. + /// -> str + title: none, + /// A filename to show in the title of the frame. + /// -> str + file: none, + /// Arguments for #cmd-(module:"codly")[local]. + /// -> any + ..args, + /// A #typ.raw block of Typst code. + /// -> content + code, +) = { + let header = () + if title != none { + header.push(text(fill: white, title)) + } + if file != none { + header.push(h(1fr)) + header.push(text(fill: white, icon("file")) + sym.space.nobreak) + header.push(text(fill: white, emph(file))) + } + + frame( + title: if header == () { + "" + } else { + header.join() + }, + deps.codly.local(..args, code), + ) +} + + +/// Shows some #typ.raw code in a @cmd:frame, but +/// without line numbers or other enhancements. +/// +/// ````example +/// #codesnippet[```typc +/// let a = "some content" +/// [Content: #a] +/// ```] +/// ```` +/// -> content +#let codesnippet( + /// If #typ.v.true, line numbers are shown. + /// -> bool + number-format: none, + /// Arguments for #cmd-(module:"codly")[local]. + /// -> any + ..args, + /// A #typ.raw block of Typst code. + /// -> content + code, +) = frame(deps.codly.local(number-format: number-format, ..args, code)) + + +/// Show an example by evaluating the given #typ.raw code with Typst and showing the source and result in a @cmd:frame. +/// +/// See @sec:examples for more information on sourcecode and examples. +/// +/// -> content +#let example( + /// Shows the source and example in two columns instead of the result beneath the source. + /// -> content + side-by-side: false, + /// A scope to pass to #typ.eval. + /// -> dict + scope: (:), + /// Additional imports for evaluating this example. Imports will be added as a preamble to #arg[example-code]. + /// -> dict + imports: (:), + /// Set to #typ.v.false to *not* use the global #arg[examples-scope] passed to @cmd:mantys. + /// -> bool + use-examples-scope: true, + /// The evaulation mode: #choices("markup", "code", "math") + /// -> str + mode: "markup", + /// If #typ.v.true, the frame may brake over multiple pages. + /// -> bool + breakable: false, + /// A #typ.raw block of Typst code. + /// -> content + example-code, + /// An optional second positional argument that overwrites the evaluation result. This can be used to show the result of a sourcecode, that can not evaulated directly. + /// -> content + ..args, +) = context { + if args.named() != (:) { + panic("unexpected arguments", args.named().keys().join(", ")) + } + if args.pos().len() > 1 { + panic("unexpected argument") + } + + let code = example-code + if not code.func() == raw { + code = example-code.children.find(it => it.func() == raw) + } + let cont = ( + raw( + lang: if mode == "code" { + "typc" + } else { + "typ" + }, + code.text, + ), + ) + if not side-by-side { + cont.push(themable(theme => line(length: 100%, stroke: .75pt + theme.text.fill))) + } + + // If the result was provided as an argument, use that, + // otherwise eval the given example as code or content. + if args.pos() != () { + cont.push(args.pos().first()) + } else if not use-examples-scope { + cont.push( + eval( + mode: mode, + scope: scope, + utils.add-preamble( + code.text, + imports, + ), + ), + ) + } else { + let doc = document.get() + cont.push( + eval( + mode: mode, + scope: doc.examples-scope.scope + scope, + utils.add-preamble( + code.text, + doc.examples-scope.imports + imports, + ), + ), + ) + } + + frame( + breakable: breakable, + grid( + columns: if side-by-side { + (1fr, 1fr) + } else { + (1fr,) + }, + gutter: 12pt, + ..cont + ), + ) +} + + +/// Same as @cmd:example, but with #arg(side-by-side: true). +/// -> content +#let side-by-side = example.with(side-by-side: true) + + +/// Show a "short example" by showing #arg[code] and the evaluation of #arg[code] separated +/// by #arg[sep]. This can be used for quick one-line examples as seen in @cmd:name and other command docs in this manual. +/// +/// ```example +/// - #ex(`#name("Jonas Neugebauer")`) +/// - #ex(`#meta("arg-name")`, sep: ": ") +/// ``` +/// -> content +#let ex( + /// The #typ.raw code example to show. + /// -> content + code, + /// The separator between #arg[code] and its evaluated result. + /// -> content + sep: [ #sym.arrow.r ], + /// One of #choices("markup", "code", "math"). + /// -> str + mode: "markup", + /// A scope argument similar to @type:examples-scope. + /// -> dict + scope: (:), +) = context { + let doc = document.get() + raw( + code.text, + lang: "typ", + ) + sep + eval( + utils.build-preamble(doc.examples-scope.imports) + code.text, + mode: "markup", + scope: doc.examples-scope.scope + scope, + ) +} + + +/// Alias for @cmd:ex. +/// #property(deprecated: true) +#let shortex = ex + + +/// Shows an import statement for this package. The name and version from the document are used by default. +/// #example[``` +/// #show-import() +/// #show-import(repository: "@local", imports: "mantys", mode:"code") +/// ```] +/// -> content +#let show-import( + /// Custom package repository to show. + /// -> str + repository: "@preview", + /// What to import from the package. Use #value(none) to just import the package into the global scope. + /// -> str | none + imports: "*", + /// Package name for the import. + /// -> str | auto + name: auto, + /// Package version for the import. + /// -> version | auto + version: auto, + /// One of #choices("markup", "code"). Will show the import in markup or code mode. + /// -> str + mode: "markup", + /// Additional code to add after the import. Useful if your package requires some more steps for initialization. + /// ```example + /// #show-import(name: "codly", version: version(1,1,1), code: "#show: codly-init") + /// ``` + /// -> str | auto + code: none, +) = { + document.use(doc => { + let name = if name == auto { + doc.package.name + } else { + name + } + let version = if version == auto { + doc.package.version + } else { + version + } + codesnippet( + raw( + lang: if mode == "markup" { + "typ" + } else { + "typc" + }, + if mode == "markup" { + "#" + } else { + "" + } + + "import \"" + + repository + + "/" + + name + + ":" + + str(version) + + "\"" + + if imports != none { + ": " + imports + } + + if code != none { "\n" + if is.raw(code) { code.text } else { code } }, + ), + ) + }) +} + + +/// Shows a git clone command for this package. The name and version from the document are used by default. +/// ```example +/// #show-git-clone() +/// #show-git-clone(repository: "typst/packages", out:"preview/mantys/1.0.0") +/// ``` +#let show-git-clone( + /// Custom package repository to show. + /// -> str | auto + repository: auto, + /// Output path to clone into. + /// -> str | none | auto + out: auto, + /// Syntax language to passs to #typ.raw. + /// -> str + lang: "bash", +) = { + document.use(doc => { + let repo = if repository == auto { + doc.package.repository + } else { + repository + } + let url = if not repo.starts-with(regex("https?://")) { + "https://github.com/" + repo + } else { + repo + } + let out = if out == auto { + doc.package.name + "/" + str(doc.package.version) + } else { + out + } + + codesnippet( + raw( + lang: lang, + "git clone " + url + " " + out, + ), + ) + }) +} diff --git a/packages/preview/mantys/1.0.0/src/api/icons.typ b/packages/preview/mantys/1.0.0/src/api/icons.typ new file mode 100644 index 0000000000..371b1bb89d --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/icons.typ @@ -0,0 +1,32 @@ +#import "../_deps.typ": octique, codly + +/// Shows an icon from the #github("0x6b/typst-octique") package. +/// -> content +#let icon( + /// - name: A name from the Octique icon set. + /// -> str + name, + /// - fill: The fill color for the icon. #value(auto) will use the fill of the surrounding text. + /// -> color | auto + fill: auto, + /// - ..args: Further args for the #cmd[octique] command. + /// -> any + ..args, +) = context { + octique.octique-inline( + name, + color: if fill == auto { text.fill } else { fill }, + baseline: 10%, + ..args, + ) +} + +/// The default info icon: #icons.info +#let info = icon("info") + +/// The default info icon: #icons.warning +#let warning = icon("alert-fill") + + +/// Typst icon provided by #universe("codly"): #icons.typst +#let typst = codly.typst-icon.typ.icon diff --git a/packages/preview/mantys/1.0.0/src/api/links.typ b/packages/preview/mantys/1.0.0/src/api/links.typ new file mode 100644 index 0000000000..782046d2df --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/links.typ @@ -0,0 +1,570 @@ +#import "../util/utils.typ" +#import "../core/document.typ" +#import "elements.typ": package + + +#let _type-map = ( + "auto": "foundations/auto", + "none": "foundations/none", + // foundation + arguments: "foundations/arguments", + array: "foundations/array", + boolean: "foundations/bool", + bytes: "foundations/bytes", + content: "foundations/content", + datetime: "foundations/datetime", + dictionary: "foundations/dictionary", + float: "foundations/float", + function: "foundations/function", + integer: "foundations/int", + location: "foundations/location", + module: "foundations/module", + plugin: "foundations/plugin", + regex: "foundations/regex", + selector: "foundations/selector", + string: "foundations/str", + type: "foundations/type", + label: "foundations/label", + version: "foundations/version", + // layout + alignment: "layout/alignment", + angle: "layout/angle", + direction: "layout/direction", + fraction: "layout/fraction", + length: "layout/length", + ratio: "layout/ratio", + relative: "layout/relative", + // visualize + color: "visualize/color", + gradient: "visualize/gradient", + stroke: "visualize/stroke", +) + +#let _builtin-map = ( + // tutorial + "set": "../tutorial/formatting/#set-rules", + "show": "../tutorial/formatting/#show-rules", + // scripting + "import": "scripting/#modules", + // foundations + "context": "context", + arguments: "foundations/arguments", + array: "foundations/array", + assert: "foundations/assert", + "auto": "foundations/auto", + bool: "foundations/bool", + bytes: "foundations/bytes", + with: "foundations/function/#definitions-with", + // Calc and its functions + calc: "foundations/calc", + abs: "foundations/calc#function-abs", + pow: "foundations/calc#function-pow", + exp: "foundations/calc#function-exp", + sqrt: "foundations/calc#function-sqrt", + root: "foundations/calc#function-root", + sin: "foundations/calc#function-sin", + cos: "foundations/calc#function-cos", + tan: "foundations/calc#function-tan", + asin: "foundations/calc#function-asin", + acos: "foundations/calc#function-acos", + atan: "foundations/calc#function-atan", + atan2: "foundations/calc#function-atan2", + sinh: "foundations/calc#function-sinh", + cosh: "foundations/calc#function-cosh", + tanh: "foundations/calc#function-tanh", + log: "foundations/calc#function-log", + ln: "foundations/calc#function-ln", + fact: "foundations/calc#function-fact", + perm: "foundations/calc#function-perm", + binom: "foundations/calc#function-binom", + gcd: "foundations/calc#function-gcd", + lcm: "foundations/calc#function-lcm", + floor: "foundations/calc#function-floor", + ceil: "foundations/calc#function-ceil", + trunc: "foundations/calc#function-trunc", + fract: "foundations/calc#function-fract", + round: "foundations/calc#function-round", + clamp: "foundations/calc#function-clamp", + min: "foundations/calc#function-min", + max: "foundations/calc#function-max", + even: "foundations/calc#function-even", + odd: "foundations/calc#function-odd", + rem: "foundations/calc#function-rem", + quo: "foundations/calc#function-quo", + // + content: "foundations/content", + datetime: "foundations/datetime", + dictionary: "foundations/dictionary", + duration: "foundations/duration", + eval: "foundations/eval", + float: "foundations/float", + function: "foundations/function", + int: "foundations/int", + label: "foundations/label", + module: "foundations/module", + "none": "foundations/none", + panic: "foundations/panic", + plugin: "foundations/plugin", + regex: "foundations/regex", + repr: "foundations/repr", + selector: "foundations/selector", + str: "foundations/str", + style: "foundations/style", + sys: "foundations/sys", + type: "foundations/type", + version: "foundations/version", + // model + bibliography: "model/bibliography", + cite: "model/cite", + document: "model/document", + figure: "model/figure", + emph: "model/emph", + enum: "model/enum", + list: "model/list", + numbering: "model/numbering", + outline: "model/outline", + par: "model/par", + parbreak: "model/parbreak", + quote: "model/quote", + strong: "model/strong", + ref: "model/ref", + table: "model/table", + terms: "model/terms", + link: "model/link", + // text + raw: "text/raw", + text: "text/text", + highlight: "text/highlight", + linebreak: "text/linebreak", + lorem: "text/lorem", + lower: "text/lower", + upper: "text/upper", + overline: "text/overline", + underline: "text/underline", + smallcaps: "text/smallcaps", + smartquote: "text/smartquote", + strike: "text/strike", + sub: "text/sub", + super: "text/super", + // layout + align: "layout/align", + alignment: "layout/alignment", + angle: "layout/angle", + block: "layout/block", + box: "layout/box", + colbreak: "layout/colbreak", + columns: "layout/columns", + direction: "layout/direction", + fraction: "layout/fraction", + grid: "layout/grid", + h: "layout/h", + hide: "layout/hide", + layout: "layout/layout", + length: "layout/length", + measure: "layout/measure", + move: "layout/move", + pad: "layout/pad", + page: "layout/page", + pagebreak: "layout/pagebreak", + place: "layout/place", + ratio: "layout/ratio", + relative: "layout/relative", + repeat: "layout/repeat", + rotate: "layout/rotate", + scale: "layout/scale", + stack: "layout/stack", + v: "layout/v", + // math + accent: "math/accent", + attach: "math/attach", + cancel: "math/cancel", + cases: "math/cases", + class: "math/class", + equation: "math/equation", + frac: "math/frac", + lr: "math/lr", + mat: "math/mat", + op: "math/op", + primes: "math/primes", + roots: "math/roots", + sizes: "math/sizes", + styles: "math/styles", + underover: "math/underover", + variants: "math/variants", + vec: "math/vec", + // visualize + circle: "visualize/circle", + color: "visualize/color", + ellipse: "visualize/ellipse", + gradient: "visualize/gradient", + image: "visualize/image", + line: "visualize/line", + path: "visualize/path", + pattern: "visualize/pattern", + polygon: "visualize/polygon", + rect: "visualize/rect", + square: "visualize/square", + stroke: "visualize/stroke", + // instrospection + counter: "introspection/counter", + here: "introspection/here", + locate: "introspection/locate", + location: "introspection/location", + metadata: "introspection/metadata", + query: "introspection/query", + state: "introspection/state", + // data-loading + cbor: "data-loading/cbor", + csv: "data-loading/csv", + json: "data-loading/json", + read: "data-loading/read", + toml: "data-loading/toml", + xml: "data-loading/xml", + yaml: "data-loading/yaml", +) + + +/// Utility function to create urls from components and +/// url parameters. +/// - #ex(`#url("forum.typst.app", "search", params: (q: "Mantys Package"))`) +/// - #ex(`#url("github@neugebauer.cc", scheme:"mailto:")`) +/// -> str +#let url( + /// Host of the url. Like #value("github.com"). + /// Note that the host should not include an URL-Scheme. + /// -> str + host, + /// Path components of the URL. + /// -> array + ..components, + /// URL-Scheme to use. + /// -> str + scheme: "https:/" + "/", + /// Optional anchor part. + /// -> str + anchor: none, + /// Dictionary of parameters to include in the URL. + params: (:), +) = { + if host.starts-with(scheme) { + host = host.slice(scheme.len()) + } + + let parts = (host,) + components.pos().filter(v => v != none).map(str) + let url = scheme + parts.join("/") + if params != none and params != (:) { + url += "?" + params.pairs().map(((k, v)) => k + "=" + utils.url-encode(v)).join("&") + } + if anchor != none and anchor != "" { + url += "#" + anchor + } + return url +} + + +/// Overloads the builtin #typ.link function to add a #arg[footnote] argument. +/// ```typ #link(url, lable, footnote: false)``` will not show the #arg[url] in +/// a footnote, independent of @cmd:mantys.show-urls-in-footnotes. +#let link(..args) = { + let dest = args.pos().first() + let body = if args.pos().len() > 1 { + args.pos().at(1) + } else { + dest + } + if not args.named().at("footnote", default: true) { + [#std.link(dest, body)] + } else { + [#std.link(dest, body)] + } +} + + +/// Utility function to create a #typ.link to the official Typst reference documentation. +/// - #ex(`#link-docs("introspection/counter")`) +/// - #ex(`#link-docs("introspection/counter", "counter")`) +/// -> content +#let link-docs( + /// Path in the docs and an optional label. + /// -> str + ..path, +) = std.link("https://typst.app/docs/reference/" + path.pos().first(), ..path.pos().slice(1)) + +/// Utility function to create a #typ.link to a data +/// type in the official Typst reference documentation. +/// - #ex(`#link-dtype("int")`) +/// - #ex(`#link-dtype("int", "number")`) +/// In most cases you should rather use @cmd:dtype for +/// linking directly to the documentation. +/// -> content +#let link-dtype( + /// Data type name and an optional label. + /// -> str + ..name, +) = link-docs(_type-map.at(name.pos().first(), default: ""), ..name.pos().slice(1)) + +/// Utility function to create a #typ.link to a builtin +/// function in the official Typst reference documentation. +/// - #ex(`#link-builtin("strong")`) +/// - #ex(`#link-builtin("strong", "emphasis")`) +/// In most cases you should rather use @cmd:builtin for +/// linking directly to the documentation. +/// -> content +#let link-builtin( + /// Function name and an optional label. + /// -> str + ..name, +) = link-docs(_builtin-map.at(name.pos().first(), default: ""), ..name.pos().slice(1)) + + +/// Utility function to create a link to a named repository, +/// usually at _github.com_, but the hostname can be +/// changed via the #arg[host] argument. +/// +/// With #arg(repo: auto) the repository stored in the @type:document is used, if any. +/// - #ex(`#link-repo(auto)`) +/// - #ex(`#link-repo("jneug/typst-finite")`) +/// - #ex(`#link-repo("Kuchenmampfer/flautomat", host:"codeberg.org")`) +#let link-repo(repo, host: "github.com", path: none, label: auto) = { + let make-link(repo) = { + link( + url( + host, + repo, + if path != none { path.trim("/", at: start) }, + ), + if label == auto { + repo + } else { + label + }, + ) + } + if repo != auto { + make-link(repo) + } else { + document.use-value( + "package.repository", + repo-url => { + if repo-url != none { + let m = repo-url.match(regex("https?://[^/]+/([^/]+/[^/]+)")) + let repo = if m != none { + m.captures.at(0) + } else { + repo-url + } + make-link(repo) + } + }, + ) + } +} + +/// Displays a link to a #link("https://github.com", "github.com", footnote:false) user page. +/// If #arg[name] is empty or #typ.v.auto, the package +/// author is linked (if a github username was provided during initialization). +/// ```example +/// - #github-user() +/// - #github-user("typst") +/// ``` +/// -> content +#let github-user( + /// Name of the user on GitHub, like `jneug` or #typ.v.auto. + /// -> str | auto + ..name, +) = { + let name = name.pos().at(0, default: auto) + if name != auto { + link("https://github.com/" + name, sym.at + name) + } else { + context { + // TODO (jneug) how to handle multiple authors? + let author = document.get-value("package.authors").first() + if "github" in author { + link("https://github.com/" + author.github, sym.at + author.github) + } else { + let repo = document.get-value("package.repository") + if repo != none { + let name = repo.split("/").first() + link("https://github.com/" + name, sym.at + name) + } else { + panic("#github-user either needs a gihub name for the author or a repository URL set during initialization.") + } + } + } + } +} + +/// Displays a #typ.link to a #link("https://github.com", "github.com", footnote:false) +/// repository. If #arg[repo] is empty or #typ.v.auto, the package +/// repository is linked (if a repository URL was provided during initialization). +/// ```example +/// - #github() +/// - #github("typst/packages") +/// - #github(path: "/issues") +/// - #github("typst/packages", path: "/issues") +/// ``` +/// -> content +#let github( + /// Name of the repository on GitHub, like `jneug/typst-mantys` or #typ.v.auto. + /// -> str | auto + ..repo, + /// Optional path to append to the URL. This + /// is appended to the repository URL as is and + /// can include anchors. + /// -> str + path: none, + /// Custom label for the link. + /// -> content | auto + label: auto, +) = { + link-repo( + repo.pos().at(0, default: auto), + path: path, + label: label, + ) +} + +/// Displays a #typ.link to a #link("https://github.com", "github.com", footnote:false) +/// repository. If #arg[repo] is empty or #typ.v.auto, the package +/// repository is linked (if a repository URL was provided). +/// ```example +/// - #github-file("README.md") +/// - #github-file("typst/packages", "README.md") +/// ``` +/// -> content +#let github-file( + /// Either a file path or a repository name and a filepath. + /// -> str + ..repo-filepath, + /// The branch to link to. + /// -> str + branch: "main", +) = { + if repo-filepath.pos() == () { + panic("#github-file requires a filepath to link to.") + } + + let filepath = repo-filepath.pos().last() + + if repo-filepath.pos().len() > 1 { + github(repo-filepath.pos().first(), path: "/tree/" + branch + "/" + filepath, label: filepath) + } else { + github(path: "/tree/" + branch + "/" + filepath, label: filepath) + } +} + +/// Displays a #typ.link to a Typst package in the #link("https://typst.app/universe", "Typst universe"). +/// If #arg[pkg] is empty, the name of the package from the @type:document +/// is used. +/// +/// - #ex(`#universe()`) +/// - #ex(`#universe("tidy")`) +/// - #ex(`#universe("tidy", version: version(0,4,0))`) +/// +/// -> content +#let universe( + /// An optional package name. + /// -> str + ..pkg, + /// An optional version. + /// -> version | str + version: none, +) = { + let make-link(pkg) = { + link( + url( + "typst.app", + "universe/package", + pkg, + version, + ), + package(pkg), + ) + } + + let pkg = pkg.pos().at(0, default: auto) + if pkg != auto { + make-link(pkg) + } else { + document.use-value( + "package.name", + pkg => { + make-link(pkg) + }, + ) + } +} + +/// Displays a #typ.link to the #github("typst/package") repository +/// in the `@preview` namespace. #arg[pkg] may include a version number +/// for the package after a colon. +/// +/// - #ex(`#preview()`) +/// - #ex(`#preview(version: version(0,4,1))`) +/// - #ex(`#preview("tidy")`) +/// - #ex(`#preview("tidy:0.3.1", namespace:"local")`) +/// +/// -> content +#let preview( + /// An optional package name. + /// -> str + ..pkg, + /// An optional version. If #typ.v.auto, #arg[pkg] is checked for a verison number. + /// -> version | str + version: auto, + namespace: "preview", +) = { + let pkg = pkg.pos().at(0, default: auto) + + if version == auto and pkg != auto { + // Find version in package name + let m = pkg.match(regex(":(\d+\.\d+\.\d+)$")) + if m != none { + version = m.captures.at(0) + pkg = pkg.slice(0, -version.len() - 1) + } else { + version = none + } + } else if version == auto { + version = none + } + + if version != none { + version = utils.ver(version) + } + + let make-link(pkg) = { + link( + url( + "github.com", + "typst/packages/tree/main/packages", + namespace, + pkg, + version, + ), + package( + sym.at + + namespace + + "/" + + pkg + + if version != none { + ":" + str(version) + } else { + "" + }, + ), + ) + } + + if pkg != auto { + make-link(pkg) + } else { + document.use-value( + "package.name", + pkg => { + make-link(pkg) + }, + ) + } +} diff --git a/packages/preview/mantys/1.0.0/src/api/tidy.typ b/packages/preview/mantys/1.0.0/src/api/tidy.typ new file mode 100644 index 0000000000..74330de064 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/tidy.typ @@ -0,0 +1,100 @@ + +#import "../_deps.typ" as deps +#import "../_api.typ" as api + +#import "../core/tidy.typ" as tidy-style +#import "../core/document.typ" + +// Internal helper to pre-configure tidy +#let _show-module = deps.tidy.show-module.with( + first-heading-level: 2, + show-module-name: false, + sort-functions: auto, + show-outline: true, +) + +#let _post-process-module(module-doc, module-args: (:), func-opts: (:), filter: func => true) = { + let functions = module-doc.functions.filter(filter) + + for (i, func) in functions.enumerate() { + for (key, value) in func-opts { + functions.at(i).insert(key, value) + } + } + + module-doc.functions = functions + return module-doc +} + + +/// Parses and displays a library file with #package("Tidy"). +/// #sourcecode[```typ +/// #tidy-module("utils", read("../src/lib/utils.typ")) +/// ```] +/// -> content +#let tidy-module( + /// Name of the module. + /// -> str + name, + /// Data of the module, usually read with #typ.read. + /// -> str + data, + /// Additional scope for evaluating the modules docstrings. + /// -> dictionary + scope: (:), + /// Optional module name for functions in this module. + /// By default, all functions will be displayed without a module prefix. This will add a module to the functions by passing + /// #arg[module] to @cmd:command. + /// + /// #frame[ + /// Without module: #cmd[some-command] + /// + /// With module: #cmd(module: "util")[another-command] + /// ] + /// + /// Note that setting this will also change function labels to include the module. + /// -> str + module: none, + /// A filter function to apply after parsing the module data. For each function in the module the parsed information is passed to #arg[filter]. It should return #typ.v.true if the function should be displayed and #typ.v.false otherwise. + /// -> function + filter: func => true, + /// Set to #typ.v.true to enable #package[Tidy]s legacy parser (pre version 0.4.0). + /// -> bool + legacy-parser: false, + /// Additional arguments to be passed to + /// #cmd(module:"tidy", "show-module"). + /// -> any + ..tidy-args, +) = { + context { + let doc = document.get() + let scope = ( + if doc.examples-scope != none { + doc.examples-scope.scope + } else { + (:) + } + + scope + ) + + let module-doc = deps.tidy.parse-module( + data, + name: name, + scope: scope, + label-prefix: if module == none { "cmd:" } else { module + ":" }, + enable-curried-functions: true, + old-syntax: legacy-parser, + ) + + module-doc = _post-process-module( + module-doc, + // TODO: (jneug) still necessary if label-prefix is module? => Filtering + func-opts: ( + module: module, + ), + filter: filter, + ) + + _show-module(module-doc, style: tidy-style, ..tidy-args.named()) + } +} diff --git a/packages/preview/mantys/1.0.0/src/api/types.typ b/packages/preview/mantys/1.0.0/src/api/types.typ new file mode 100644 index 0000000000..2b9424c715 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/types.typ @@ -0,0 +1,291 @@ + +#import "../util/is.typ" +#import "../util/utils.typ" +#import "../util/valkyrie.typ" +#import "../core/themes.typ": themable +#import "../core/index.typ" +#import "../core/styles.typ" + +#import "links.typ" +#import "values.typ" + +/// Dictionary of builtin types, mapping the types name to its actual type. +#let _type-map = ( + "auto": auto, + "none": none, + // foundations + arguments: arguments, + array: array, + boolean: bool, + bytes: bytes, + content: content, + datetime: datetime, + dictionary: dictionary, + float: float, + function: function, + integer: int, + location: location, + module: module, + plugin: plugin, + regex: regex, + selector: selector, + string: str, + type: type, + label: label, + version: version, + // layout + alignment: alignment, + angle: angle, + direction: direction, + fraction: fraction, + length: length, + ratio: ratio, + relative: relative, + // visualize + color: color, + gradient: gradient, + stroke: stroke, +) +/// Dictionary of allowed type aliases, like `dict` for `dictionary`. +#let _type-aliases = ( + bool: "boolean", + str: "string", + arr: "array", + dict: "dictionary", + int: "integer", + func: "function", +) +/// Dictionary of colors to use for builtin types. +#let _type-colors = { + let red = rgb(255, 203, 195) + let gray = rgb(239, 240, 243) + let purple = rgb(230, 218, 255) + + ( + // fallback + default: rgb(239, 240, 243), + custom: rgb("#fcfdb7"), + // special + any: gray, + "auto": red, + "none": red, + // foundations + arguments: gray, + array: gray, + boolean: rgb(255, 236, 193), + bytes: gray, + content: rgb(166, 235, 229), + datetime: gray, + dictionary: gray, + float: purple, + function: gray, + integer: purple, + location: gray, + module: gray, + plugin: gray, + regex: gray, + selector: gray, + string: rgb(209, 255, 226), + type: gray, + label: rgb(167, 234, 255), + version: gray, + // layout + alignment: gray, + angle: purple, + direction: gray, + fraction: purple, + length: purple, + "relative length": purple, + ratio: purple, + relative: purple, + // visualize + color: gradient.linear( + (rgb("#7cd5ff"), 0%), + (rgb("#a6fbca"), 33%), + (rgb("#fff37c"), 66%), + (rgb("#ffa49d"), 100%), + ), + gradient: gradient.linear( + (rgb("#7cd5ff"), 0%), + (rgb("#a6fbca"), 33%), + (rgb("#fff37c"), 66%), + (rgb("#ffa49d"), 100%), + ), + stroke: gray, + ) +} + + +/// Creates a colored box for a type, similar to those on the Typst website. +/// - #ex(`#type-box("color", red)`) +/// -> content +#let type-box( + /// Name of the type. + /// -> str + name, + /// Color for the type box. + /// -> color + color, +) = box( + fill: color, + radius: 2pt, + inset: (x: 4pt, y: 0pt), + outset: (y: 2pt), + text( + .88em, + utils.get-text-color(color), + utils.rawi(name), + ), +) + +/// Test if #arg[name] was registered as a custom type. +/// #property(requires-context: true) +/// -> bool +#let is-custom-type( + /// Name to check. + /// -> str + name, +) = query(label("mantys:custom-type-" + name)) != () + + +/// Displays a type link to the custom type #arg[name], if that +/// name is registered as a @cmd:custom-type in the manual. +/// - #ex(`#link-custom-type("document")`) +/// - #ex(`#link-custom-type("theme")`) +/// -> content +#let link-custom-type( + /// Name of the custom type. + /// -> str + name, +) = context { + let _q = query(label("mantys:custom-type-" + name)) + if _q != () { + let custom-type = _q.first() + if custom-type.value.color == auto { + return link(custom-type.location(), type-box(name, _type-colors.custom)) + } else { + return link(custom-type.location(), type-box(name, custom-type.value.color)) + } + } else { + panic("custom type " + name + " not found. use #custom-type in your manual to add the type first.") + } +} + + +/// Displays a type link to the type #arg[name]. #arg[name] can +/// either be a #link(, "builtin type") or a registered @type:custom-type. +/// +/// Builtin types are linked to the official Typst reference documentation. Custom types to their location in the manual. +/// Some builtin types can be referenced by aliases like `dict` for `dictionary`. +/// +/// If #arg[name] is given as a #typ.t.str it is taken as the name of the type. If #arg[name] is a #typ.type or any other value, the type of the value is displayed. +/// +/// - #ex(`#dtype("string")`) +/// - #ex(`#dtype("dict")`) +/// - #ex(`#dtype(1.0)`) +/// - #ex(`#dtype(true)`) +/// - #ex(`#dtype("document")`) +/// -> content +#let dtype( + /// Name of the type. + /// -> any + name, + /// If the type should be linked to the Typst documentation or the location of the custom type. + /// Set to #typ.v.false to disable linking. + /// -> bool + link: true, +) = context { + // TODO: (jneug) parse types like "array[str]" + let _type + if is.type(name) or is._auto(name) or is._none(name) { + _type = name + } else if not is.str(name) { + _type = type(name) + } else { + let name = _type-aliases.at(name, default: name) + if name in _type-map { + _type = _type-map.at(name) + } else if is-custom-type(name) { + return link-custom-type(name) + } else { + return links.link-dtype(name, type-box(name, _type-colors.default)) + } + } + + + _type = repr(_type) + return links.link-dtype(_type, type-box(_type, _type-colors.at(_type))) +} + +/// Creates a list of datatypes. Each value in +/// #sarg[types] is passed to @cmd:dtype. +/// - #ex(`#dtypes(int, str, "theme", "dict")`) +/// - #ex(`#dtypes(int, float, sep: ", ")`) +/// -> content +#let dtypes( + ..types, + link: true, + sep: box(inset: (left: 1pt, right: 1pt), sym.bar.v), +) = { + types.pos().map(dtype.with(link: link)).join(sep) +} + +/// Registers a custom type. +#let custom-type(name, color: auto) = [ + #metadata((name: name, color: color)) + #label("mantys:custom-type-" + name) + #utils.place-reference(label("type:" + name), "type", "type") + #index.idx( + name, + kind: "type", + main: true, + display: if color == auto { + type-box(name, _type-colors.custom) + } else { + type-box(name, color) + }, + ) +] + +// TODO Adding schemas as custom types +// TODO: move into sub-module (like valkyrie)? +#let _parse-dict-schema(schema, ..options, _dtype: none, _value: none) = { + `(` + terms( + hanging-indent: 1.28em, + indent: .64em, + ..for (key, el) in schema { + ( + terms.item( + styles.arg(key), + if type(el) == dictionary { + if el == (:) { + _dtype(dictionary) + } else { + _parse-dict-schema(el, ..options, _dtype: _dtype, _value: _value) + } + } else { + if _dtype != none { + _dtype(el) + } + }, + ), + ) + }, + ) + `)` +} + +// TODO: Merge with #custom-type ? +/// Registeres a schema as a @type:custom-type. +#let schema(name, definition, color: auto, ..args) = { + assert(is.dict(definition)) + + custom-type(name, color: color) + if "valkyrie-type" in definition { + valkyrie.parse-schema(definition, ..args, _dtype: dtype, _value: values.value) + } else { + _parse-dict-schema(definition, ..args, _dtype: dtype, _value: values.value) + } +} + diff --git a/packages/preview/mantys/1.0.0/src/api/values.typ b/packages/preview/mantys/1.0.0/src/api/values.typ new file mode 100644 index 0000000000..5d8dc0bffa --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/values.typ @@ -0,0 +1,84 @@ +#import "../util/utils.typ": rawc +#import "../core/themes.typ": themable + +// TODO: (jneug) shorten values if they are large dicts / modules +/// Shows #arg[value] as content. +/// - #ex(`#value("string")`) +/// - #ex(`#value([string])`) +/// - #ex(`#value(true)`) +/// - #ex(`#value(1.0)`) +/// - #ex(`#value(3em)`) +/// - #ex(`#value(50%)`) +/// - #ex(`#value(left)`) +/// - #ex(`#value((a: 1, b: 2))`) +/// +/// -> content +#let value( + /// - Value to show. + /// -> any + value, + /// If #value(true), parses strings as type names. + /// -> boolean + parse-str: false, +) = { + if parse-str and type(value) == str { + themable(theme => rawc(theme.values.default, value)) + } else { + themable(theme => rawc(theme.values.default, std.repr(value))) + } +} + +#let _v = value + + +/// Highlights the default value of a set of #cmd[choices]. +/// - #ex(`#default("default-value")`) +/// - #ex(`#default(true)`) +/// +/// -> content +#let default( + /// The value to highlight. + /// -> any + value, + /// If #value(true), parses strings as type names. + /// -> boolean + parse-str: true, +) = { + themable(theme => underline(_v(value, parse-str: parse-str), offset: 0.2em, stroke: .8pt + theme.values.default)) +} +#let _d = default + + +/// Shows a list of choices possible for an argument. +/// +/// If #arg[default] is set to something else than #value("__none__"), the value +/// is highlighted as the default choice. If #arg[default] is already present in +/// #arg[values] the value is highlighted at its current position. Otherwise +/// #arg[default] is added as the first choice in the list. +/// // - #ex(`#choices(left, right, center)`) +/// // - #ex(`#choices(left, right, center, default:center)`) +/// // - #ex(`#choices(left, right, default:center)`) +/// // - #ex(`#arg(align: choices(left, right, default:center))`) +/// -> content +#let choices( + /// The default value to highlight. + /// -> any + default: "__none__", + /// Seperator between choices. + /// -> content + sep: sym.bar.v, + /// Values to choose from. + /// -> any + ..values, +) = { + let list = values.pos().map(_v) + if default != "__none__" { + if default in values.pos() { + let pos = values.pos().position(v => v == default) + list.at(pos) = _d(default) + } else { + list.insert(0, _d(default)) + } + } + list.join(box(inset: (left: 1pt, right: 1pt), sep)) +} diff --git a/packages/preview/mantys/1.0.0/src/core/document.typ b/packages/preview/mantys/1.0.0/src/core/document.typ new file mode 100644 index 0000000000..8756b331df --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/core/document.typ @@ -0,0 +1,69 @@ +#import "../util/typing.typ" as t +#import "../util/utils.typ" as util +#import "schema.typ" + +/// Creates a document by parsing the supplied arguments agains the document schema using #universe("valkyrie"). +/// -> document +#let create( + /// Arguments accepted by @type:document. + /// -> any + ..args, +) = t.parse(args.named(), schema.document) + +/// Saves the @type:document in an internal state. +/// -> content +#let save( + /// The @type:document created by @cmd:document:create. + /// -> document + doc, +) = state("mantys:document").update(doc) + +/// Updates the @type:document in the internal state. +/// -> content +#let update( + /// An update function to be passed to #typ.state: #lambda("document", ret:"document") + /// -> function + func, +) = state("mantys:document").update(func) + +/// Updates the value at #arg[key] with the update function #arg[func]: #lambda("any", "any") +/// #arg[key] may be in dot-notation to update values in nested dictionaries. +/// #property(see: ()) +#let update-value(key, func) = update(doc => { + doc.insert(key, func(doc.at(key, default: default))) + doc +}) + +/// Retrieves the document at the current location from the internally saved state. +/// #property(requires-context: true) +#let get() = state("mantys:document").get() + +/// Retrieves the final document from the internally saved state. +/// #property(requires-context: true) +#let final() = state("mantys:document").final() + +/// Gets a value from the internally saved document. +/// #property(requires-context: true) +#let get-value(key, default: none) = util.dict-get(get(), key, default: default) + +/// Retrieves the @type:document from the internal state and passes is to #arg[func]. +/// #property(requires-context: true) +#let use( + /// A function to receive the @type:document. + /// -> function + func, +) = context func(get()) + +/// Gets a value from the internally saved document. +/// #property(requires-context: true) +#let use-value( + /// Key to retrieve. May be in dot-notation. + /// -> str + key, + /// Function to receive the value. + /// -> function + func, + /// default value to use, if #arg[key] is not found. + /// -> any + default: none, +) = context func(util.dict-get(get(), key, default: default)) diff --git a/packages/preview/mantys/1.0.0/src/core/index.typ b/packages/preview/mantys/1.0.0/src/core/index.typ new file mode 100644 index 0000000000..9c76b76774 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/core/index.typ @@ -0,0 +1,145 @@ +#import "../util/utils.typ" +#import "../_deps.typ" as deps + +/// Removes special characters from #arg[term] to make it +/// a valid format for the index. +/// -> string +#let idx-term( + /// The term to sanitize. + /// -> str | content + term, +) = { + utils.get-text(term).replace(regex("[#_()]"), "") +} + + +/// Adds #arg[term] to the index. +/// +/// Each entry can be categorized by setting #arg[kind]. +/// @cmd:make-index can be used to generate the index for one kind only. +/// -> none | content +#let idx( + /// An optional term to use, if it differs from #arg[body]. + /// -> str | content + term, + /// A category for this term. + /// -> str + kind: "term", + /// If this is the "main" entry for this #arg[term]. + /// -> bool + main: false, + /// An optional content element to show in the index instead of #arg[term], + /// -> content + display: auto, +) = [#metadata(( + term: utils.get-text(term), + kind: kind, + main: main, + display: display, + ))] + +/// Creates an index from previously set entries. +/// -> content +#let make-index( + /// An optional kind of entries to show. + /// -> str + kind: auto, + /// Function to format headings in the index: #lambda("str", ret: "content") + /// -> function + heading-format: text => heading(depth: 2, numbering: none, outlined: false, bookmarked: false, text), + /// Function to format index entries. Receives the @type:index-entry and an array of page numbers. + /// + /// #lambda("content", "array", ret: "content") + /// -> content + entry-format: (term, pages) => [#term #box(width: 1fr, repeat[.]) #pages.join(", ")\ ], + /// Sorting function to sort index entries. + /// + /// #lambda("str", ret:"str") + /// -> function + sort-key: it => it.term, + /// Grouping function to group index entries by. Usually entries are grouped by the first letter of #arg[term], but this can be changed to group by other keys. See below for an example. + /// + /// #lambda("str", ret:"str") + /// -> function + grouping: it => upper(it.term.at(0)), +) = context { + let entries = query() + + if kind != auto { + let kinds = (kind,).flatten() + entries = entries.filter(entry => entry.value.kind in kinds) + } + + let register = (:) + for entry in entries { + if entry.value.term not in register { + register.insert( + entry.value.term, + ( + term: entry.value.term, + kind: entry.value.kind, + display: entry.value.display, + main: if entry.value.main { + entry.location() + } else { + none + }, + locations: (entry.location(),), + ), + ) + } else { + let idx = register.at(entry.value.term) + if idx.main == none and entry.value.main { + idx.main = entry.location() + } + idx.locations.push(entry.location()) + if idx.display == auto and entry.value.display != auto { + idx.display = entry.value.display + } + register.at(entry.value.term) = idx + } + } + + let get-page(loc) = loc.page() + let link-page(loc) = link(loc, str(loc.page())) + let link-term(term, loc) = { + if loc != none { + link(loc, term) + } else { + [#term] + } + } + + for (term, entry) in register { + entry.locations = entry.locations.dedup(key: get-page).sorted(key: get-page) + register.at(term) = entry + } + + let index = (:) + for (term, entry) in register { + let key = grouping(entry) + if key not in index { + index.insert(key, ()) + } + index.at(key).push(entry) + } + + for group in index.keys().sorted() { + let entries = index.at(group) + + heading-format(group) + for entry in entries.sorted(key: sort-key) { + entry-format( + link-term( + if entry.display == auto { + entry.term + } else { + entry.display + }, + entry.main, + ), + entry.locations.map(link-page), + ) + } + } +} diff --git a/packages/preview/mantys/1.0.0/src/core/layout.typ b/packages/preview/mantys/1.0.0/src/core/layout.typ new file mode 100644 index 0000000000..02598d2d93 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/core/layout.typ @@ -0,0 +1,164 @@ +#import "../_deps.typ": typearea, hydra, codly, marginalia + +#import "styles.typ" +#import "index.typ": idx + +#import "../util/utils.typ" +#import "../api/elements.typ": package +#import "../api/examples.typ": codesnippet, example, side-by-side +#import "../api/types.typ": link-custom-type, type-box, _type-map + +#let page-header(doc, theme) = { + context { + set text(..theme.header) + hydra.hydra(1) + h(1fr) + emph(hydra.hydra(2)) + } +} + +#let page-footer(doc, theme) = context { + let (n, ..) = counter(page).get() + if n > 1 { + set text(..theme.footer) + show link: set text(..theme.footer) + grid( + columns: (5fr, 1fr), + align: (left + bottom, right + bottom), + { + [compiled: #datetime.today().display()] + if doc.git != none { + if ( + doc.package.repository != none + ) [, git #link(doc.package.repository + "/tree/" + doc.git.hash, doc.git.hash.slice(0,8))] else [, git #doc.git.hash.slice(0,8)] + } + }, + [#n], + ) + } +} + +#let page-init(doc, theme) = ( + body => { + // Configure main page + // TODO: let user set paper? + show: typearea.typearea.with( + paper: "a4", + div: 12, + two-sided: false, + header: page-header(doc, theme), + footer: page-footer(doc, theme), + binding-correction: 1cm, + ) + // drafting.set-page-properties( + // margin-left: 3.2cm, + // side: left, + // stroke: none, + // ) + marginalia.configure( + inner: (far: 10mm, width: 20mm, sep: 5mm), + book: false, + ) + + + // Setup look & feel + set par(justify: true) + + set text(..theme.text) + set heading(numbering: "I.1.1.a") + // show heading: it => { + // let level = it.at("level", default: it.at("depth", default: 2)) + // let scale = (1.6, 1.4, 1.2).at(level - 1, default: 1.0) + + // if it.at("level", default: it.at("depth", default: 2)) == 1 { + // pagebreak(weak: true) + // } + // set text(1em * scale, ..theme.heading) + // it + // } + // TODO: Style headings (crashes ?) + // show heading: it => { + // let level = it.at("level", default: it.at("depth", default: 2)) + // let scale = (1.6, 1.4, 1.2).at(level - 1, default: 1.0) + // set text(1em * scale, ..theme.heading) + // it + // } + + show link: set text(theme.emph.link) + // Show (some) urls in footnotes + show : it => { + let (dest, body) = (it.dest, it.body) + + let show-urls = doc.show-urls-in-footnotes + if type(dest) == str and dest.starts-with("*") { + show-urls = not show-urls + dest = dest.slice(1) + } else if type(dest) != str { + show-urls = false + } + std.link(dest, body) + if show-urls { + footnote(std.link(dest, dest)) + } + } + + // TODO: let the user do this? + show: codly.codly-init + codly.codly( + display-name: false, + display-icon: false, + // inset: 2pt, + fill: none, + zebra-fill: none, + stroke: none, + ) + show raw.where(block: true): it => { + set par(justify: false) + if doc.wrap-snippets { + codesnippet(it) + } else { + it + } + } + show raw.where(lang: "example"): example + show raw.where(lang: "side-by-side"): side-by-side + + // Allow theme overrides + // TODO: Should theme call be in mantys.typ? + let page-init-func = theme.page-init + show: page-init-func(doc, theme) + + // Setup show-rule for theming support + show : it => { + let element = it.value + (element.func)(theme, ..element.args) + } + + show ref: it => { + set text(theme.secondary) + if it.element != none and it.element.func() == figure { + if it.element.kind in ("cmd", "arg") { + let command = utils.parse-label(it.element.label) + if (doc.index-references and it.supplement != [-]) or (not doc.index-references and it.supplement == [+]) { + idx( + command.name, + kind: it.element.kind, + main: true, + display: styles.cmd(command.name, module: command.module), + ) + } + link( + it.target, + styles.cmd(command.name, module: command.module, arg: command.arg), + ) + } else if it.element.kind == "type" { + link-custom-type(str(it.target).slice(5)) + } + } else { + it + } + } + + show upper(doc.package.name): it => package(doc.package.name) + + body + } +) diff --git a/packages/preview/mantys/1.0.0/src/core/schema.typ b/packages/preview/mantys/1.0.0/src/core/schema.typ new file mode 100644 index 0000000000..ef814c449e --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/core/schema.typ @@ -0,0 +1,244 @@ +#import "../util/typing.typ" as t +#import "../util/is.typ" + + +#let author = t.dictionary( + ( + name: t.string(), + email: t.email(optional: true), + github: t.string(optional: true), + urls: t.array(t.url(), optional: true, pre-transform: t.coerce.array), + affiliation: t.string(optional: true), + ), + pre-transform: (self, it) => { + if is.str(it) { + let name-match = it.match(regex("^[^<]+")) + let info = ( + name: if name-match != none { name-match.text.trim() } else { it }, + ) + + let info-matches = it.matches(regex("<([^>]+?)>")) + for m in info-matches { + let _info = m.captures.last() + if _info.starts-with("@") { + info.insert("github", _info.trim("@")) + } else if _info.starts-with("http") { + info.insert("urls", (_info,)) + } else if "@" in _info { + info.insert("email", _info) + } + } + return info + } else { + return it + } + }, + aliases: ( + url: "urls", + ), +) + +#let package = t.dictionary(( + name: t.string(), + version: t.version(), + entrypoint: t.string(), + authors: t.array(author, pre-transform: t.coerce.array), + license: t.string(), + description: t.string(), + homepage: t.url(optional: true), + repository: t.url(optional: true), + keywords: t.array(t.string(), optional: true), + categories: t.array( + t.choice(( + "components", + "visualization", + "model", + "layout", + "text", + "scripting", + "languages", + "integration", + "utility", + "fun", + "book", + "paper", + "thesis", + "flyer", + "poster", + "presentation", + "office", + "cv", + )), + assertions: (t.assert.length.max(3),), + optional: true, + ), + disciplines: t.array( + t.choice(( + "agriculture", + "anthropology", + "archaeology", + "architecture", + "biology", + "business", + "chemistry", + "communication", + "computer-science", + "design", + "drawing", + "economics", + "education", + "engineering", + "fashion", + "film", + "geography", + "geology", + "history", + "journalism", + "law", + "linguistics", + "literature", + "mathematics", + "medicine", + "music", + "painting", + "philosophy", + "photography", + "physics", + "politics", + "psychology", + "sociology", + "theater", + "theology", + "transportation", + )), + optional: true, + ), + compiler: t.version(optional: true), + exclude: t.array(t.string(), optional: true), +)) + +#let template = t.dictionary(( + path: t.string(), + entrypoint: t.string(), + thumbnail: t.string(optional: true), +)) + +#let asset = t.dictionary(( + id: t.string(), + src: t.string(), + dest: t.string(), +)) + +#let document = t.dictionary( + // typstyle off + ( + // Document info + title: t.content(), + subtitle: t.content(optional: true), + urls: t.array(t.url(), optional: true, pre-transform: t.coerce.array), + date: t.date(pre-transform: t.optional-coerce(t.coerce.date), optional: true), + abstract: t.content(optional: true), + // General package-info + // Will be pre-transform to the package dict + // TODO: Should this be allowed to override package? + // name: t.string(), + // description: t.content(), + // authors: t.array(author), + // repository: t.url(optional: true), + // version: t.version(), + // license: t.string(), + // Data loaded from typst.toml + package: package, + template: t.optional(template), + // Options available to the theme + theme-options: t.dictionary( + (:), + default: (:), + ), + // Configuration options + show-index: t.boolean(default: true), + show-outline: t.boolean(default: true), + show-urls-in-footnotes: t.boolean(default: true), + index-references: t.boolean(default: true), + wrap-snippets: t.boolean(default: false), + examples-scope: t.dictionary( + ( + scope: t.dictionary((:)), + imports: t.dictionary((:), optional: true), + ), + optional: true, + pre-transform: t.coerce.dictionary(it => (scope: it)), + post-transform: (_, it) => { + if it == none { + it = (:) + } + return (scope: (:), imports: (:)) + it + }, + ), + assets: t.array( + asset, + default: (), + pre-transform: (_, it) => { + if type(it) == dictionary { + let assets = () + for (id, spec) in it { + assets.push(( + id: id, + src: if type(spec) == str { spec } else { spec.src }, + dest: if type(spec) == str { id } else { spec.at("dest", default: id) }, + )) + } + return assets + } else { + return it + } + }, + ), + // Git info + git: t.dictionary( + ( + branch: t.string(default: "main"), + hash: t.string(), + ), + optional: true, + pre-transform: t.coerce.dictionary(it => if it != none { (hash: it) } else { none }), + ), + ), + + pre-transform: (self, it) => { + // If package info is not loaded from typst.toml, + // a faux package entry is created. + if "package" not in it { + it.insert("package", (entrypoint: "")) + } + // User supplied keys are moved to the "package" + // dictionary and may overwrite data from typst.toml. + for key in ("name", "description", "repository", "version", "license") { + if key in it { + it.package.insert(key, it.remove(key)) + } + } + + // Mantys allows for authors to have more fields than package authors. + if "authors" in it { + it.package.insert("authors", it.remove("authors")) + } + if "author" in it { + it.package.insert("authors", it.remove("author")) + } + + // Set empty title + if "title" not in it and "name" in it.package { + if "template" in it { + it.insert("title", [The #sym.quote#it.package.name#sym.quote template]) + } else { + it.insert("title", [The #sym.quote#it.package.name#sym.quote package]) + } + } + it + }, + aliases: ( + url: "urls", + author: "authors", + ), +) diff --git a/packages/preview/mantys/1.0.0/src/core/styles.typ b/packages/preview/mantys/1.0.0/src/core/styles.typ new file mode 100644 index 0000000000..d8c1d24b7e --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/core/styles.typ @@ -0,0 +1,61 @@ +#import "themes.typ": themable +#import "../util/utils.typ": rawi, rawc, dict-get + + +#let meta(name, l: none, r: none, kind: "arg") = themable( + theme => { + if l != none { + rawc(theme.commands.symbol, l) + } + rawc(theme.commands.argument, name) + if r != none { + rawc(theme.commands.symbol, r) + } + }, + kind: kind, +) + + +// TODO: Use styles consistently +#let arg = meta.with(l: sym.angle.l, r: sym.angle.r, kind: "arg") + + +#let sarg = meta.with(l: ".." + sym.angle.l, r: sym.angle.r, kind: "sarg") + + +#let barg = meta.with(l: sym.bracket.l, r: sym.bracket.r, kind: "barg") + + +#let carg = meta.with(l: sym.brace.l, r: sym.brace.r, kind: "carg") + + +#let cmd(name, module: none, arg: none, color: "command") = themable( + theme => { + rawc(theme.commands.symbol, sym.hash) + if module != none { + rawc(theme.emph.module, module) + rawc(theme.commands.symbol, ".") + } + rawc(theme.commands.at(color, default: theme.commands.command), name) + if arg != none { + rawc(theme.commands.symbol, sym.dot.basic) + rawc(theme.commands.argument, arg) + } + }, + kind: "cmd", +) + + +#let lambda(args, ret) = themable( + theme => { + box({ + rawc(theme.commands.symbol, sym.paren.l) + args.join(",") + rawc(theme.commands.symbol, sym.paren.r + sym.arrow.r) + ret + }) + }, + kind: "lambda", +) + +#let pill(color, body) = themable(theme => (theme.tag)(dict-get(theme, color, default: theme.muted.bg), body)) diff --git a/packages/preview/mantys/1.0.0/src/core/themes.typ b/packages/preview/mantys/1.0.0/src/core/themes.typ new file mode 100644 index 0000000000..5d73c3c5d6 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/core/themes.typ @@ -0,0 +1,41 @@ +#import "../util/utils.typ": dict-merge + +#import "../themes/default.typ": default +#import "../themes/modern.typ" +#import "../themes/cnltx.typ" +#import "../themes/orly.typ" + + +#let themable-init(theme) = body => { + show : it => { + let element = it.value + (element.func)(theme, ..element.args) + } +} + +#let themable(func, kind: "themable", ..args) = [#metadata((func: func, args: args.pos(), kind: kind))] + + +#let create-theme(..theme-args, base-theme: default) = { + if type(base-theme) == module { + base-theme = dictionary(base-theme) + } + return dict-merge(base-theme, theme-args.named()) +} + + +#let color-theme(primary, secondary, ..theme-args, base-theme: default) = { + return create-theme( + primary: primary, + secondary: secondary, + heading: ( + fill: primary, + ), + emph: ( + link: secondary, + package: primary, + ), + ..theme-args.named(), + base-theme: base-theme, + ) +} diff --git a/packages/preview/mantys/1.0.0/src/core/tidy.typ b/packages/preview/mantys/1.0.0/src/core/tidy.typ new file mode 100644 index 0000000000..7021d2e9ff --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/core/tidy.typ @@ -0,0 +1,264 @@ +#import "../_deps.typ" as deps +#import "../_api.typ" as api +#import "styles.typ" + +#import "../util/utils.typ" + +#import deps.tidy.utilities: * + +// TODO: handle Tidy settings like label-prefix, locale ... + +// Internal version of #eval_docstring. +// Adds Mantys API as preamble. +#let _eval-docstring(docstring, style-args) = deps.tidy.utilities.eval-docstring( + utils.add-preamble(docstring, ("mantys": "*")), + style-args, +) + +/// Shows the outline of a module (list pf functions). +/// +/// - module-doc (dictionary): Parsed module data. +/// - style-args (dictionary): Styling arguments. +/// -> content +#let show-outline(module-doc, style-args: (:)) = { + let prefix = module-doc.label-prefix + let items = () + for fn in module-doc.functions.sorted(key: fn => fn.name) { + items.push(api.cmdref(fn.name, module: fn.at("module", default: none))) + } + + if items != () { + let cols_num = 3 + let per_col = calc.ceil(items.len() / cols_num) + let cols = () + for i in range(items.len(), step: per_col) { + cols.push( + items + .slice( + i, + calc.min(i + per_col, items.len()), + ) + .join(linebreak()), + ) + } + api.frame({ + set text(.88em) + grid( + columns: (1fr,) * cols_num, + ..cols + ) + }) + } +} + + +/// Uses @cmd:dtype to create a colorful type-box. +#let show-type(type, style-args: (:)) = api.dtype(type) + + + +#let show-parameter-list(fn, style-args) = { + block( + fill: rgb("#d8dbed"), + width: 100%, + inset: (x: 0.5em, y: 0.7em), + { + set text(font: "Cascadia Mono", size: 0.85em, weight: 340) + text(fn.name, fill: blue) + "(" + 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("_") { + continue + } + let types + if "types" in info { + types = ": " + info.types.map(x => show-type(x)).join(" ") + } + items.push(box(arg-name + types)) + } + items.join(if inline-args { + ", " + } else { + ",\n " + }) + ( + if not inline-args { + "\n" + } + + ")" + ) + if fn.return-types != none { + box[~-> #fn.return-types.map(x => show-type(x)).join(" ")] + } + }, + ) +} + + +#let show-parameter-block( + name, + types, + content, + style-args, + show-default: false, + command: none, + default: none, +) = api.argument( + if name.starts-with("..") { + name.slice(2) + } else { + name + }, + is-sink: name.starts-with(".."), + types: types, + default: if show-default { + default + } else { + "__none__" + }, + command: command, + _value: api.value.with(parse-str: true), + content, +) + + +#let show-function( + fn, + style-args, +) = { + // add mantys api to scope + style-args.scope.insert("mantys", api) + style-args.scope.insert("property", api.property) + + // evaluate docstring + let descr = _eval-docstring(fn.description, style-args) + + // remove private parameters + if style-args.omit-private-parameters { + for (name, arg) in fn.args { + if name.starts-with("_") { + _ = fn.args.remove(name) + } + } + } + + let args = if "args" in fn { + fn.args + } else if "parent" in fn { + fn.parent.named + } else { + (:) + } + + [ + #api.command( + fn.name, + ..fn + .args + .pairs() + .map(((a, info)) => { + if a.starts-with("..") { + api.sarg(a.slice(2)) + } else if "types" in info and info.types == ("content",) and "default" not in info { + api.barg(a) + } else { + if "default" in info { + api.arg(a, info.default, _value: api.value.with(parse-str: true)) + } else { + api.arg(a) + } + } + }), + ret: fn.return-types, + module: fn.at("module", default: none), + label: style-args.enable-cross-references, + { + descr + + for (name, info) in fn.args { + let description = info.at("description", default: "") + if description in ("", []) and style-args.omit-empty-param-descriptions { + continue + } + (style-args.style.show-parameter-block)( + name, + info.at("types", default: ()), + _eval-docstring(description, style-args), + style-args, + show-default: "default" in info, + default: info.at("default", default: none), + command: (name: fn.name, module: fn.module), + ) + } + }, + ) + ] +} + + +#let show-variable( + var, + style-args, +) = api.variable( + var.name, + types: var.at("type", default: none), + value: none, //var.value, + _eval-docstring(var.description, style-args), +) + + +// TODO: (jneug) catch "()" suffix as commands (?) +// TODO: (jneug) use label-prefix as module +#let show-reference(label, name, style-args: none) = { + let _l = label + label = if style-args.label-prefix != none { + str(label).trim(style-args.label-prefix) + } else { + str(label) + } + + let parsed-label = utils.parse-label(label) + if parsed-label.prefix in ("cmd", "arg") { + std.link( + utils.create-label(parsed-label.name, arg: parsed-label.arg, module: parsed-label.module), + { + styles.cmd(parsed-label.name, module: parsed-label.module, arg: parsed-label.arg) + }, + ) + } else if parsed-label.prefix == "type" { + return api.link-custom-type(parsed-label.name) + } else { + // Workaround to allow external references from Tidy doc comments + // context { + // let q = query(std.label(label)) + // if q != () { + // q = q.first() + // std.link( + // q.location(), + // [#q.supplement #std.numbering(q.numbering, ..counter(q.func()).at(q.location()))], + // ) + // } else { + // panic("label `" + repr(_l) + "` does not exists in the document") + // } + + ref(std.label(label)) + } +} + +#let show-example( + code, + inherited-scope: (:), + preamble: "", + mode: "markup", +) = { + api.example( + code, + scope: inherited-scope, + mode: mode, + ) +} diff --git a/packages/preview/mantys/1.0.0/src/mantys.typ b/packages/preview/mantys/1.0.0/src/mantys.typ new file mode 100644 index 0000000000..289f6a6151 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/mantys.typ @@ -0,0 +1,107 @@ + +#import "core/document.typ" +#import "core/themes.typ" as themes: color-theme, create-theme +#import "core/index.typ": * +#import "core/layout.typ" +#import "api/tidy.typ": * + +#import "_api.typ": * + +#import "_deps.typ": codly, showybox + +#let mantys-init( + theme: themes.default, + ..args, +) = { + let doc = document.create(..args) + + let mantys-func = body => { + document.save(doc) + + // Add assets metadata to document + for asset in doc.assets [#metadata(asset)] + + // Asset mode skips rendering the body + if sys.inputs.at("mode", default: "full") == "assets" { + return + } + + // coerce theme to dictionary + let theme = if type(theme) == module { + dictionary(theme) + } else { + theme + } + + // theme.page-init(doc) + // set page(paper: "a4") + show: layout.page-init(doc, theme) + + let title-page-func = theme.title-page + title-page-func(doc, theme) + + if sys.inputs.at("debug", default: "false") in ("true", "1") { + page(flipped: true, columns: 2)[ + #set text(10pt) + #doc + ] + } + pagebreak(weak: true) + + body + + // Show index if enabled and at least one entry + if doc.show-index { + pagebreak(weak: true) + [= Index] + columns(3, make-index()) + } + + let last-page-func = theme.last-page + last-page-func(doc, theme) + } + + return (doc, mantys-func) +} + +/// Main MANTYS template function. +/// Use it to initialize the template: +/// ```typ +/// #show: mantys( +/// // initialization +/// ) +/// ``` +#let mantys(..args) = { + let (_, mantys) = mantys-init(..args) + return mantys +} + +/// Reads some information about the current commit from the +/// local `.git` directory. The result can be passed to #cmd[mantys] with the #arg[git] key. +/// +/// Since MANTYS can't read files from outside its package directory, +/// #cmd-[git-info] needs the #builtin[read] function to be +/// passed in. +/// - read (function): The builtin #builtin[read] function. +/// - git-root (string): relative path to the `.git` directory. +/// -> dictionary +#let git-info(reader, git-root: "../.git") = { + if git-root.at(-1) != "/" { + git-root += "/" + } + let head-data = reader(git-root + "HEAD") + let m = head-data.match(regex("^ref: (\S+)")) + if m == none { + return none + } + + let ref = m.captures.at(0) + + let branch = ref.split("/").last() + let hash = reader(git-root + ref).trim() + + return ( + branch: branch, + hash: hash, + ) +} diff --git a/packages/preview/mantys/1.0.0/src/themes/cnltx.typ b/packages/preview/mantys/1.0.0/src/themes/cnltx.typ new file mode 100644 index 0000000000..6f9fba0d8e --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/themes/cnltx.typ @@ -0,0 +1,250 @@ +#let primary = rgb(166, 9, 18) +#let secondary = primary + + +#let fonts = ( + serif: ("New Computer Modern", "Computer Modern", "Libertinus Serif", "Liberation Serif"), + sans: ("New Computer Modern Sans", "Computer Modern Sans", "Libertinus Sans", "Helvetica Neue", "Helvetica"), + mono: ("Liberation Mono",), +) + +#let muted = ( + fill: rgb(128, 128, 128), + bg: luma(233), +) + +#let text = ( + size: 12pt, + font: fonts.serif, + fill: rgb(0, 0, 0), +) + +#let header = ( + size: 10pt, + fill: text.fill, +) +#let footer = ( + size: 9pt, + fill: muted.fill, +) + +#let heading = ( + font: fonts.serif, + fill: text.fill, +) + +#let emph = ( + link: primary, + package: primary, + module: rgb(5, 10, 122), + since: rgb("#a6fbca"), + until: rgb("#ffa49d"), + changed: rgb("#fff37c"), + deprecated: rgb("#ffa49d"), + compiler: teal, + "context": rgb("#fff37c"), +) + +#let code = ( + size: 9pt, + font: fonts.mono, + fill: rgb("#999999"), +) + +#let alert-colors = ( + info: rgb(23, 162, 184), + warning: rgb(255, 193, 7), + error: rgb(220, 53, 69), + success: rgb(40, 167, 69), +) + +#let alert(alert-type, body) = { + let color = if type(alert-type) == color { + alert-type + } else { + alert-colors.at(alert-type, default: luma(100)) + } + block( + stroke: (left: 2pt + color, rest: 0pt), + fill: color.lighten(88%), + inset: 8pt, + width: 100%, + spacing: 2%, + std.text(size: .88em, fill: color.darken(60%), body), + ) +} + + +#let tag(color, body) = box( + stroke: none, + fill: color, + // radius: 50%, + radius: 3pt, + inset: (x: .5em, y: .25em), + baseline: 1%, + std.text(body), +) + +#let commands = ( + argument: navy, + command: rgb(153, 64, 39), + variable: rgb(214, 181, 93), + builtin: eastern, + comment: rgb(128, 128, 128), + symbol: text.fill, +) + +#let values = ( + default: rgb(181, 2, 86), +) + +#let page-init(doc, theme) = ( + body => { + show std.heading: it => { + let level = it.at("level", default: it.at("depth", default: 2)) + let scale = (1.6, 1.4, 1.2).at(level - 1, default: 1.0) + + let size = 1em * scale + let above = ( + if level == 1 { + 1.8em + } else { + 1.44em + } + / scale + ) + let below = 0.75em / scale + + set std.text(size: size, ..heading) + set block(above: above, below: below) + + if level == 1 { + pagebreak(weak: true) + block({ + if it.numbering != none { + std.text( + fill: theme.primary, + { + [Part ] + counter(std.heading).display() + }, + ) + linebreak() + } + it.body + }) + } else { + block({ + if it.numbering != none { + std.text(fill: theme.primary, counter(std.heading).display()) + [ ] + } + set std.text(size: size, ..heading) + it.body + }) + } + } + body + } +) + +#let _display-author(author) = block({ + smallcaps(author.name) + set std.text(.88em) + if author.email != none { + linebreak() + " " + sym.lt + link("mailto://" + author.email, author.email) + sym.gt + } + if author.affiliation != () { + linebreak() + smallcaps(author.affiliation) + } + if author.urls != () { + linebreak() + author.urls.map(link).join(linebreak()) + } + if author.github != none { + linebreak() + [GitHub: ] + link("https://github.com/" + author.github, author.github) + } +}) + +#let title-page(doc, theme) = { + set align(center) + set block(spacing: 2em) + + block( + smallcaps( + std.text( + 40pt, + theme.primary, + if doc.title == none { + doc.package.name + } else { + doc.title + }, + ), + ), + ) + if doc.subtitle != none { + block(above: 1em, std.text(18pt, doc.subtitle)) + } + + std.text(14pt)[v#doc.package.version] + if doc.date != none { + h(4em) + std.text(14pt, doc.date.display()) + } + h(4em) + std.text(14pt, doc.package.license) + + if doc.package.description != none { + block(doc.package.description) + } + + grid( + columns: calc.min(4, doc.package.authors.len()), + gutter: .64em, + ..doc.package.authors.map(_display-author) + ) + + if doc.urls != none { + block(doc.urls.map(link).join(linebreak())) + } + + if doc.abstract != none { + pad( + x: 10%, + { + set align(left) + // set par(justify: true) + // show par: set block(spacing: 1.3em) + doc.abstract + }, + ) + } + + if doc.show-outline { + set align(left) + set block(spacing: 0.65em) + show outline.entry.where(level: 1): it => { + v(0.85em, weak: true) + strong(link(it.element.location(), it.body)) + } + + std.heading(level: 2, outlined: false, bookmarked: false, numbering: none, "Table of Contents") + columns( + 2, + outline( + title: none, + indent: 1em, + depth: 3, + fill: repeat("."), + ), + ) + } + + pagebreak() +} + +#let last-page(doc, theme) = { } diff --git a/packages/preview/mantys/1.0.0/src/themes/default.typ b/packages/preview/mantys/1.0.0/src/themes/default.typ new file mode 100644 index 0000000000..b8d3e64ebf --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/themes/default.typ @@ -0,0 +1,252 @@ +#import "../api/icons.typ": icon + +// TODO: (jneug) Maybe move to a "theme-utils" module? +#let _display-author(author) = block({ + smallcaps(author.name) + set std.text(.88em) + if author.email != none { + linebreak() + icon("mail") + h(.25em) + link("mailto://" + author.email, author.email) + } + if author.affiliation not in (none, ()) { + linebreak() + smallcaps(author.affiliation) + } + if author.urls not in (none, ()) { + linebreak() + author.urls.map(l => icon("link") + h(.25em) + link(l)).join(linebreak()) + } + if author.github != none { + linebreak() + icon("mark-github") + h(.25em) + link("https://github.com/" + author.github, "@" + author.github) + } +}) + +#let default = { + let base = ( + primary: eastern, + secondary: teal, + muted: luma(120), + fonts: ( + serif: ("TeX Gyre Schola", "Liberation Serif"), + sans: ("TeX Gyre Heros", "Liberation Sans", "Helvetica Neue", "Helvetica"), + mono: ("TeX Gyre Cursor", "Liberation Mono"), + ), + text: rgb(35, 31, 32), + ) + + ( + primary: base.primary, + secondary: base.secondary, + fonts: base.fonts, + muted: ( + fill: base.muted, + bg: base.muted.lighten(88%), + ), + text: ( + size: 11pt, + font: base.fonts.serif, + fill: base.text, + ), + heading: ( + font: base.fonts.sans, + fill: base.text, + ), + header: ( + size: 10pt, + fill: base.text, + ), + footer: ( + size: 9pt, + fill: base.muted, + ), + code: ( + size: 9pt, + font: base.fonts.mono, + fill: rgb("#999999"), + ), + alert: (alert-type, body) => { + let alert-colors = ( + info: rgb(23, 162, 184), + warning: rgb(255, 193, 7), + error: rgb(220, 53, 69), + success: rgb(40, 167, 69), + ) + + let color = if type(alert-type) == color { + alert-type + } else { + alert-colors.at(alert-type, default: luma(100)) + } + block( + stroke: (left: 2pt + color, rest: 0pt), + fill: color.lighten(88%), + inset: 8pt, + width: 100%, + spacing: 2%, + std.text(size: .88em, fill: color.darken(60%), body), + ) + }, + tag: (color, body) => box( + stroke: none, + fill: color, + // radius: 50%, + radius: 3pt, + inset: (x: .5em, y: .25em), + baseline: 1%, + text(body), + ), + emph: ( + link: base.secondary, + package: base.primary, + module: rgb("#8c3fb2"), + since: rgb("#a6fbca"), + until: rgb("#ffa49d"), + changed: rgb("#fff37c"), + deprecated: rgb("#ffa49d"), + compiler: base.secondary, + "context": rgb("#fff37c"), + ), + commands: ( + argument: navy, + command: blue, + variable: rgb("#9346ff"), + builtin: eastern, + comment: gray, + symbol: base.text, + ), + values: ( + default: rgb(181, 2, 86), + ), + page-init: (doc, theme) => ( + body => { + show std.heading: it => { + let level = it.at("level", default: it.at("depth", default: 2)) + let scale = (1.6, 1.4, 1.2, 1.1).at(level - 1, default: 1.0) + + let size = 1em * scale + let above = ( + if level == 1 { + 1.8em + } else { + 1.44em + } + / scale + ) + let below = 0.75em / scale + + set std.text(size: size, ..theme.heading) + set block(above: above, below: below) + + if level == 1 { + pagebreak(weak: true) + block({ + if it.numbering != none { + std.text( + fill: theme.primary, + { + [Part ] + counter(std.heading).display() + }, + ) + linebreak() + } + it.body + }) + } else if level < 4 { + block({ + if it.numbering != none { + std.text(fill: theme.primary, counter(std.heading).display()) + [ ] + } + it.body + }) + } else { + block(it.body) + } + } + body + } + ), + title-page: (doc, theme) => { + set align(center) + set block(spacing: 2em) + + block( + std.text( + 40pt, + theme.primary, + if doc.title == none { + doc.package.name + } else { + doc.title + }, + ), + ) + if doc.subtitle != none { + block(above: 1em, std.text(18pt, doc.subtitle)) + } + + std.text(14pt)[v#doc.package.version] + if doc.date != none { + h(4em) + std.text(14pt, doc.date.display()) + } + h(4em) + std.text(14pt, doc.package.license) + + if doc.package.description != none { + block(doc.package.description) + } + + grid( + columns: calc.min(4, calc.max(doc.package.authors.len(), 1)), + gutter: .64em, + ..doc.package.authors.map(_display-author) + ) + + if doc.urls != none { + block(doc.urls.map(link).join(linebreak())) + } + + if doc.abstract != none { + pad( + x: 10%, + { + set align(left) + // set par(justify: true) + // show par: set block(spacing: 1.3em) + doc.abstract + }, + ) + } + + if doc.show-outline { + set align(left) + set block(spacing: 0.65em) + show outline.entry.where(level: 1): it => { + v(0.85em, weak: true) + strong(link(it.element.location(), it.body)) + } + + std.text(font: base.fonts.sans, weight: "bold", size: 1.4em, "Table of Contents") + columns( + 2, + outline( + title: none, + indent: 1em, + depth: 3, + fill: repeat("."), + ), + ) + } + + pagebreak() + }, + last-page: (doc, theme) => { }, + ) +} diff --git a/packages/preview/mantys/1.0.0/src/themes/modern.typ b/packages/preview/mantys/1.0.0/src/themes/modern.typ new file mode 100644 index 0000000000..82fddbdb9f --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/themes/modern.typ @@ -0,0 +1,166 @@ +#let primary = rgb("#ed592f") +#let secondary = rgb("#05b5da") + +#let fonts = ( + serif: ("Linux Libertine", "Liberation Serif"), + sans: ("Source Sans Pro", "Roboto"), + mono: ("Fira Code", "Liberation Mono"), +) + +#let muted = ( + fill: luma(80%), + bg: luma(95%), +) + +#let text = ( + size: 12pt, + font: fonts.sans, + fill: rgb("#333333"), +) + +#let header = ( + size: 10pt, + fill: text.fill, +) +#let footer = ( + size: 9pt, + fill: muted.fill, +) + +#let heading = ( + size: 15pt, + font: fonts.sans, + fill: primary, +) + +#import "@preview/gentle-clues:1.0.0" + +#let _alert-funcs = ( + "info": gentle-clues.info, + "warning": gentle-clues.warning, + "error": gentle-clues.error, + "success": gentle-clues.success, + "default": gentle-clues.memo, +) + +#let alert(alert-type, body) = { + let alert-func = _alert-funcs.at(alert-type, default: _alert-funcs.default) + alert-func(title: none, body) +} + + +#let tag(color, body) = box( + stroke: none, + fill: color, + // radius: 50%, + radius: 3pt, + inset: (x: .5em, y: .25em), + baseline: 1%, + std.text(body), +) + +#let code = ( + size: 12pt, + font: fonts.mono, + fill: rgb("#999999"), +) + +#let emph = ( + link: secondary, + package: primary, + module: rgb("#8c3fb2"), + since: rgb("#a6fbca"), + until: rgb("#ffa49d"), + changed: rgb("#fff37c"), + deprecated: rgb("#ffa49d"), + compiler: teal, + "context": rgb("#fff37c"), +) + +#let commands = ( + argument: rgb("#3c5c99"), + option: rgb(214, 182, 93), + command: blue, // rgb(75, 105, 197), + builtin: eastern, + comment: gray, // rgb(128, 128, 128), + symbol: text.fill, +) + +#let values = ( + default: rgb(181, 2, 86), +) + +#let page-init(doc, theme) = ( + body => { + show std.heading.where(level: 1): it => { + pagebreak(weak: true) + set std.text(fill: theme.primary) + block( + width: 100%, + breakable: false, + inset: (bottom: .33em), + stroke: (bottom: .6pt + theme.secondary), + [#if it.numbering != none { + ( + std.text( + weight: "semibold", + theme.secondary, + [Part ] + counter(std.heading.where(level: it.level)).display(it.numbering), + ) + + h(1.28em) + ) + } #it.body], + ) + } + body + } +) + +#let title-page(doc, theme) = { + set align(center) + v(2fr) + + block( + width: 100%, + inset: (y: 1.28em), + stroke: (bottom: 2pt + theme.secondary), + [ + #set std.text(40pt) + #doc.title + ], + ) + + if doc.subtitle != none { + std.text(18pt, doc.subtitle) + v(1em) + } + + std.text(14pt)[Version #doc.package.version] + if doc.date != none { + h(3em) + std.text(14pt, doc.date.display()) + } + h(3em) + std.text(14pt, doc.package.license) + + v(1fr) + pad( + x: 10%, + { + set align(left) + doc.abstract + }, + ) + v(1fr) + if doc.show-outline { + std.heading(level: 2, outlined: false, numbering: none, "Table of Contents") + columns( + 2, + outline(title: none), + ) + } + v(2fr) + pagebreak() +} + +#let last-page(doc, theme) = { } diff --git a/packages/preview/mantys/1.0.0/src/themes/orly.typ b/packages/preview/mantys/1.0.0/src/themes/orly.typ new file mode 100644 index 0000000000..43137f6875 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/themes/orly.typ @@ -0,0 +1,263 @@ + +#import "@preview/fauxreilly:0.1.1": orly + + +#import "../api/icons.typ": icon + +#let primary = rgb("#b22784") +#let secondary = rgb("#0a0a0a") + + +#let fonts = ( + serif: ("TeX Gyre Schola", "Liberation Serif"), + sans: ("TeX Gyre Heros", "Open Sans Condensed", "Open Sans", "Liberation Sans", "Helvetica Neue", "Helvetica"), + mono: ("TeX Gyre Cursor", "Liberation Mono"), +) + +#let muted = ( + fill: luma(210), + bg: luma(240), +) + +#let text = ( + size: 12pt, + font: fonts.serif, + fill: rgb(35, 31, 32), +) + +#let heading = ( + font: fonts.serif, + fill: text.fill, +) + +#let header = ( + size: 10pt, + fill: text.fill, +) + +#let footer = ( + size: 9pt, + fill: muted.fill, +) + +#let code = ( + size: 9pt, + font: fonts.mono, + fill: rgb("#999999"), +) + +#let alert-colors = ( + info: rgb(23, 162, 184), + warning: rgb(255, 193, 7), + error: rgb(220, 53, 69), + success: rgb(40, 167, 69), +) + +#let alert(alert-type, body) = { + let color = if type(alert-type) == color { + alert-type + } else { + alert-colors.at(alert-type, default: luma(100)) + } + block( + stroke: (left: 2pt + color, rest: 0pt), + fill: color.lighten(88%), + inset: 8pt, + width: 100%, + spacing: 2%, + std.text(size: .88em, fill: color.darken(60%), body), + ) +} + + +#let tag(color, body) = box( + stroke: none, + fill: color, + // radius: 50%, + radius: 3pt, + inset: (x: .5em, y: .25em), + baseline: 1%, + std.text(body), +) + +#let emph = ( + link: secondary, + package: primary, + module: rgb("#8c3fb2"), + since: rgb("#a6fbca"), + until: rgb("#ffa49d"), + changed: rgb("#fff37c"), + deprecated: rgb("#ffa49d"), + compiler: teal, + "context": rgb("#fff37c"), +) + +#let commands = ( + argument: navy, + command: blue, + variable: rgb("#9346ff"), + builtin: eastern, + comment: gray, + symbol: text.fill, +) + +#let values = ( + default: rgb(181, 2, 86), +) + + +#let page-init(doc, theme) = ( + body => { + show std.heading: it => { + let level = it.at("level", default: it.at("depth", default: 2)) + let scale = (1.4, 1.2, 1.1).at(level - 1, default: 1.0) + + let size = 1em * scale + let above = ( + if level == 1 { + 1.8em + } else { + 1.44em + } + / scale + ) + let below = 0.75em / scale + + set std.text(size: size, ..heading) + set block(above: above, below: below) + + if level == 1 { + pagebreak(weak: true) + + block( + below: .82em, + stroke: (bottom: 2pt + theme.secondary), + width: 100%, + inset: (bottom: .64em), + { + if it.numbering != none { + set std.text(fill: secondary) + [Part ] + counter(std.heading).display() + linebreak() + } + + set std.text(fill: primary, size: 2em) + + it.body + + // v(-.64em) + // line(length: 100%, stroke: 2pt + theme.secondary) + }, + ) + } else { + block({ + set std.text(fill: secondary) + if it.numbering != none { + counter(std.heading).display() + h(1.8em) + } + it.body + }) + } + } + body + } +) + +#let _display-author(author) = block({ + smallcaps(author.name) + if author.affiliation not in (none, ()) { + footnote(smallcaps(author.affiliation)) + } + set std.text(.88em) + if author.email != none { + " <" + link("mailto://" + author.email, icon("mail") + " " + author.email) + ">" + } + if author.github != none { + let username = author.github.trim("@") + [ (#link("https://github.com/" + username, icon("mark-github") + " " + username))] + } + // if author.urls != () { + // author.urls.map(link).join(linebreak()) + // } +}) + +#let title-page(doc, theme) = { + let title = if doc.title == none { + doc.package.name + } else { + doc.title + } + + orly( + color: theme.primary, + title: title, + subtitle: doc.package.description, + signature: [ + #set std.text(14pt) + v#doc.package.version + #if doc.date != none [ + #h(4em) + #doc.date.display() + ] + #h(4em) + #doc.package.license + ], + publisher: doc.package.authors.map(_display-author).join(", "), + pic: doc.theme-options.at("title-image", default: none), + top-text: doc.subtitle, + + font: theme.heading.font, + publisher-font: theme.fonts.sans, + ) + + set page(header: none, footer: none) + std.heading(depth: 1, title, numbering: none) + + grid( + columns: calc.min(4, doc.package.authors.len()), + gutter: .64em, + ..doc.package.authors.map(_display-author) + ) + + if doc.urls != none { + block(doc.urls.map(link).join(linebreak())) + } + + if doc.abstract != none { + pad( + x: 10%, + { + set align(left) + doc.abstract + }, + ) + v(1em) + } + + if doc.show-outline { + set align(left) + set block(spacing: 0.65em) + show outline.entry.where(level: 1): it => { + v(0.85em, weak: true) + strong(link(it.element.location(), it.body)) + } + + v(1fr) + std.heading(depth: 2, outlined: false, bookmarked: false, numbering: none, "Table of Contents") + columns( + 2, + outline( + title: none, + indent: 1em, + depth: 3, + fill: repeat("."), + ), + ) + } + + pagebreak() +} + +#let last-page(doc, theme) = { } diff --git a/packages/preview/mantys/1.0.0/src/util/is.typ b/packages/preview/mantys/1.0.0/src/util/is.typ new file mode 100644 index 0000000000..08811d76b1 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/util/is.typ @@ -0,0 +1,20 @@ +// TODO: (jneug) rename module to something else + +#let str(value) = std.type(value) == std.type("") +#let dict(value) = std.type(value) == std.type((:)) +#let arr(value) = std.type(value) == std.type(()) +#let type(value) = std.type(value) == std.type +#let content(value) = std.type(value) == std.content + +#let raw(value) = std.type(value) == std.content and value.func() == std.raw + +#let _none(value) = value == none +#let _auto(value) = value == auto + + +#let barg(it) = { + it.func() == metadata and it.value.kind == "barg" +} +#let sarg(it) = { + return repr(it.func()) == "sequence" and it.children.first() == [..] +} diff --git a/packages/preview/mantys/1.0.0/src/util/typing.typ b/packages/preview/mantys/1.0.0/src/util/typing.typ new file mode 100644 index 0000000000..4a4669a5de --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/util/typing.typ @@ -0,0 +1,73 @@ +#let typst-content = content +#let typst-regex = regex + +#import "is.typ" +#import "../_deps.typ": valkyrie +#import valkyrie: * + + +// Aliases for the original valkyrie types. +#let valkyrie-choice = choice +#let valkyrie-content = content + +/// Schema for a field that always will be set to a constant value, no matter +/// the value supplied for that key. The value is taken from the supplied +/// types default value. +/// +/// - type (dictionary): Any of the valkyrie types with a default value set. +/// -> dictionary +#let constant(type) = { + type.optional = true + type.pre-transform = (..) => type.default + type +} + +#let optional(type) = { + type.optional = true + type +} + +/// Augments the content type to include #dtype("symbol") as allowed type. +#let content = base-type.with(name: "content", types: (typst-content, str, symbol)) + +/// Type for Typst build-in #dtype("version"). +#let version = base-type.with( + name: "version", + types: (std.version,), + pre-transform: (self, it) => { + if is.str(it) { + std.version(..it.split(".").map(int)) + } else { + it + } + }, +) +/// Type for Typst build-in #dtype("symbol"). +#let symbol = base-type.with(name: "symbol", types: (symbol,)) +/// Type for Typst build-in #dtype("label"). +#let label = base-type.with(name: "label", types: (label,)) +/// Type for Typst build-in #dtype("auto"). +#let _auto = base-type.with(name: "auto", types: (type(auto),)) + +#let url( + assertions: (), + ..args, +) = string( + name: "url", + assertions: ( + assert.matches( + typst-regex("(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?\/[a-zA-Z0-9]{2,}|((https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?)|(https:\/\/www\.|http:\/\/www\.|https:\/\/|http:\/\/)?[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})?"), + ), + ..assertions, + ), + ..args, +) + + +#let optional-coerce(coercion) = (self, it) => { + if self.optional and it == none { + return none + } else { + return coercion(self, it) + } +} diff --git a/packages/preview/mantys/1.0.0/src/util/utils.typ b/packages/preview/mantys/1.0.0/src/util/utils.typ new file mode 100644 index 0000000000..7ef39ad3f8 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/util/utils.typ @@ -0,0 +1,409 @@ + +/// Extracts text from #dtype("content"). +/// -> string +#let get-text( + /// A content element. + /// -> content + it, +) = { + if type(it) == content and it.has("text") { + return it.text + } else if type(it) in (symbol, int, float, version, bytes, label, type, str) { + return str(it) + } else { + return repr(it) + } +} + + +/// Gets the value at #arg[key] from the #typ.t.dict #arg[dict]. #arg[key] can be in dot-notation to access values in nested dictionaries. +/// -> any +#let dict-get( + /// Dictionary to get Data from + /// -> dictionary + dict, + /// String key of the value in dot-notation. + /// -> str + key, + /// Default value, if the key can't be found. + /// -> any + default: none, +) = { + if "." not in key { + return dict.at(key, default: default) + } else { + let keys = key.split(".") + while (keys.len() > 0) { + let k = keys.remove(0) + if type(dict) != dictionary or k not in dict { + return default + } else { + dict = dict.at(k) + } + } + if keys.len() > 0 { + return default + } else { + return dict + } + } +} + +/// #test( +/// `utils.update-dict({a: 1, b: { c: 2 }}, "b.c", v => v + 1) == {a: 1, b: { c: 3 }}` +/// ) + +/// Updates the value in #arg[dict] at #arg[key] by passing the value to #arg[func] and storing the result. If #arg[key] is not in #arg[dict], #arg[default] is used instead. +/// +/// #arg[key] may be in dot-notation to update values in nested dictionaries. +/// -> dictionary +#let dict-update( + /// The #typ.t.dict to update. + /// -> dictionary | any + dict, + /// The key of the value. May be in dot-notation. + /// -> str + key, + /// Update function: #lambda("any", ret:"any") + /// -> function + func, + /// Default value to use if #arg[key] is not found in #arg[dict]. + /// -> any + default: none, +) = { + if key != "" and type(dict) != dictionary { + return func(default) + } + + if "." not in key { + dict.insert(key, func(dict.at(key, default: default))) + } else { + let keys = key.split(".") + let k = keys.remove(0) + + if k in dict { + dict.at(k) = dict-update(dict.at(k), keys.join("."), func, default: default) + } else { + dict.insert(k, func(default)) + } + } + return dict +} + +/// Recursivley merges the passed in dictionaries. +/// #codesnippet[```typ +/// #get.dict-merge( +/// (a: 1, b: 2), +/// (a: (one: 1, two:2)), +/// (a: (two: 4, three:3)) +/// ) +/// // gives (a:(one:1, two:4, three:3), b: 2) +/// ```] +/// -> dictionary +#let dict-merge( + /// Dictionaries to merge. + /// -> dictionary + ..dicts, +) = { + if dicts.pos().all(d => type(d) == dictionary) { + let c = (:) + for dict in dicts.pos() { + for (k, v) in dict { + if k not in c { + c.insert(k, v) + } else { + let d = c.at(k) + c.insert(k, dict-merge(d, v)) + } + } + } + return c + } else { + return dicts.pos().last() + } +} + +/// Creates a #typ.version object from the supplied arguments. #arg[args] can +/// be a string with a version in dot-notation. +/// - #ex(`#utils.ver(1,2,3)`) +/// - #ex(`#utils.ver("1.2.3")`) +/// - #ex(`#utils.ver("1.2", 3)`) +/// -> version +#let ver( + /// -> int | array | str + ..args, +) = { + if args.pos() == () { + return version(0) + } + + version( + ..args + .pos() + .map(v => { + if type(v) == version { + array(v) + } else if type(v) == str { + v.split(".") + } else { + v + } + }) + .flatten() + .map(int), + ) +} + + +#let _unreserved = ( + ( + "-", + "_", + ".", + "~", + ) + + range(10).map(str) + + range(65, 65 + 26).map(str.from-unicode) + + range(97, 97 + 26).map(str.from-unicode) +) + +#let to-hex(i) = { + let _hex-digits = "0123456789ABCDEF" + + "%" + _hex-digits.at(i.bit-rshift(4).bit-and(0x0F)) + _hex-digits.at(i.bit-and(0x0F)) +} + +#let encode-char(char) = if char not in _unreserved { + array(bytes(char)).map(to-hex).join() +} else { + char +} + +/// URL-encode a string. +/// +/// - #ex(`#utils.url-encode("ä b ß")`) +/// +/// -> str +#let url-encode(t) = { + t.codepoints().map(encode-char).join() +} + + + +/// Displays #arg[code] as inline #typ.raw code (with #arg(inline: true)). +/// - #ex(`#utils.rawi("my-code")`) +/// -> content +#let rawi( + /// The content to show as inline raw. + /// -> string | content + code, + /// Optional language for highlighting. + /// -> str + lang: none, +) = raw(block: false, lang: lang, get-text(code)) + + +/// Shows #arg[code] as inline #typ.raw text (with #arg(block: false)) and with the given #arg[color]. The language argument will be passed to #typ.raw, but will have no effect, since #arg[code] will have an uniform color. +/// - #ex(`#utils.rawc(purple, "some inline code")`) +/// -> content +#let rawc( + /// Color for the #typ.raw text. + /// -> color + color, + /// String content to be displayed as #typ.raw. + /// -> str + code, + /// Optional language name. + /// -> str + lang: none, +) = text(fill: color, rawi(lang: lang, code)) + + +/// Returns a light or dark color, depending on the provided #arg[color]. +/// ```example +/// - #utils.get-text-color(red) +/// - #utils.get-text-color(red.lighten(50%)) +/// ``` +/// -> color +#let get-text-color( + /// Paint to get the text color for. + /// -> color | gradient + color, + /// Color to use, if #arg[color] is a dark color. + /// -> color | gradient + light: white, + /// Color to use, if #arg[color] is a light color. + /// -> color | gradient + dark: black, +) = { + if type(color) == gradient { + color = color.sample(50%) + } + if std.color.hsl(color).components(alpha: false).last() < 62% { + light + } else { + dark + } +} + + +/// Places a hidden #typ.figure in the document, that can be referenced via the +/// usual `@label-name` syntax. +/// -> content +#let place-reference( + /// Label to reference. + /// -> label + label, + /// Kind for the reference to properly step counters. + /// -> str + kind, + /// Supplement to show when referencing. + /// -> str + supplement, + /// Numbering schema to use. + /// -> str + numbering: "1", +) = place()[#figure( + kind: kind, + supplement: supplement, + numbering: numbering, + [], + )#label] + + +/// Creates a preamble to attach to code before evaluating. +/// #arg("imports") is a #typ.t.dict with (`module`: `imports`) pairs, +/// like #utils.rawi(lang:"typc", `(mantys: "*")`.text). This will create a +/// preamble of the form #value("#import mantys: *;") +/// ```example +/// #utils.build-preamble((mantys: "*", tidy: "parse-module, show-module")) +/// ``` +/// -> str +#let build-preamble( + /// (`module`: `imports`) pairs. + /// -> dict + imports, +) = { + if imports != (:) { + return ( + imports + .pairs() + .map(((mod, imp)) => { + "#import " + mod + ": " + imp + }) + .join("; ") + + ";\n" + ) + } else { + return "" + } +} + + +/// Adds a preamble for customs imports to #arg[code]. +/// -> str +#let add-preamble( + /// A Typst code block as #typ.raw or #typ.str. + /// -> content | text + code, + /// The imports to add to the code. If it is a #typ.t.dict it will + /// first be passed to @cmd:utils:build-preamble. + /// -> dict | str + imports, +) = { + if type(code) != str { + code = code.text + } + return build-preamble(imports) + code +} + +/// Creates a #typ.label to be placed in the document (usually by cmd:utils.place-reference). +/// // TODO fix label +/// The created label is in the same format #package[Tidy] uses but will be prefixed with `cmd` to identify command references outside of docstrings. +/// - #ex(`#str(mantys.utils.create-label("create-label", arg:"module", module:"utils"))`) +/// +/// #test( +/// `str(mantys.utils.create-label("create-label", arg:"module", module:"utils")) == "cmd:utils:create-label.module"`, +/// `str(mantys.utils.create-label("create-label", arg:"module")) == "cmd:create-label.module"`, +/// `str(mantys.utils.create-label("create-label")) == "cmd:create-label"`, +/// `str(mantys.utils.create-label("create-label", module:"utils")) == "cmd:utils:create-label"` +/// ) +/// +/// -> label +#let create-label( + /// Name of the command. + /// -> str + command, + /// Argument name to add to the label + /// -> str + arg: none, + /// Optional module of the command. + /// -> str + module: none, + /// Prefix for command labels. By default command labels + /// are prefixed with `cmd`, eg. `cmd:utils.create-label`. + /// -> str + prefix: "cmd", +) = { + let not-none(s) = s != none + let label-text = (command, arg).filter(not-none).join(".") + let label-prefix = (prefix, module).flatten().filter(not-none).join(":") + return std.label(label-prefix + ":" + label-text) +} + +/// Parses a #typ.label text into a #typ.t.dictionary with the command and module name (if present). +/// A label in the format `"cmd:utils.split-cmd-name.arg-name"` will be split into\ +/// `(name: "split-cmd-name", arg:"arg-name", module: "utils", prefix:"cmd")`\ +/// TODO: removing "mantys" prefix should happen in tidy template +/// +/// #test( +/// `mantys.utils.parse-label("cmd:foo:bar.baz") == (name:"bar", arg:"baz", module:"foo", prefix:"cmd")`, +/// `mantys.utils.parse-label(label("cmd:foo:bar")) == (name:"bar", arg:none, module:"foo", prefix:"cmd")`, +/// `mantys.utils.parse-label(label("type:bar.baz")) == (name:"bar", arg:"baz", module:none, prefix:"type")` +/// ) +/// +/// -> dictionary +#let parse-label( + /// The label to parse. + /// -> label | str + label, +) = { + if type(label) == std.label { label = str(label) } + + let (..prefixes, command) = label.split(":") + let parts = command.split(".") + + let module = if prefixes != () and prefixes.last() not in ("cmd", "type", "arg") { + prefixes.remove(-1) + } + return ( + name: parts.first(), + arg: parts.at(1, default: none), + module: module, + prefix: prefixes.join(":"), + ) +} + + +/// Splits a string into a dictionary with the command name and module (if present). +/// A string of the form `"cmd:utils.split-cmd-name"` will be split into\ +/// `(name: "split-cmd-name", module: "utils")`\ +/// (Note that the prefix `cmd:` is removed.) +/// +/// #test(`mantys.utils.split-cmd-name("cmd:foo.bar") == (name:"bar", module:"foo")`) +/// +/// -> dictionary +#let split-cmd-name( + /// The command optionally with module and `cmd:` prefix. + /// -> str + name, +) = { + let cmd = (name: name, module: none) + if name.starts-with("cmd:") { + cmd.name = name.slice(4) + } + if cmd.name.contains(".") { + (cmd.name, cmd.module) = (cmd.name.split(".").slice(1).join("."), cmd.name.split(".").first()) + } + return cmd +} diff --git a/packages/preview/mantys/1.0.0/src/util/valkyrie.typ b/packages/preview/mantys/1.0.0/src/util/valkyrie.typ new file mode 100644 index 0000000000..b2404bc067 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/util/valkyrie.typ @@ -0,0 +1,76 @@ +#import "../core/styles.typ" + +#let parse-schema( + schema, + expand-schemas: false, + expand-choices: 2, + child-schemas: none, + // Passing in dtype and value to avoid circular imports + _dtype: none, + _value: none, +) = { + let el = schema + + if type(child-schemas) == str { + return _dtype(child-schemas) + } + + // TODO: implement expand-schemas and child-schemas options + let options = ( + expand-schemas: expand-schemas, + expand-choices: expand-choices, + _dtype: _dtype, + _value: _value, + ) + + // Recursivley handle dictionaries + if "dictionary-schema" in el { + if el.dictionary-schema != (:) { + `(` + terms( + hanging-indent: 1.28em, + indent: .64em, + ..for (key, el) in el.dictionary-schema { + ( + terms.item( + styles.arg(key) + if el.optional { + ": " + _value(none) + } else if el.default != none { + ": " + raw(lang: "typc", repr(el.default)) + }, + parse-schema( + el, + child-schemas: if type(child-schemas) == dictionary { child-schemas.at(key, default: none) }, + ..options, + ), + ), + ) + }, + ) + `)` + } else { + _dtype(dictionary) + } + } else if "descendents-schema" in el { + _dtype(array) + " of " + parse-schema( + el.descendents-schema, + child-schemas: if type(child-schemas) == array { child-schemas }, + ..options, + ) + } else if el.name == "enum" { + [one of ] + `(` + if expand-choices not in (false, 0) { + if type(expand-choices) == int and el.choices.len() > expand-choices { + el.choices.slice(0, expand-choices).map(_value).join(", ") + [ #sym.dots ] + } else { + el.choices.map(_value).join(", ") + } + } else [ + #sym.dots + ] + `)` + } else { + _dtype(el.name) + } +} diff --git a/packages/preview/mantys/1.0.0/template/manual.typ b/packages/preview/mantys/1.0.0/template/manual.typ new file mode 100644 index 0000000000..544cbbf005 --- /dev/null +++ b/packages/preview/mantys/1.0.0/template/manual.typ @@ -0,0 +1,44 @@ +#import "@preview/mantys:1.0.0": * + +#show: mantys( + name: "mantys", + version: "1.0.0", + authors: ( + "Jonas Neugebauer", + ), + license: "MIT", + description: "Helpers to build manuals for Typst packages.", + repository: "https://github.com/jneug/typst-mantys", + + /// Uncomment one of the following lines to load the above + /// package information directly from the typst.toml file + // ..toml("../typst.toml"), + // ..toml("typst.toml"), + + title: "Manual title", + // subtitle: "Tagline", + date: datetime.today(), + + // url: "", + + abstract: [ + #lorem(50) + ], + + // examples-scope: ( + // scope: (:), + // imports: (:) + // ) + + // theme: themes.modern +) + +/// Helper for Tidy-Support +/// Uncomment, if you are using Tidy for documentation +// #let show-module(name, scope: (:), outlined: true) = tidy-module( +// read(name + ".typ"), +// name: name, +// show-outline: outlined, +// include-examples-scope: true, +// extract-headings: 3, +// ) diff --git a/packages/preview/mantys/1.0.0/typst.toml b/packages/preview/mantys/1.0.0/typst.toml new file mode 100644 index 0000000000..52338c1cc0 --- /dev/null +++ b/packages/preview/mantys/1.0.0/typst.toml @@ -0,0 +1,17 @@ +[package] +name = "mantys" +version = "1.0.0" +entrypoint = "src/mantys.typ" +authors = ["Jonas Neugebauer <@jneug>"] +license = "MIT" +description = "Helpers to build manuals for Typst packages and templates." +repository = "https://github.com/jneug/typst-mantys" +compiler = "0.12.0" +exclude = ["tests", "scripts", "docs", "Justfile", "tbump.toml"] +categories = ["layout", "model", "utility"] +keywords = ["manual", "documentation"] + +[template] +path = "template" +entrypoint = "manual.typ" +thumbnail = "docs/assets/thumbnail.png"