diff --git a/packages/preview/mantys/1.0.0/LICENSE b/packages/preview/mantys/1.0.0/LICENSE new file mode 100644 index 000000000..cfe6de305 --- /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 000000000..fac5f202f --- /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 000000000..232b30d97 --- /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 000000000..55418943f 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 000000000..15b07cc18 --- /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/theme-cnltx-pages.typ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-cnltx-pages.typ new file mode 100644 index 000000000..d1d4f0a90 --- /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 000000000..26f31a8fa 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 000000000..5be9273e3 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 000000000..90d7779bf 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 000000000..cee27a542 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 000000000..061dabe91 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 000000000..b8b05a29b 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 000000000..01adc5b24 --- /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 000000000..127a3bdc9 --- /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 000000000..296fb432b 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 000000000..be2e52fa3 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 000000000..91c703317 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 000000000..197851950 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 000000000..b52f08f82 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 000000000..a7f926e16 --- /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 000000000..6e4f25c1e --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/assets/examples/theme-orly-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.orly +) + + +#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 000000000..0c7f31ddd 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 000000000..821d044db 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 000000000..cbf316dab 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 000000000..3f51d998e 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 000000000..ce7d3a5cc 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 000000000..7a03021b9 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 000000000..a30ba1ac1 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 000000000..0143bab40 --- /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 000000000..c5f3003ee --- /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 000000000..2dcb11a50 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 000000000..75b912aa3 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 000000000..9bab07cf8 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 000000000..ad95f1570 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 000000000..d27e77d15 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 000000000..80cdc7e2e 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 000000000..a36fa0f33 --- /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 000000000..9d7b9d151 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 000000000..7cfa5c7f0 --- /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 000000000..36cb5f56b 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 000000000..8d8f49da9 --- /dev/null +++ b/packages/preview/mantys/1.0.0/docs/manual.typ @@ -0,0 +1,848 @@ +#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 + +#let CNLTX = package("CNLTX") +#let 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, + + 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: +#columns(3)[ + #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 + ```] + +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 #builtin[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 #builtin[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 #builtin[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))[ + 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))[ + +] + +#info-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 #arg[index-references] was set to #typ.v.false, no index entries are created by default but by adding `[+]` to a reference it will be. + +```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. + +== Showing 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 wrapped inside the corresponding command. + +#example[````typ + ```example + Some *bold* text. + ``` + + ```side-by-side + Some *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. + += 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: 2, + column-gutter: 5%, + [The default theme for MANTYS. Based on the Typst documentation and website.], + frame(arg(theme: "default", _value: theme-var)), +) +#align(center, image("assets/examples/theme-typst.png", height: 30%)) + +==== Modern theme +#grid( + columns: 2, + 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)), +) +#align(center, image("assets/examples/theme-modern.png", height: 30%)) + +==== CNLTX theme +#grid( + columns: 2, + column-gutter: 5%, + [This theme is based on the original #CNLTX template.], frame(arg(theme: "cnltx", _value: theme-var)), +) +#align(center, image("assets/examples/theme-cnltx.png", height: 30%)) + +==== O'Rly#super[?] theme +#grid( + columns: 2, + 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)), +) +#align(center, image("assets/examples/theme-orly.png", height: 30%)) + +===== Theme Options +#argument("pic", 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", + 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/src/_api.typ b/packages/preview/mantys/1.0.0/src/_api.typ new file mode 100644 index 000000000..e11b35e9c --- /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 000000000..2bdf03ebe --- /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.1" +#import "@preview/marginalia:0.1.1" + +#import "@preview/showybox:2.0.3" +#import "@preview/codly:1.1.1" + +#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 000000000..df80f298a --- /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 000000000..d3979f2c5 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/commands.typ @@ -0,0 +1,680 @@ + +#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()[ + #utils.place-reference( + utils.create-label(name, module: args.named().at("module", default: none)), + "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 000000000..ccf8ee9ec --- /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 000000000..1b26373fb --- /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 000000000..371b1bb89 --- /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 000000000..006a1e163 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/links.typ @@ -0,0 +1,277 @@ +#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: "foundations/calc", + clamp: "foundations/calc#functions-clamp", + abs: "foundations/calc#functions-abs", + pow: "foundations/calc#functions-pow", + // TODO: link rest of calc functions + 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", + // TODO: link rest of str functions + 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", +) + + +#let link-docs(..path) = std.link("https://typst.app/docs/reference/" + path.pos().first(), ..path.pos().slice(1)) + +#let link-dtype(..name) = link-docs(_type-map.at(name.pos().first(), default: ""), ..name.pos().slice(1)) + +#let link-builtin(..name) = link-docs(_builtin-map.at(name.pos().first(), default: ""), ..name.pos().slice(1)) + +#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)] + } +} + +// TODO: Make repo: auto named and load repo url from document +#let github(repo) = { + if repo.starts-with("https://github.com/") { + repo = repo.slice(19) + } + link("https://github.com/" + repo, repo) +} + +#let github-user(name) = { + link("https://github.com/" + name, sym.at + name) +} + +// TODO: Make repo: auto named and load repo name from document +#let github-file(repo, filepath, branch: "main") = { + if repo.starts-with("https://github.com/") { + repo = repo.slice(19) + } + if filepath.starts-with("/") { + filepath = filepath.slice(1) + } + let url = "https://github.com/" + repo + "/tree/" + branch + "/" + filepath + link(url, filepath) +} + +// TODO: Make repo: auto named and load repo name from document +#let universe(pkg, version: none) = { + let url = "https://typst.app/universe/package/" + pkg + if version != none { + url += "/" + str(version) + } + link(url, package(pkg)) +} + +// TODO: Make repo: auto named and load repo name from document +#let preview(pkg, ver: auto) = { + if ver == auto { + let m = pkg.match(regex("\d+\.\d+\.\d+$")) + if m != none { + ver = m.text + pkg = pkg.slice(0, ver.len() + 1) + } else { + ver = none + } + } + if type(ver) == str { + ver = version(..ver.split(".").map(int)) + } + link( + "https://github.com/typst/packages/tree/main/packages/preview/" + + pkg + + if ver != none { + "/" + str(ver) + } else { + "" + }, + package( + pkg + + if ver != none { + ":" + str(ver) + } else { + "" + }, + ), + ) +} 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 000000000..c52558de0 --- /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 000000000..5668a32b3 --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/api/types.typ @@ -0,0 +1,242 @@ + +#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)`) +#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) +#let is-custom-type(name) = query(label("mantys:custom-type-" + name)) != () + +#let link-custom-type(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.") + } +} + +// TODO: (jneug) parse types like "array[str]" +#let dtype(name, link: true) = context { + 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. +#let dtypes(..types, link: true, sep: box(inset: (left: 1pt, right: 1pt), sym.bar.v)) = { + types.pos().map(dtype.with(link: link)).join(sep) +} + +#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 ? +#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 000000000..5d8dc0bff --- /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 000000000..8756b331d --- /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 000000000..9c76b7677 --- /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 000000000..fb2d015ab --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/core/layout.typ @@ -0,0 +1,158 @@ +#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): codesnippet + show raw.where(block: true): set par(justify: false) + 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 000000000..787e52c5e --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/core/schema.typ @@ -0,0 +1,243 @@ +#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), + 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 000000000..d8c1d24b7 --- /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 000000000..5d73c3c5d --- /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 000000000..73eb32c6b --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/core/tidy.typ @@ -0,0 +1,257 @@ +#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), + { + 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 000000000..289f6a615 --- /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 000000000..6f9fba0d8 --- /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 000000000..b8d3e64eb --- /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 000000000..82fddbdb9 --- /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 000000000..43137f687 --- /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 000000000..08811d76b --- /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 000000000..4a4669a5d --- /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 000000000..30c70caaa --- /dev/null +++ b/packages/preview/mantys/1.0.0/src/util/utils.typ @@ -0,0 +1,342 @@ + +/// 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() + } +} + +/// 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 000000000..b2404bc06 --- /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 000000000..544cbbf00 --- /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 000000000..52338c1cc --- /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"