diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 0000000..fb7672f --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,18 @@ +name: Continuous integration +on: [push, pull_request] + +jobs: + tests: + name: Tests + runs-on: ubuntu-latest + steps: + - uses: yusancky/setup-typst@v2 + id: setup-typst + with: + version: 'latest' + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + - run: python tools/check_tests.py + - run: python tools/check_readme.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe1de4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +tools/__pycache__/ +tools/tmp/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c3d9be4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,25 @@ +. Version 0.2.0 + - Add type checking to arguments + - Add in-source documentation using tidy + - Add more examples + - Add link-to parameter and refactor code + - Add unit tests and CI + - Add Russian translations ([@WeetHet](https://github.com/WeetHet)) + +- Version 0.1.4 + - Fix error on unnamed theorems + +- Version 0.1.3 + - Allow "1.1." numbering style by default + - Ignore unnumbered subheadings + - Add max-heading-level parameter to thm-numbering-heading + - Adapt lemmify to typst version 0.8.0 + +- Version 0.1.2 + - Better error message on unnumbered headings + - Add Italian translations ([@porcaror](https://github.com/porcaror)) + +- Version 0.1.1 + - Add Dutch translations ([@BroodjeKroepoek](https://github.com/BroodjeKroepoek)) + - Add French translations ([@MDLC01](https://github.com/MDLC01)) + - Fix size of default styles and make them breakable diff --git a/README.md b/README.md index 458cf04..de13687 100644 --- a/README.md +++ b/README.md @@ -1,310 +1,263 @@ -# Lemmify +# lemmify Lemmify is a library for typesetting mathematical theorems in typst. It aims to be easy to use while trying to be as flexible and idiomatic as possible. -This means that the interface might change with updates -to typst (for example if user-defined element functions -are introduced). But no functionality should be lost. +This means that the interface might change with updates to typst +(for example if user-defined element functions are introduced). +But no functionality should be lost. -## Basic Usage +If you are encountering any bugs, have questions or are missing +features, feel free to open an issue on +[GitHub](https://github.com/Marmare314/lemmify). -To get started with Lemmify, follow these steps: +## Basic usage + +1. Import lemmify: -1. Import the Lemmify library: ```typst -#import "@preview/lemmify:0.1.4": * +#import "@preview/lemmify:0.2.0": default-theorems, select-kind + ``` -2. Define the default styling for a few default theorem types: +2. Generate some common theorem kinds with pre-defined style: + ```typst #let ( theorem, lemma, corollary, remark, proposition, example, - proof, rules: thm-rules -) = default-theorems("thm-group", lang: "en") + proof, theorem-rules +) = default-theorems(lang: "en") ``` -3. Apply the generated styling: +3. Apply the generated style: + ```typst -#show: thm-rules +#show: theorem-rules +``` + +4. Customize the theorems using show rules. For example, to add a block around proofs: + +```typst +#show select-kind(proof): block.with( + breakable: true, + width: 100%, + fill: gray, + inset: 1em, + radius: 5pt +) ``` -4. Create theorems, lemmas, and proofs using the defined styling: +5. Create theorems, lemmas, and proofs: + ```typst #theorem(name: "Some theorem")[ Theorem content goes here. ] -#proof[ +#theorem(numbering: none)[ + Another theorem. +] + +#proof(link-to: )[ Complicated proof. ] @proof and @thm[theorem] ``` -5. Customize the styling further using show rules. For example, to add a red box around proofs: -```typst -#show thm-selector("thm-group", subgroup: "proof"): it => box( - it, - stroke: red + 1pt, - inset: 1em -) -``` - The result should now look something like this: -![image](https://github.com/Marmare314/lemmify/assets/49279081/ba5e7ed4-336d-4b1a-8470-99fa23bf5119) +![image](docs/images/basic-usage.png) -## Useful examples +## Examples -If you do not want to reset the theorem counter on headings -you can use the `max-reset-level` parameter: +By default theorems are reset on every heading. This can be changed with the +`max-reset-level` +parameter of +`default-theorems()`. This also +changes which heading levels will be used in the numbering. To not reset them +at all +`max-reset-level` +can be set to 0. ```typst -default-theorems("thm-group", max-reset-level: 0) -``` +#import "@preview/lemmify:0.2.0": default-theorems -It specifies the highest level at which the counter is reset. To manually reset the counter you can use the -`thm-reset-counter` function. +#let ( + theorem, theorem-rules +) = default-theorems(max-reset-level: 2) +#show: theorem-rules +#set heading(numbering: "1.1") + += Heading +#theorem(lorem(5)) +== Heading +#theorem(lorem(5)) +=== Heading +#theorem(lorem(5)) +``` ---- +![image](docs/images/reset-example.png) -By specifying `numbering: none` you can create unnumbered -theorems. +Each theorem belongs to a group and every group shares one counter. The theorems created +by +`default-theorems()` +all belong to the same group, except for proofs. +You can create seperate groups by passing a group parameter to +`default-theorems()`. +The next example shows how to create seperately numbered examples. ```typst -#example(numbering: none)[ - Some example. -] -``` +#import "@preview/lemmify:0.2.0": default-theorems -To make all examples unnumbered you could use the following code: - -```typst -#let example = example.with(numbering: none) +#let (theorem, theorem-rules) = default-theorems() +#show: theorem-rules +#let ( + example, theorem-rules +) = default-theorems(group: "example-group") +#show: theorem-rules + +#theorem(lorem(5)) +#example(lorem(5)) +#example(lorem(5)) +#theorem(lorem(5)) ``` ---- +![image](docs/images/group-example.png) -To create other types (or subgroups) of theorems you can use the -`new-theorems` function. +The link-to parameter can be used to link theorems to other content. By default +theorems are linked to the last heading and proofs are linked to the last theorem. +This example shows how corallaries can be linked to the last theorem. +Note that it's fine to only apply the +`theorem-rules` +once here since both theorem-kinds belong to the same group. ```typst -#let (note, rules) = new-theorems("thm-group", ("note": text(red)[Note])) -#show: rules -``` +#import "@preview/lemmify:0.2.0": default-theorems, select-kind, reset-counter -If you have already defined custom styling you will notice that -the newly created theorem does not use it. -You can create a dictionary to make applying it again easier. - -```typst -#let my-styling = ( - thm-styling: thm-styling-simple, - thm-numbering: .., - ref-styling: .. +#let (theorem, theorem-rules) = default-theorems() +#let (corollary,) = default-theorems( + group: "corollary-group", + link-to: select-kind(theorem) ) - -#let (note, rules) = new-theorems("thm-group", ("note": "Note), ..my-styling) +#show: theorem-rules +#show select-kind(theorem): it => {it; reset-counter(corollary)} + +#theorem(lorem(5)) +#corollary(lorem(5)) +#corollary(lorem(5)) +#theorem(lorem(5)) +#corollary(lorem(5)) ``` ---- +![image](docs/images/corollary-example.png) -By varying the `group` parameter you can create independently numbered theorems: +The best and easiest way to change the look of theorems is to use show-rules. +The next example shows another way how the appearance of theorems can be changed. ```typst +#import "@preview/lemmify:0.2.0": default-theorems, style-simple + #let ( - theorem, proof, - rules: thm-rules-a -) = default-theorems("thm-group-a") -#let ( - definition, - rules: thm-rules-b -) = default-theorems("thm-group-b") + theorem, proof, theorem-rules +) = default-theorems( + lang: "en", + style: style-simple.with(seperator: ". "), + proof-style: style-simple.with(kind-name-style: emph, seperator: ". ") +) +#show: theorem-rules -#show: thm-rules-a -#show: thm-rules-b +#theorem(lorem(5)) +#proof(lorem(5)) ``` ---- +![image](docs/images/proof-example.png) -To specify parameters of the [styling](#styling-parameters) functions the `.with` function is used. +Doing the same thing to remarks is a bit more complicated since the style parameter applies to both theorems and remarks. ```typst +#import "@preview/lemmify:0.2.0": default-theorems, style-simple + #let ( - theorem, - rules: thm-rules + theorem, theorem-rules ) = default-theorems( - "thm-group", - thm-numbering: thm-numbering-heading.with(max-heading-level: 2) + lang: "en", + style: style-simple.with(seperator: ". ") ) +#let (remark,) = default-theorems( + style: style-simple.with(kind-name-style: emph, seperator: ". "), + numbering: none +) +#show: theorem-rules + +#theorem(lorem(5)) +#remark(lorem(5)) ``` -## Example +![image](docs/images/remark-example.png) -```typst -#import "@preview/lemmify:0.1.4": * - -#let my-thm-style( - thm-type, name, number, body -) = grid( - columns: (1fr, 3fr), - column-gutter: 1em, - stack(spacing: .5em, strong(thm-type), number, emph(name)), - body -) +If the pre-defined styles are not customizable enough you can also provide your own style. -#let my-styling = ( - thm-styling: my-thm-style -) +```typst +#import "@preview/lemmify:0.2.0": default-theorems, get-theorem-parameters + +#let custom-style(thm) = { + let params = get-theorem-parameters(thm) + let number = (params.numbering)(thm, false) + block( + inset: .5em, + fill: gray, + { + params.kind-name + " " + number + if params.name != none { ": " + params.name } + } + ) + v(0pt, weak: true) + block( + width: 100%, + inset: 1em, + stroke: gray + 1pt, + params.body + ) +} #let ( - theorem, rules -) = default-theorems("thm-group", lang: "en", ..my-styling) -#show: rules -#show thm-selector("thm-group"): box.with(inset: 1em) + theorem, theorem-rules +) = default-theorems(lang: "en", style: custom-style) +#show: theorem-rules -#lorem(20) -#theorem[ - #lorem(40) -] -#lorem(20) #theorem(name: "Some theorem")[ - #lorem(30) + #lorem(40) ] ``` -![image](https://github.com/Marmare314/lemmify/assets/49279081/b3c72b3e-7e21-4acd-82bb-3d63f87ec84b) - - -## Documentation - -The two most important functions are: - -`default-theorems`: Create a default set of theorems -based on the given language and styling. -- `group`: The group id. -- `lang`: The language to which the theorems are adapted. -- `thm-styling`, `thm-numbering`, `ref-styling`: Styling -parameters are explained in further detail in the -[Styling](#styling-parameters) section. -- `proof-styling`: Styling which is only applied to proofs. -- `max-reset-level`: The highest heading level on which -theorems are still reset. - -`new-theorems`: Create custom sets of theorems with -the given styling. -- `group`: The group id. -- `subgroup-map`: Mapping from group id to some argument. -The simple styles use `thm-type` as the argument (ie -"Beispiel" or "Example" for group id "example") -- `thm-styling`, `thm-numbering`, -`ref-styling`, `ref-numbering`: Styling which to apply -to all subgroups. - ---- - -`use-proof-numbering`: Decreases the numbering of -a theorem function by one. -See [Styling](#styling) for more information. - ---- - -`thm-selector`: Returns a selector for all theorems -of the specified group. If subgroup is specified, only the -theorems belonging to it will be selected. - ---- +![image](docs/images/style-example.png) + +There is one other way to create +`theorem-function`s: +the +`theorem-kind()` +function. It is used to create +the theorem-functions returned by +`default-theorems()` +so it behaves almost the same. The only difference is that there is no +`max-reset-level` +parameter and that no +`theorem-rules` +are returned. +A default rule which does not reset any theorem counters can be imported. -There are also a few functions to help with resetting counters. - -`thm-reset-counter`: Reset theorem group counter manually. -Returned content needs to added to the document. - -`thm-reset-counter-heading-at`: Reset theorem group counter -at headings of the specified level. Returns a rule that -needs to be shown. - -`thm-reset-counter-heading`: Reset theorem group counter -at headings of at most the specified level. Returns a rule -that needs to be shown. - -### Styling parameters - -If possible the best way to adapt the look of theorems is to use show -rules as shown [above](#basic-usage), but this is not always possible. -For example if we wanted theorems to start -with `1.1 Theorem` instead of `Theorem 1.1`. -You can provide the following functions to adapt the look of the theorems. - ----- -`thm-styling`: A function: `(arg, name, number, body) -> content`, that -allows you to define the styling for different types of theorems. -Below only the `arg` will be specified. - -Pre-defined functions -- `thm-style-simple(thm-type)`: **thm-type num** _(name)_ body -- `thm-style-proof(thm-type)`: **thm-type num** _(name)_ body □ -- `thm-style-reversed(thm-type)`: **num thm-type** _(name)_ body - ---- - -`thm-numbering`: A function: `figure -> content`, that determines how -theorems are numbered. - -Pre-defined functions: (Assume heading is 1.1 and theorem count is 2) -- `thm-numbering-heading`: 1.1.2 - - `max-heading-level`: only use the a limited number of subheadings. In this - case if it is set to `1` the result would be `1.2` instead. -- `thm-numbering-linear`: 2 -- `thm-numbering-proof`: No visible content is returned, but the -counter is reduced by 1 (so that the proof keeps the same count as -the theorem). Useful in combination with `use-proof-numbering` -to create theorems that reference the previous theorem (like proofs). - ---- - -`ref-styling`: A function: `(arg, thm-numbering, ref) -> content`, to style -theorem references. - -Pre-defined functions: -- `thm-ref-style-simple(thm-type)` - - `@thm -> thm-type 1.1` - - `@thm[custom] -> custom 1.1` - ---- -`ref-numbering`: Same as `thm-numbering` but only applies -to the references. - -## Roadmap - -- More pre-defined styles. - - Referencing theorems by name. -- Support more languages. -- Better documentation. -- Outlining theorems. - -If you are encountering any bugs, have questions or -are missing features, feel free to open an issue on -[Github](https://github.com/Marmare314/lemmify). - -## Changelog +```typst +#import "@preview/lemmify:0.2.0": theorem-kind, theorem-rules -- Version 0.1.4 - - Fix error on unnamed theorems +#let note = theorem-kind("Note") +#show: theorem-rules -- Version 0.1.3 - - Allow "1.1." numbering style by default - - Ignore unnumbered subheadings - - Add max-heading-level parameter to thm-numbering-heading - - Adapt lemmify to typst version 0.8.0 +#note(lorem(5)) +``` -- Version 0.1.2 - - Better error message on unnumbered headings - - Add Italian translations ([@porcaror](https://github.com/porcaror)) +![image](docs/images/kind-example.png) -- Version 0.1.1 - - Add Dutch translations ([@BroodjeKroepoek](https://github.com/BroodjeKroepoek)) - - Add French translations ([@MDLC01](https://github.com/MDLC01)) - - Fix size of default styles and make them breakable +For a full documentation of all functions check the [pdf-version](docs/readme.pdf) of this readme. diff --git a/docs/function-types.typ b/docs/function-types.typ new file mode 100644 index 0000000..601ffd1 --- /dev/null +++ b/docs/function-types.typ @@ -0,0 +1,51 @@ +#let theorem-kind = (link-to: 0, numbering: 0) + +/// The functions that actually create the theorems. +/// The default values of the arguments are set to the +/// values provided in @@theorem-kind() or @@default-theorems. +/// +/// - name (content, str): The name of the #ref-type("theorem"). +/// - link-to (label, selector, selector-function, none): Link the #ref-type("theorem") +/// to some other content. For #ref-type("label")s and #ref-type("selector")s the last +/// match before the #ref-type("theorem") is used. +/// - numbering (theorem-numbering-function, none): See #ref-type("theorem-numbering-function") +/// for more information. Can be set to #ref-type("none") for unnumbered #ref-type("theorem")s. +/// - body (content): +/// -> theorem +#let theorem-function(name: none, link-to: theorem-kind.link-to, numbering: theorem-kind.numbering, body) = 0 + +/// Create combined numberings from `theorem` and the content linked to it. +/// +/// There are two pre-defined #ref-type("theorem-numbering-function")s: @@numbering-concat() and @@numbering-proof(). +/// +/// - thm (theorem): The `theorem` for which the numbering should be generated. +/// See also @@get-theorem-parameters(). +/// - referenced (bool): This is false if +/// the numbering was requested from the `theorem` it belongs to. +/// Otherwise it is false. See @@numbering-proof() as an example. +/// -> content +#let theorem-numbering-function(thm, referenced) = 0 + +/// Defines how the #ref-type("theorem") will look. Use @@get-theorem-parameters() to get +/// all information stored in the #ref-type("theorem"). +/// +/// There are two pre-defined #ref-type("style-function")s: @@style-simple() and @@style-reversed(). +/// +/// - thm (theorem): +/// -> content +#let style-function(thm) = 0 + +/// Useful for more advanced queries. See @@last-heading() for an example. +/// +/// - loc (location): When used in `link-to` parameter +/// of some #ref-type("theorem") its #ref-type("location") +/// will be passed when resolving the link with @@resolve-link(). +/// -> content, none +#let selector-function(loc) = 0 + +/// A normal numbering function as described +/// in the #link("https://typst.app/docs/reference/meta/numbering/#parameters-numbering")[typst documentation]. +/// +/// - ..state (int): +/// -> content +#let numbering-function(..state) = 0 diff --git a/docs/images/basic-usage.png b/docs/images/basic-usage.png new file mode 100644 index 0000000..32dae4b Binary files /dev/null and b/docs/images/basic-usage.png differ diff --git a/docs/images/corollary-example.png b/docs/images/corollary-example.png new file mode 100644 index 0000000..cc0f373 Binary files /dev/null and b/docs/images/corollary-example.png differ diff --git a/docs/images/group-example.png b/docs/images/group-example.png new file mode 100644 index 0000000..2caa0fc Binary files /dev/null and b/docs/images/group-example.png differ diff --git a/docs/images/kind-example.png b/docs/images/kind-example.png new file mode 100644 index 0000000..8f7628e Binary files /dev/null and b/docs/images/kind-example.png differ diff --git a/docs/images/proof-example.png b/docs/images/proof-example.png new file mode 100644 index 0000000..6b6b4f2 Binary files /dev/null and b/docs/images/proof-example.png differ diff --git a/docs/images/remark-example.png b/docs/images/remark-example.png new file mode 100644 index 0000000..0d1d289 Binary files /dev/null and b/docs/images/remark-example.png differ diff --git a/docs/images/reset-example.png b/docs/images/reset-example.png new file mode 100644 index 0000000..d3c7c58 Binary files /dev/null and b/docs/images/reset-example.png differ diff --git a/docs/images/style-example.png b/docs/images/style-example.png new file mode 100644 index 0000000..66a9726 Binary files /dev/null and b/docs/images/style-example.png differ diff --git a/docs/readme.pdf b/docs/readme.pdf new file mode 100644 index 0000000..93f7493 Binary files /dev/null and b/docs/readme.pdf differ diff --git a/docs/readme.typ b/docs/readme.typ new file mode 100644 index 0000000..17891ea --- /dev/null +++ b/docs/readme.typ @@ -0,0 +1,444 @@ +#import "../src/export-lib.typ": * +#import "source-docs.typ": link-color, ref-type + +#let exported-functions = ( + default-theorems: default-theorems, + theorem-kind: theorem-kind, + theorem-rules: theorem-rules, + select-kind: select-kind, + select-group: select-group, + get-theorem-parameters: get-theorem-parameters, + reset-counter: reset-counter, + style-simple: style-simple +) + +#let export-code(name, code, setup, imports) = { + imports = "#import \"../../src/export-lib.typ\": " + imports.join(", ") + code = imports + "\n" + setup + "\n" + code + + [#metadata((code: code, name: name)) ] +} + +#let eval-raws(..raws, name, scope: (:), export-setup: "") = { + assert(raws.named().len() == 0) + let code = raws.pos().map(x => x.text + "\n").sum() + [ + #block( + stroke: gray + 1pt, + inset: 1em, + radius: 5pt, + breakable: false, + { + set heading(bookmarked: false) + eval(code, mode: "markup", scope: scope) + let (theorem, proof) = default-theorems() + reset-counter(theorem) + reset-counter(proof) + place(heading[]) // hack to reset heading-level to 0 + } + ) + ] + export-code(name, code, export-setup, scope.keys()) +} + +#let code-with-import(..imports, code: raw("")) = { + assert(imports.named().len() == 0) + + let import-statement = "#import \"@preview/lemmify:" + LEMMIFY-VERSION + "\": " + imports.pos().join(", ") + "\n" + if code.text != "" { "\n" } + code = raw(import-statement + code.text, lang: "typst", block: true) + + let scope = (:) + for i in imports.pos() { + scope.insert(i, exported-functions.at(i)) + } + + return (scope, code) +} + +#let export-only(text) = [ + #assert(type(text) == str) + #metadata(text) +] + +#let combine-spaces(seq) = { + let last-space = none + let result = () + for child in seq.children { + if child == [ ] and last-space != parbreak() { + last-space = child + } else if child.func() == parbreak { + last-space = child + } else if child.has("children") and child.children.len() == 0 { + // ignore + } else { + if last-space != none { + result.push(last-space) + last-space = none + } + result.push(child) + } + } + result +} + +#let content-to-markdown(con) = { + assert(type(con) == content) + if con.has("children") { // check for sequence + combine-spaces(con).map(content-to-markdown).join("") + } else if con.has("child") { // check for styled + content-to-markdown(con.child) + } else if con == [ ] { + "\n" + } else if con.func() == heading { + "#" * con.level + " " + content-to-markdown(con.body) + } else if con.func() == parbreak { + "\n\n" + } else if con.func() == text { + con.text + } else if con.func() == link { + if type(con.dest) == label { + content-to-markdown(con.body) + } else { + "[" + content-to-markdown(con.body) + "](" + con.dest + ")" + } + } else if con.func() == enum.item { + str(con.number) + ". " + content-to-markdown(con.body) + } else if con.func() == raw { + if con.has("block") and con.block { + assert(con.lang == "typst", message: "only typst code blocks expected") + "```" + con.lang + "\n" + con.text + "\n" + "```" + } else { + "`" + con.text + "`" + } + } else if con.func() == block { + if con.has("label") and con.label == { + // ignore + } else { + panic("block without ignore-content label") + } + } else if con.func() == metadata { + if con.has("label") and con.label == { + "" + con.value.code + "" + } else { + panic("metadata without generate-image label") + } + } else if con.func() == smartquote { + if con.double { + "\"" + } else { + "'" + } + } else { + panic("conversion to text not implemented for " + repr(con.func())) + } +} + +#let export(con) = { + con + export-only(content-to-markdown(con)) +} + +#let ref-function(name) = { + name = name + "()" + link(label(name), text(link-color, raw(name))) +} + +#export[ + #show raw.where(block: true): block.with(stroke: 1pt + gray, fill: gray.lighten(70%), inset: 1em, width: 100%, radius: 5pt) + + = lemmify + + Lemmify is a library for typesetting mathematical + theorems in typst. It aims to be easy to use while + trying to be as flexible and idiomatic as possible. + This means that the interface might change with updates to typst + (for example if user-defined element functions are introduced). + But no functionality should be lost. + + If you are encountering any bugs, have questions or are missing + features, feel free to open an issue on + #link("https://github.com/Marmare314/lemmify")[GitHub]. + + == Basic usage + + 1. Import lemmify: + + #let (basic-usage-scope, step1) = code-with-import("default-theorems", "select-kind") + + #step1 + + 2. Generate some common theorem kinds with pre-defined style: + + #let step2 =```typst + #let ( + theorem, lemma, corollary, + remark, proposition, example, + proof, theorem-rules + ) = default-theorems(lang: "en") + ``` + #step2 + + 3. Apply the generated style: + + #let step3 = ```typst + #show: theorem-rules + ``` + #step3 + + 4. Customize the theorems using show rules. For example, to add a block around proofs: + + #let step4 = ```typst + #show select-kind(proof): block.with( + breakable: true, + width: 100%, + fill: gray, + inset: 1em, + radius: 5pt + ) + ``` + #step4 + + 5. Create theorems, lemmas, and proofs: + + #let step5 = ```typst + #theorem(name: "Some theorem")[ + Theorem content goes here. + ] + + #theorem(numbering: none)[ + Another theorem. + ] + + #proof(link-to: )[ + Complicated proof. + ] + + @proof and @thm[theorem] + ``` + #step5 + + The result should now look something like this: + + #eval-raws( + step2, step3, step4, step5, + "basic-usage", + scope: basic-usage-scope, + export-setup: "#set page(width: 300pt, height: auto, margin: 10pt)" + ) + + == Examples + + By default theorems are reset on every heading. This can be changed with the + `max-reset-level` parameter of #ref-function("default-theorems"). This also + changes which heading levels will be used in the numbering. To not reset them + at all `max-reset-level` can be set to 0. + + #let reset-example = ``` + #let ( + theorem, theorem-rules + ) = default-theorems(max-reset-level: 2) + #show: theorem-rules + #set heading(numbering: "1.1") + + = Heading + #theorem(lorem(5)) + == Heading + #theorem(lorem(5)) + === Heading + #theorem(lorem(5)) + ``` + #let (reset-scope, reset-with-import) = code-with-import("default-theorems", code: reset-example) + #reset-with-import + + #eval-raws( + reset-example, + "reset-example", + scope: reset-scope, + export-setup: "#set page(width: 300pt, height: auto, margin: 10pt)" + ) + + Each theorem belongs to a group and every group shares one counter. The theorems created + by #ref-function("default-theorems") all belong to the same group, except for proofs. + You can create seperate groups by passing a group parameter to #ref-function("default-theorems"). + The next example shows how to create seperately numbered examples. + + #let group-example = ``` + #let (theorem, theorem-rules) = default-theorems() + #show: theorem-rules + #let ( + example, theorem-rules + ) = default-theorems(group: "example-group") + #show: theorem-rules + + #theorem(lorem(5)) + #example(lorem(5)) + #example(lorem(5)) + #theorem(lorem(5)) + ``` + #let (group-scope, group-with-import) = code-with-import("default-theorems", code: group-example) + #group-with-import + + #eval-raws( + group-example, + "group-example", + scope: group-scope, + export-setup: "#set page(width: 300pt, height: auto, margin: 10pt)" + ) + + The link-to parameter can be used to link theorems to other content. By default + theorems are linked to the last heading and proofs are linked to the last theorem. + This example shows how corallaries can be linked to the last theorem. + Note that it's fine to only apply the `theorem-rules` once here since both theorem-kinds belong to the same group. + + #let corollary-example = ``` + #let (theorem, theorem-rules) = default-theorems() + #let (corollary,) = default-theorems( + group: "corollary-group", + link-to: select-kind(theorem) + ) + #show: theorem-rules + #show select-kind(theorem): it => {it; reset-counter(corollary)} + + #theorem(lorem(5)) + #corollary(lorem(5)) + #corollary(lorem(5)) + #theorem(lorem(5)) + #corollary(lorem(5)) + ``` + + #let (corollary-scope, corollary-with-import) = code-with-import("default-theorems", "select-kind", "reset-counter", code: corollary-example) + #corollary-with-import + + #eval-raws( + corollary-example, + "corollary-example", + scope: corollary-scope, + export-setup: "#set page(width: 300pt, height: auto, margin: 10pt)" + ) + + The best and easiest way to change the look of theorems is to use show-rules. + The next example shows another way how the appearance of theorems can be changed. + + #let proof-example = ``` + #let ( + theorem, proof, theorem-rules + ) = default-theorems( + lang: "en", + style: style-simple.with(seperator: ". "), + proof-style: style-simple.with(kind-name-style: emph, seperator: ". ") + ) + #show: theorem-rules + + #theorem(lorem(5)) + #proof(lorem(5)) + ``` + #let (proof-scope, proof-with-import) = code-with-import("default-theorems", "style-simple", code: proof-example) + #proof-with-import + + #eval-raws( + proof-example, + "proof-example", + scope: proof-scope, + export-setup: "#set page(width: 300pt, height: auto, margin: 10pt)" + ) + + Doing the same thing to remarks is a bit more complicated since the style parameter applies to both theorems and remarks. + + #let remark-example = ``` + #let ( + theorem, theorem-rules + ) = default-theorems( + lang: "en", + style: style-simple.with(seperator: ". ") + ) + #let (remark,) = default-theorems( + style: style-simple.with(kind-name-style: emph, seperator: ". "), + numbering: none + ) + #show: theorem-rules + + #theorem(lorem(5)) + #remark(lorem(5)) + ``` + #let (remark-scope, remark-with-import) = code-with-import("default-theorems", "style-simple", code: remark-example) + #remark-with-import + + #eval-raws( + remark-example, + "remark-example", + scope: remark-scope, + export-setup: "#set page(width: 300pt, height: auto, margin: 10pt)" + ) + + If the pre-defined styles are not customizable enough you can also provide your own style. + + #let style-example = ``` + #let custom-style(thm) = { + let params = get-theorem-parameters(thm) + let number = (params.numbering)(thm, false) + block( + inset: .5em, + fill: gray, + { + params.kind-name + " " + number + if params.name != none { ": " + params.name } + } + ) + v(0pt, weak: true) + block( + width: 100%, + inset: 1em, + stroke: gray + 1pt, + params.body + ) + } + + #let ( + theorem, theorem-rules + ) = default-theorems(lang: "en", style: custom-style) + #show: theorem-rules + + #theorem(name: "Some theorem")[ + #lorem(40) + ] + ``` + + #let (style-scope, style-with-import) = code-with-import("default-theorems", "get-theorem-parameters", code: style-example) + #style-with-import + + #eval-raws( + style-example, + "style-example", + scope: style-scope, + export-setup: "#set page(width: 500pt, height: auto, margin: 10pt)" + ) + + There is one other way to create #ref-type("theorem-function")s: the + #ref-function("theorem-kind") function. It is used to create + the theorem-functions returned by #ref-function("default-theorems") + so it behaves almost the same. The only difference is that there is no + `max-reset-level` parameter and that no `theorem-rules` are returned. + A default rule which does not reset any theorem counters can be imported. + + #let kind-example = ``` + #let note = theorem-kind("Note") + #show: theorem-rules + + #note(lorem(5)) + ``` + + #let (kind-scope, kind-with-import) = code-with-import("theorem-kind", "theorem-rules", code: kind-example) + #kind-with-import + + #eval-raws( + kind-example, + "kind-example", + scope: kind-scope, + export-setup: "#set page(width: 500pt, height: auto, margin: 10pt)" + ) +] + +#export-only("\nFor a full documentation of all functions check the [pdf-version](docs/readme.pdf) of this readme.\n") + +#include "source-docs.typ" diff --git a/docs/source-docs.typ b/docs/source-docs.typ new file mode 100644 index 0000000..f5103d4 --- /dev/null +++ b/docs/source-docs.typ @@ -0,0 +1,126 @@ +#import "@preview/tidy:0.1.0": parse-module, show-module, styles + +#let custom-type-colors = ( + "theorem-function": rgb("#f9dfff"), + "theorem-numbering-function": rgb("#f9dfff"), + "numbering-function": rgb("#f9dfff"), + "style-function": rgb("#f9dfff"), + "selector-function": rgb("#f9dfff"), + "theorem": rgb("#fff173"), + "numbered": rgb("#bfb3ff") +) + +#let type-colors = ( + "content": rgb("#a6ebe6"), + "color": rgb("#a6ebe6"), + "str": rgb("#d1ffe2"), + "none": rgb("#ffcbc4"), + "auto": rgb("#ffcbc4"), + "bool": rgb("#ffedc1"), + "int": rgb("#e7d9ff"), + "float": rgb("#e7d9ff"), + "ratio": rgb("#e7d9ff"), + "length": rgb("#e7d9ff"), + "angle": rgb("#e7d9ff"), + "relative-length": rgb("#e7d9ff"), + "fraction": rgb("#e7d9ff"), + "symbol": rgb("#eff0f3"), + "array": rgb("#eff0f3"), + "dictionary": rgb("#eff0f3"), + "arguments": rgb("#eff0f3"), + "selector": rgb("#eff0f3"), + "module": rgb("#eff0f3"), + "stroke": rgb("#eff0f3"), + "function": rgb("#f9dfff"), + ..custom-type-colors +) + +#let get-type-color(type) = type-colors.at(type, default: rgb("#eff0f3")) + +#let link-color = blue.darken(50%) + +#let ref-type(type, color: link-color) = { + if type in custom-type-colors { + link(label(type + "()"), text(color, raw(type))) + } else { + raw(type) + } +} + +#let show-type(type) = { + h(2pt) + box(outset: 2pt, fill: get-type-color(type), radius: 2pt, text(black, ref-type(type, color: black))) + h(2pt) +} + +#let style = ( + show-type: show-type, + show-outline: styles.default.show-outline, + show-function: styles.default.show-function, + show-parameter-list: styles.default.show-parameter-list, + show-parameter-block: styles.default.show-parameter-block +) + +#let parse-module-params = ( + scope: ( + ref-type: ref-type, + ) +) + +#let show-module-params = ( + style: style, + show-module-name: false, + show-outline: false, + sort-functions: none, +) + +#let lib-parsed = parse-module(read("../src/lib.typ"), ..parse-module-params) +#let reset-counter-parsed = parse-module(read("../src/reset-counter.typ"), ..parse-module-params) +#let selectors-parsed = parse-module(read("../src/selectors.typ"), ..parse-module-params) +#let styles-parsed = parse-module(read("../src/styles.typ"), ..parse-module-params) +#let numbered-parsed = parse-module(read("../src/numbered.typ"), ..parse-module-params) +#let theorem-parsed = parse-module(read("../src/theorem.typ"), ..parse-module-params) +#let function-types-parsed = parse-module(read("function-types.typ"), ..parse-module-params) + +#show link: text.with(link-color) += Documentation + +#show-module(lib-parsed, ..show-module-params) + +== Function types + +#show-module(function-types-parsed, ..show-module-params) + +== theorem +#label("theorem()") + +A #ref-type("theorem") is a #ref-type("figure") with some additional +information stored in one of its parameters. + +#show-module(theorem-parsed, ..show-module-params) + +== numbered +#label("numbered()") + +A #ref-type("numbered") is a #ref-type("heading"), #ref-type("page"), +#ref-type("math.equation") or #ref-type("figure") that is already embedded +in the document (that means it was obtained by a query). The `numbering` +also has to be different from #ref-type("none"). + +#show-module(numbered-parsed, ..show-module-params) + +== Styles + +#show-module(styles-parsed, ..show-module-params) + +== Selectors + +The selectors can be used in show-rules to +customize the #ref-type("theorem")s styling as well as +with the `link-to` parameter. + +#show-module(selectors-parsed, ..show-module-params) + +== Resetting counters + +#show-module(reset-counter-parsed, ..show-module-params) diff --git a/examples/basic_usage.pdf b/examples/basic_usage.pdf deleted file mode 100644 index bf82fb7..0000000 Binary files a/examples/basic_usage.pdf and /dev/null differ diff --git a/examples/basic_usage.typ b/examples/basic_usage.typ deleted file mode 100644 index 99b2a8b..0000000 --- a/examples/basic_usage.typ +++ /dev/null @@ -1,40 +0,0 @@ -#import "../src/lib.typ": * - -#let my-styling = ( - thm-styling: thm-style-reversed -) - -#let (theorem, proof, rules: thm-rules) = default-theorems("thm-group", ..my-styling) -#show: thm-rules - -#let (note, rules) = new-theorems("thm-group", ("note": text(red)[Note]), ..my-styling) -#show: rules - -#set heading(numbering: "1.1") -= Section -#theorem(name: "Some theorem")[ - -] -#proof[ - Complicated proof. -] - -== Subsection -#theorem(numbering: none)[ - $e^(i pi) = -1$ -] - -#theorem(name: "Some related theorem")[ -] - -= Section -#theorem(name: "Some similar theorem")[ - -] -#proof[ - @proof is similar enough that this is clear. - Or use @related[theorem]. -] -#note[ - Test -] diff --git a/examples/readme.pdf b/examples/readme.pdf deleted file mode 100644 index 10514c0..0000000 Binary files a/examples/readme.pdf and /dev/null differ diff --git a/examples/readme.typ b/examples/readme.typ deleted file mode 100644 index 63b72c7..0000000 --- a/examples/readme.typ +++ /dev/null @@ -1,119 +0,0 @@ -#import "../src/lib.typ": * - -#set heading(numbering: "1.1") - -= Basic usage -#[ - #let ( - theorem, lemma, corollary, - remark, proposition, example, - proof, rules: thm-rules - ) = default-theorems("thm-group", lang: "en") - - #show: thm-rules - - #show thm-selector("thm-group", subgroup: "proof"): it => box( - it, - stroke: red + 1pt, - inset: 1em - ) - - #theorem(name: "Some theorem")[ - Theorem content goes here. - ] - - #proof[ - Complicated proof. - ] - - @proof and @thm[theorem] -] - -= Useful examples -#[ - // Reset settings - #let (theorem, rules) = default-theorems("thm-group", max-reset-level: 0) - #show: rules - - == Sect - #theorem[Test] - - == Sect - #theorem[Test] - - // Unnumbered theorems - #theorem(numbering: none)[Test] - - #let theorem = theorem.with(numbering: none) - #theorem[Test] - - // New theorem type - #let (note, rules) = new-theorems("thm-group", ("note": text(red)[Note])) - #show: rules - - #note[Test] - - // Styling dict - #let my-styling = ( - thm-styling: thm-style-simple, - thm-numbering: thm-numbering-linear, - ref-styling: thm-ref-style-simple - ) - - #let (note, rules) = new-theorems("thm-group", ("note": "Note"), ..my-styling) - #show: rules - - #note[Test] -] - -= Independent numbering - -#[ - #let ( - theorem, proof, - rules: thm-rules-a - ) = default-theorems("thm-group-a") - #let ( - definition, - rules: thm-rules-b - ) = default-theorems("thm-group-b") - - #show: thm-rules-a - #show: thm-rules-b - - #theorem[Test] - #theorem[Test] - #definition[Test] -] - -= Full example - -#[ - #let my-thm-style( - thm-type, name, number, body - ) = grid( - columns: (1fr, 3fr), - column-gutter: 1em, - stack(spacing: .5em, strong(thm-type), number, emph(name)), - body - ) - - #let my-styling = ( - thm-styling: my-thm-style - ) - - #let ( - theorem, rules - ) = default-theorems("thm-group", lang: "en", ..my-styling) - #show: rules - #show thm-selector("thm-group"): box.with(inset: 1em) - - #lorem(20) - #theorem[ - #lorem(40) - ] - #lorem(20) - #theorem(name: "Some theorem")[ - #lorem(30) - ] -] diff --git a/src/export-lib.typ b/src/export-lib.typ new file mode 100644 index 0000000..3354b1a --- /dev/null +++ b/src/export-lib.typ @@ -0,0 +1,65 @@ +#let LEMMIFY-VERSION = "0.2.0" + +#let theorem-kind +#let theorem-rules +#let default-theorems +#{ + import "lib.typ" + theorem-kind = lib.theorem-kind + theorem-rules = lib.theorem-rules + default-theorems = lib.default-theorems +} + +#let numbering-concat +#let numbering-proof +#let style-reversed +#let style-simple +#let qed-box +#{ + import "styles.typ" + numbering-concat = styles.numbering-concat + numbering-proof = styles.numbering-proof + style-reversed = styles.style-reversed + style-simple = styles.style-simple + qed-box = styles.qed-box +} + +#let reset-counter +#let reset-counter-heading +#{ + let tmp-reset-counter + { + import "reset-counter.typ" + reset-counter-heading = reset-counter.reset-counter-heading + tmp-reset-counter = reset-counter.reset-counter + } + reset-counter = tmp-reset-counter +} + +#let last-heading +#let select-group +#let select-kind +#{ + import "selectors.typ" + last-heading = selectors.last-heading + select-group = selectors.select-group + select-kind = selectors.select-kind +} + +#let is-theorem +#let get-theorem-parameters +#let resolve-link +#{ + import "theorem.typ" + is-theorem = theorem.is-theorem + get-theorem-parameters = theorem.get-theorem-parameters + resolve-link = theorem.resolve-link +} + +#let is-numbered +#let display-numbered +#{ + import "numbered.typ" + is-numbered = numbered.is-numbered + display-numbered = numbered.display-numbered +} diff --git a/src/lib.typ b/src/lib.typ index 39eb411..ac1a757 100644 --- a/src/lib.typ +++ b/src/lib.typ @@ -1,145 +1,175 @@ -#import "util.typ": * -#import "styles.typ": * -#import "translations.typ": * +#import "theorem.typ": create-theorem, is-theorem, get-theorem-parameters +#import "styles.typ": numbering-concat, style-simple, numbering-proof, qed-box +#import "translations.typ": get-translation +#import "selectors.typ": last-heading +#import "reset-counter.typ": concat-fold, reset-counter-heading +#import "types.typ": assert-type, None -// Transform theorem function into -// proof function. That is decrease -// the numbering by one. -#let use-proof-numbering(theorem-func) = { - let numb = n => numbering(theorem-func()[].numbering, n - 1) - return theorem-func.with(numbering: numb) -} - -// Creates a selector for all theorems of -// the specified group. If subgroup is -// specified, only the theorems belonging to it -// will be selected. -#let thm-selector(group, subgroup: none) = { - if subgroup == none { - figure.where(kind: group) - } else { - figure.where(kind: group, supplement: [#subgroup]) - } -} +#let LEMMIFY-DEFAULT-THEOREM-GROUP = "LEMMIFY-DEFAULT-THEOREM-GROUP" +#let LEMMIFY-DEFAULT-PROOF-GROUP = "LEMMIFY-DEFAULT-PROOF-GROUP" -// Reset theorem group counter to zero. -#let thm-reset-counter(group) = { - counter(thm-selector(group)).update(c => 0) -} - -// Reset counter of specified theorem group -// on headings of the specified level -#let thm-reset-counter-heading-at( - group, - level, - content +/// Creates a new #ref-type("theorem-function"). +/// +/// - kind-name (str): The name of the theorem kind. It also acts +/// as an identifier together with `group` when using `select-kind`, +/// so it should be unique. +/// - group (str): The group identifier. Each theorem group shares one counter. +/// - link-to (label, selector, selector-function, none): This parameter sets +/// what the #ref-type("theorem")s created by the #ref-type("theorem-function") will be linked to +/// by default. +/// - numbering (theorem-numbering-function, none): Specify a default value for +/// the `numbering` parameter of the #ref-type("theorem-function"). +/// - subnumbering (numbering-function, str, none): The subnumbering is +/// needed to convert the #ref-type("theorem")s counter to content, +/// which is then used in the #ref-type("theorem-numbering-function"). +/// - style (style-function): Specifies how the #ref-type("theorem")s will look. This will only be +/// visible once the @@theorem-rules() have been applied. +/// -> theorem-function +#let theorem-kind( + kind-name, + group: LEMMIFY-DEFAULT-THEOREM-GROUP, + link-to: last-heading, + numbering: numbering-concat, + subnumbering: "1", + style: style-simple ) = { - show heading.where(level: level): it => { - thm-reset-counter(group) - it - } - content -} + assert-type(kind-name, "kind-name", str) + assert-type(group, "group", str) + assert-type(link-to, "link-to", label, selector, function, None) + assert-type(numbering, "numbering", function, None) + assert-type(subnumbering, "subnumbering", function, str, None) + assert-type(style, "style", function) -// Reset counter of specified theorem group -// on headings with at most the specified level. -#let thm-reset-counter-heading( - group, - max-level, - content -) = { - let rules = range(1, max-level + 1).map( - k => thm-reset-counter-heading-at.with(group, k) + return ( + name: none, + link-to: link-to, + numbering: numbering, + body + ) => create-theorem( + name, + kind-name, + group, + link-to, + numbering, + subnumbering, + style, + body ) - show: concat-fold(rules) - content } -// Creates new theorem functions and -// a styling rule from a mapping (subgroup: args) -// and the style parameters. -// The args of each subgroup will be passed -// into thm-styling and ref-styling. -#let new-theorems( - group, - subgroup-map, - thm-styling: thm-style-simple, - thm-numbering: thm-numbering-heading, - ref-styling: thm-ref-style-simple, - ref-numbering: none -) = { - let helper-rule(subgroup, content) = { - show thm-selector( - group, - subgroup: subgroup - ): thm-style.with( - thm-styling.with(subgroup-map.at(subgroup)), - thm-numbering - ) - - let numbering = if ref-numbering != none { - ref-numbering - } else { - thm-numbering +/// Apply the style of every #ref-type("theorem") and handle references to #ref-type("theorem")s. +/// +/// - content (content): +/// -> content +#let theorem-rules(content) = { + show figure: it => if is-theorem(it) { + let params = get-theorem-parameters(it) + if params.numbering == none { + it.counter.update(n => n - 1) } - - show: thm-ref-style.with( - group, - subgroups: subgroup, - ref-styling.with(subgroup-map.at(subgroup), numbering) - ) - content - } - - let rules(content) = { - show: concat-fold(subgroup-map.keys().map(sg => helper-rule.with(sg))) - content + (params.style)(it) + } else { + it } + show ref: it => { + if it.element == none or not is-theorem(it.element) { + return it + } - let result = (:) - for (subgroup, _) in subgroup-map { - result.insert(subgroup, new-thm-func(group, subgroup)) + let params = get-theorem-parameters(it.element) + link(it.target, { + if it.citation.supplement != none { it.citation.supplement } else { params.kind-name } + " " + (params.numbering)(it.element, true) + }) } - result.insert("rules", rules) - - return result + content } -// Create a default set of theorems based -// on the language and given styling. +/// Generate a few common theorem kinds in the specified language. +/// +/// Returns a dictionary of the form +/// `(theorem, lemma, corollary, remark, proposition, example, definition, proof, theorem-rules)`. +/// The `theorem-rules` can be applied using a show statement. Additionally to showing the theorem +/// styles and handling references they also reset the counters according to `max-reset-level` +/// +/// This function accepts all parameters of @@theorem-kind() once for proofs and +/// once for all kinds except for proofs. +/// +/// - group (str): +/// - proof-group (str): +/// - lang (str): The language in which the theorem kinds are generated. +/// - style (style-function): +/// - proof-style (style-function): +/// - numbering (theorem-numbering-function, none): +/// - proof-numbering (theorem-numbering-function, none): +/// - link-to (label, selector, selector-function, none): +/// - proof-link-to (label, selector, selector-function, none): +/// - subnumbering (numbering-function, str, none): +/// - max-reset-level (int, none): If it is not none the theorem counter will +/// be reset on headings below `max-reset-level`. +/// And if `link-to` is set to `last-heading` +/// higher levels will not be displayed in the numbering. +/// -> dictionary #let default-theorems( - group, + group: LEMMIFY-DEFAULT-THEOREM-GROUP, + proof-group: LEMMIFY-DEFAULT-PROOF-GROUP, lang: "en", - thm-styling: thm-style-simple, - proof-styling: thm-style-proof, - thm-numbering: thm-numbering-heading, - ref-styling: thm-ref-style-simple, - max-reset-level: 2 + style: style-simple, + proof-style: style-simple.with(qed: qed-box), + numbering: numbering-concat, + proof-numbering: numbering-proof, + link-to: last-heading, + proof-link-to: none, + subnumbering: "1", + max-reset-level: none ) = { - let (proof, ..subgroup-map) = translations.at(lang) + assert-type(group, "group", str) + assert-type(proof-group, "proof-group", str) + assert-type(lang, "lang", str) + assert-type(style, "style", function) + assert-type(proof-style, "proof-style", function) + assert-type(numbering, "numbering", function, None) + assert-type(proof-numbering, "proof-numbering", function, None) + assert-type(link-to, "link-to", label, selector, function, None) + assert-type(proof-link-to, "proof-link-to", label, selector, function, None) + assert-type(subnumbering, "subnumbering", function, str, None) + assert-type(max-reset-level, "max-reset-level", int, None) - let (rules: rules-theorems, ..theorems) = new-theorems( - group, - subgroup-map, - thm-styling: thm-styling, - thm-numbering: thm-numbering - ) + let (proof: proof-translation, ..other-kinds) = get-translation(lang) - let (rules: rules-proof, proof) = new-theorems( - group, - (proof: translations.at(lang).at("proof")), - thm-styling: proof-styling, - thm-numbering: thm-numbering-proof, - ref-numbering: thm-numbering - ) + if link-to == last-heading { + link-to = last-heading.with(max-level: max-reset-level) + } + let theorems = (:) + for (kind, translation) in other-kinds { + theorems.insert(kind, theorem-kind( + translation, + group: group, + numbering: numbering, + subnumbering: subnumbering, + style: style, + link-to: link-to + )) + } + + theorems.insert("proof", theorem-kind( + proof-translation, + group: proof-group, + numbering: proof-numbering, + subnumbering: subnumbering, + style: proof-style, + link-to: proof-link-to + )) + + let rules = concat-fold(( + theorem-rules, + reset-counter-heading.with(theorems.theorem, max-reset-level), + reset-counter-heading.with(theorems.proof, max-reset-level) + )) return ( ..theorems, - proof: use-proof-numbering(proof), - rules: concat-fold(( - thm-reset-counter-heading.with(group, max-reset-level), - rules-theorems, - rules-proof - )) + theorem-rules: rules ) } diff --git a/src/numbered.typ b/src/numbered.typ new file mode 100644 index 0000000..dd74c3d --- /dev/null +++ b/src/numbered.typ @@ -0,0 +1,76 @@ +#import "theorem.typ": is-theorem, get-theorem-parameters + +#let is-countable(c) = { + return ( + type(c) == content + and c.func() in (heading, page, math.equation, figure) + ) +} + +#let assert-countable(arg, arg-name) = { + assert( + is-countable(arg), + message: "expected " + arg-name + "to be one of heading, page, math.equation or figure, but got " + str(type(arg)) + ) +} + +#let get-countable-parameters(c) = { + assert-countable(c, "c") + + let counter = if c.func() == heading { + counter(heading) + } else if c.func() == page { + counter(page) + } else if c.func() == math.equation { + counter(math.equation) + } else if c.func() == figure { + c.counter + } + + let numbering = if is-theorem(c) { + get-theorem-parameters(c).subnumbering + } else { + if c.has("numbering") { + c.numbering + } else { + none + } + } + + return ( + numbering: numbering, + counter: counter, + location: c.location() + ) +} + +/// Check if argument is #ref-type("numbered"). +/// +/// - n (any): +/// -> bool +#let is-numbered(n) = { + if is-countable(n) { + let params = get-countable-parameters(n) + return params.numbering != none + } + return false +} + +#let assert-numbered(arg, arg-name) = { + assert( + is-numbered(arg), + message: "expected " + arg-name + " to be numbered, but got " + str(type(arg)) + ) +} + +/// Display the numbering of the argument +/// at its location. +/// +/// - n (numbered): +/// -> content +#let display-numbered(n) = { + assert-numbered(n, "n") + + let params = get-countable-parameters(n) + numbering(params.numbering, ..params.counter.at(params.location)) +} diff --git a/src/reset-counter.typ b/src/reset-counter.typ new file mode 100644 index 0000000..6048c72 --- /dev/null +++ b/src/reset-counter.typ @@ -0,0 +1,49 @@ +#import "selectors.typ": select-group +#import "types.typ": assert-type, None + +// Create a concatenated function from +// a list of functions (with one argument) +// starting with the last function: +// concat-fold((f1, f2, f3))(x) = f1(f2(f3(x))) +#let concat-fold(functions) = { + functions.fold((c => c), (f, g) => (c => f(g(c)))) +} + +/// Reset theorem group counter to zero. +/// The result needs to be added to the document. +/// +/// - thm-func (theorem-function): The group is obtained from this argument. +/// -> content +#let reset-counter(thm-func) = { + assert-type(thm-func, "thm-func", function) + return counter(select-group(thm-func)).update(c => 0) +} + +/// Reset counter of theorem group +/// on headings with at most the specified level. +/// +/// - thm-func (theorem-function): The group is obtained from this argument. +/// - max-level (int, none): Should be at least 1. +/// - content (content): +/// -> content +#let reset-counter-heading( + thm-func, + max-level, + content +) = { + assert-type(thm-func, "thm-func", function) + assert-type(max-level, "max-level", int, None) + if max-level != none { assert(max-level >= 0, message: "max-level should be at least 0") } + + if max-level == none { + show heading: it => {reset-counter(thm-func); it} + content + } else { + let rules = range(1, max-level + 1).map(k => content => { + show heading.where(level: k): it => {reset-counter(thm-func); it} + content + }) + show: concat-fold(rules) + content + } +} diff --git a/src/selectors.typ b/src/selectors.typ new file mode 100644 index 0000000..aacad00 --- /dev/null +++ b/src/selectors.typ @@ -0,0 +1,71 @@ +#import "types.typ": assert-type, None +#import "theorem.typ": get-theorem-parameters + +/// Selector-function which selects the last heading. +/// +/// - ignore-unnumbered (bool): Use the last heading which is numbered. +/// - max-level (int, none): Do not select headings above this level. +/// - loc (location): +/// -> heading, none +#let last-heading( + ignore-unnumbered: false, + max-level: none, + loc +) = { + assert-type(ignore-unnumbered, "ignore-unnumbered", bool) + assert-type(max-level, "max-level", int, None) + if max-level != none { assert(max-level >= 0, message: "max-level should be at least 0") } + assert-type(loc, "loc", location) + + let sel = if max-level == none { + selector(heading) + } else if max-level > 0 { + let s = heading.where(level: 1) + for i in range(2, max-level + 1) { + s = s.or(heading.where(level: i)) + } + s + } else { + heading.where(level: 0) // pretty much impossible + } + + let headings = query(sel.before(loc), loc) + if headings.len() == 0 { + return none + } + if ignore-unnumbered { + let current-level = headings.last().level + for h in headings.rev() { + if h.level <= current-level and h.numbering != none { + return h + } + } + } else { + return headings.last() + } +} + +/// Generate selector that selects all +/// theorems of the same group as the +/// argument. +/// +/// - thm-func (theorem-function): +/// -> selector +#let select-group(thm-func) = { + assert-type(thm-func, "thm-func", function) + let params = get-theorem-parameters(thm-func[]) + return figure.where(kind: params.group) +} + +/// Generate selector that selects only +/// theorems that were create from +/// the #ref-type("theorem-function"). +/// +/// - thm-func (theorem-function): +/// -> selector +#let select-kind(thm-func) = { + assert-type(thm-func, "thm-func", function) + + let params = get-theorem-parameters(thm-func[]) + return figure.where(kind: params.group, supplement: [#params.kind-name]) +} diff --git a/src/styles.typ b/src/styles.typ index cc4c02b..47491c9 100644 --- a/src/styles.typ +++ b/src/styles.typ @@ -1,114 +1,120 @@ -#import "util.typ": * +#import "theorem.typ": resolve-link, get-theorem-parameters, is-theorem +#import "numbered.typ": display-numbered, is-numbered +#import "types.typ": assert-type, None -// Numbering function which combines -// heading number and theorem number -// with a dot: 1.1 and 2 -> 1.1.2 -#let thm-numbering-heading(fig, max-heading-level: none) = { - if fig.numbering != none { - let heading-counter = display-heading-counter-at(fig.location(), max-heading-level) - if type(heading-counter) == str and heading-counter.ends-with(".") { - heading-counter - } else { - heading-counter - "." - } - numbering(fig.numbering, ..fig.counter.at(fig.location())) - } -} - -// Numbering function which only -// returns the theorem number. -#let thm-numbering-linear(fig) = { - if fig.numbering != none { - numbering(fig.numbering, ..fig.counter.at(fig.location())) +/// If the linked content is numbered combine it with the numbering +/// of the #ref-type("theorem"). +/// +/// - thm (theorem): +/// - referenced (bool): +/// - seperator (content, str): The sepeartor is put between both numberings. +#let numbering-concat(thm, referenced, seperator: ".") = { + assert-type(seperator, "seperator", content, str) + let linked = resolve-link(thm) + if linked != none and is-numbered(linked) { + display-numbered(linked) + seperator } + display-numbered(thm) } -// Numbering function which takes -// the theorem number of the last -// theorem, but does not return it. -#let thm-numbering-proof(fig) = { - if fig.numbering != none { - fig.counter.update(n => n - 1) +/// Copy the numbering of a linked #ref-type("theorem") if referenced. +/// Otherwise no numbering is returned. +/// +/// - thm (theorem): +/// - referenced (bool): +#let numbering-proof(thm, referenced) = { + let linked = resolve-link(thm) + if referenced and linked != none { + assert(is-theorem(linked), message: "can only link proof to theorem") + let params = get-theorem-parameters(linked) + (params.numbering)(linked, true) } } -// Simple theorem style: -// thm-type n (name) body -#let thm-style-simple( - thm-type, - name, - number, - body -) = block(width: 100%, breakable: true)[#{ - strong(thm-type) + " " - if number != none { - strong(number) + " " - } - - if name != none { - emph[(#name)] + " " - } - " " + body -}] - -// Reversed theorem style: -// n thm-type (name) body -#let thm-style-reversed( - thm-type, - name, - number, - body -) = block(width: 100%, breakable: true)[#{ - if number != none { - strong(number) + " " - } - strong(thm-type) + " " - - if name != none { - emph[(#name)] + " " - } - " " + body -}] +/// A box for convenience. (Not a function but a constant.) +#let qed-box = box(scale(160%, origin: bottom + right, sym.square.stroked)) -// Simple proof style: -// thm-type n (name) body □ -#let thm-style-proof( - thm-type, - name, - number, - body -) = block(width: 100%, breakable: true)[#{ - strong(thm-type) + " " - if number != none { - strong(number) + " " - } +/// Simple theorem style. The theorem gets represented as a breakable block of the form +/// `kind-name-style(kind-name) number-style(numbering) name-style(name) seperator body`. +/// +/// - thm (theorem): +/// - kind-name-style (function): A function `str -> content` to change the look of the `kind-name`. +/// - number-style (function): A function `content -> content` to change the look of the generated numbering. +/// - name-style (function): A function `content -> content` to change the look of the `name`. +/// - seperator (content, str): How to seperate the theorem header and its body. +/// - qed (content, none): Select what content to show at the end of the theorem. +#let style-simple( + thm, + kind-name-style: strong, + number-style: strong, + name-style: name => emph("(" + name + ")"), + seperator: " ", + qed: none +) = { + assert-type(kind-name-style, "kind-name-style", function) + assert-type(number-style, "number-style", function) + assert-type(name-style, "name-style", function) + assert-type(seperator, "seperator", content, str) + assert-type(qed, "qed", content, None) - if name != none { - emph[(#name)] + " " - } - " " + body + h(1fr) + $square$ -}] + let params = get-theorem-parameters(thm) + block(width: 100%, breakable: true, { + kind-name-style(params.kind-name) + if params.numbering != none { + " " + number-style((params.numbering)(thm, false)) + } + if params.name != none { + " " + name-style(params.name) + } + seperator + params.body + if qed != none { + h(1fr) + qed + } + }) +} -// Basic theorem reference style: -// @thm -> thm-type n -// @thm[X] -> X n -// where n is the numbering specified -// by the numbering function -#let thm-ref-style-simple( - thm-type, - thm-numbering, - ref -) = link(ref.target, box[#{ - assert( - ref.element.numbering != none, - message: "cannot reference theorem without numbering" - ) +/// Reverses numbering and `kind-name`, otherwise the same as @@style-simple(). +/// +/// - thm (theorem): +/// - kind-name-style (function): +/// - number-style (function): +/// - name-style (function): +/// - seperator (content, str): +/// - qed (content, none): +#let style-reversed( + thm, + kind-name-style: strong, + number-style: strong, + name-style: name => emph("(" + name + ")"), + seperator: " ", + qed: none +) = { + assert-type(kind-name-style, "kind-name-style", function) + assert-type(number-style, "number-style", function) + assert-type(name-style, "name-style", function) + assert-type(seperator, "seperator", content, str) + assert-type(qed, "qed", content, None) - if ref.citation.supplement != none { - ref.citation.supplement - } else { - thm-type - } - " " + thm-numbering(ref.element) -}]) + let params = get-theorem-parameters(thm) + block(width: 100%, breakable: true, { + if params.numbering != none { + strong((params.numbering)(thm, false)) + " " + } + strong(params.kind-name) + if params.name != none { + emph(" (" + params.name + ")") + } + " " + params.body + if qed != none { + h(1fr) + qed + } + }) +} diff --git a/src/theorem.typ b/src/theorem.typ new file mode 100644 index 0000000..4a33b91 --- /dev/null +++ b/src/theorem.typ @@ -0,0 +1,99 @@ +#import "types.typ": assert-type, None + +#let LEMMIFY-THEOREM-ID() = 0 + +#let create-theorem( + name, + kind-name, + group, + link-to, + numbering, + subnumbering, + style, + body +) = { + assert-type(name, "name", str, content, None) + assert-type(kind-name, "kind-name", str) + assert-type(group, "group", str) + assert-type(link-to, "link-to", label, selector, function, None) + assert-type(numbering, "numbering", function, None) + assert-type(subnumbering, "subnumbering", str, function, None) + assert-type(style, "style", function) + assert-type(body, "body", str, content) + + return figure( + body, + caption: name, + kind: group, + supplement: kind-name, + numbering: (..) => ( + type: LEMMIFY-THEOREM-ID, + link-to: link-to, + numbering: numbering, + subnumbering: subnumbering, + style: style + ) + ) +} + +/// Check if argument is #ref-type("theorem"). +/// +/// - c (any): +/// -> bool +#let is-theorem(c) = { + if ( + type(c) == content + and c.func() == figure + and type(c.numbering) == function + and type((c.numbering)()) == dictionary // TODO: make sure this cannot fail + ) { + let res = (c.numbering)() + return "type" in res and res.type == LEMMIFY-THEOREM-ID + } else { + return false + } +} + +#let assert-theorem(arg, arg-name) = { + assert( + is-theorem(arg), + message: "expected " + arg-name + " to be of type theorem, but got " + str(type(arg)) + ) +} + +/// Extract theorem parameters from figure. +/// Returns a #ref-type("dictionary") of the form +/// `(body, group, kind-name, name, link-to, numbering, subnumbering, style)`. +/// +/// - thm (theorem): +/// -> dictionary +#let get-theorem-parameters(thm) = { + assert-theorem(thm, "thm") + let (type, ..hidden-params) = (thm.numbering)() + return ( + body: thm.body, + group: thm.kind, + kind-name: thm.supplement.text, + name: if thm.caption != none { thm.caption.body }, + ..hidden-params + ) +} + +/// Return the #ref-type("content") that is linked +/// to the #ref-type("theorem"). +/// +/// - thm (theorem): +/// -> content +#let resolve-link(thm) = { + let (link-to,) = get-theorem-parameters(thm) + if type(link-to) == label or type(link-to) == selector { + let res = query(selector(link-to).before(thm.location(), inclusive: false), thm.location()) + if res.len() > 0 { + return res.last() + } + } else if type(link-to) == function { + return link-to(thm.location()) + } else { + return none + } +} diff --git a/src/translations.typ b/src/translations.typ index c13d1fa..2a1f2bd 100644 --- a/src/translations.typ +++ b/src/translations.typ @@ -1,3 +1,5 @@ +#import "types.typ": assert-type + #let translations = ( "en": ( "theorem": "Theorem", @@ -60,3 +62,12 @@ "proof": "Доказательство" ) ) + +#let get-translation(lang) = { + assert-type(lang, "lang", str) + if lang in translations { + return translations.at(lang) + } else { + panic("no translation available for the specified language") + } +} diff --git a/src/types.typ b/src/types.typ new file mode 100644 index 0000000..4dd9f92 --- /dev/null +++ b/src/types.typ @@ -0,0 +1,37 @@ +#let None = type(none) + +#let check-type-args(types) = { + assert(types.named().len() == 0, message: "expected no named arguments") + assert(types.pos().len() > 0, message: "expected at least one argument") + for t in types.pos() { + assert(type(t) == type, message: "expected only type arguments, but got " + str(type(t))) + } +} + +#let check-type(arg, ..types) = { + check-type-args(types) + return type(arg) in types.pos() +} + +#let types-to-string(..types) = { + check-type-args(types) + return types.pos().map(str).join(", ", last: " or ") +} + +#let assert-type-error-msg(arg, arg-name, ..types) = { + check-type-args(types) + let s = if types.pos().len() == 1 { + " to be of type " + } else { + " to be one of " + } + return "expected " + arg-name + s + types-to-string(..types) + ", but got " + str(type(arg)) +} + +#let assert-type(arg, arg-name, ..types) = { + check-type-args(types) + assert( + check-type(arg, ..types), + message: assert-type-error-msg(arg, arg-name, ..types) + ) +} diff --git a/src/util.typ b/src/util.typ deleted file mode 100644 index 00eab5c..0000000 --- a/src/util.typ +++ /dev/null @@ -1,98 +0,0 @@ -#let new-thm-func( - group, - subgroup, - numbering: "1" -) = { - return (name: none, numbering: numbering, content) => { - figure( - content, - caption: name, - kind: group, - supplement: subgroup, - numbering: numbering - ) - } -} - -// Applies theorem styling and theorem -// numbering functions to theorem. -#let thm-style( - thm-styling, - thm-numbering, - fig -) = { - thm-styling( - if fig.has("caption") and fig.caption != none { fig.caption.body }, - thm-numbering(fig), - fig.body - ) -} - -// Applies reference styling to the -// theorems belonging to the specified -// group/subgroups. -#let thm-ref-style( - group, - subgroups: none, - ref-styling, - content -) = { - show ref: it => { - if it.element == none { - return it - } - if it.element.func() != figure { - return it - } - if it.element.kind != group { - return it - } - - let refd-subgroup = it.element.supplement.text - if subgroups == none { - ref-styling(it) - } else if subgroups == refd-subgroup { - ref-styling(it) - } else if type(subgroups) == "array" and subgroups.contains(refd-subgroup) { - ref-styling(it) - } else { - it - } - } - content -} - -// Utility function to display a counter -// at the given position. -#let display-heading-counter-at(loc, max-heading-level) = { - let locations = query(selector(heading).before(loc), loc) - if locations.len() == 0 { - [0] - } else { - let numb = query(selector(heading).before(loc), loc).last().numbering - if numb != none { - let c = counter(heading).at(loc) - if max-heading-level != none and c.len() > max-heading-level { - c = c.slice(0, max-heading-level) - } - numbering(numb, ..c) - } else { - let current-level = locations.last().level - for h in locations.rev() { - if h.level < current-level { - display-heading-counter-at(h.location(), max-heading-level) - return - } - } - panic("No numbering set for headings. Try setting the heading numbering or use a different thm-numbering") - } - } -} - -// Create a concatenated function from -// a list of functions (with one argument) -// starting with the last function: -// concat-fold((f1, f2, fn))(x) = f1(f2(f3(x))) -#let concat-fold(functions) = { - functions.fold((c => c), (f, g) => (c => f(g(c)))) -} diff --git a/tests/basic.typ b/tests/basic.typ new file mode 100644 index 0000000..48e981c --- /dev/null +++ b/tests/basic.typ @@ -0,0 +1,35 @@ +#import "../src/export-lib.typ": default-theorems + +#let (theorem, proof, theorem-rules) = default-theorems( + max-reset-level: 2 +) +#set page(width: 500pt, height: auto, margin: 10pt) +#show: theorem-rules + +#set heading(numbering: "1.1") += Heading + +#theorem[Short Body] +#theorem(numbering: none, lorem(200)) +#theorem(numbering: none, name: "Name")[$ e^(i pi) = -1 $] +#theorem(name: $sqrt(C)"omplicated Name"$)[Body4] + +#proof(link-to: )[Short Body]

+ +@a @b @p @z @a[theorem] + += Heading + +@a @b @p @z + +#theorem[Body6] + +== Heading + +#theorem[Body7] +#proof(name: "Named proof")[$ a^2+b^2=c^2 $] + +=== Heading + +#theorem[Body8] +#proof(lorem(200)) diff --git a/tests/last-heading.typ b/tests/last-heading.typ new file mode 100644 index 0000000..ff7a922 --- /dev/null +++ b/tests/last-heading.typ @@ -0,0 +1,18 @@ +#import "../src/export-lib.typ": theorem-kind, theorem-rules, last-heading + +#let theorem = theorem-kind("Theorem") +#set page(width: 200pt, height: auto, margin: 10pt) +#show: theorem-rules + +#set heading(numbering: "1.1") += Heading 1 +=== Heading 3 +==== Heading 4 +#set heading(numbering: none) +===== Heading 5 +#theorem(lorem(5)) +#theorem(link-to: last-heading.with(ignore-unnumbered: true), lorem(5)) +#theorem(link-to: last-heading.with(max-level: 2), lorem(5)) +#theorem(link-to: last-heading.with(max-level: 3), lorem(5)) +=== Heading 3 +#theorem(link-to: last-heading.with(ignore-unnumbered: true), lorem(5)) diff --git a/tests/max-reset-level.typ b/tests/max-reset-level.typ new file mode 100644 index 0000000..e314bfa --- /dev/null +++ b/tests/max-reset-level.typ @@ -0,0 +1,53 @@ +#import "../src/export-lib.typ": default-theorems + +#let (theorem, theorem-rules) = default-theorems(max-reset-level: 0, group: "a") +#show: theorem-rules +#let (lemma, theorem-rules) = default-theorems(max-reset-level: 1, group: "b") +#show: theorem-rules +#let (example, theorem-rules) = default-theorems(max-reset-level: 2, group: "c") +#show: theorem-rules +#let (corollary, theorem-rules) = default-theorems(max-reset-level: none, group: "d") +#show: theorem-rules +#set page(width: 300pt, height: auto, margin: 10pt) + +#theorem(lorem(5)) +#lemma(lorem(5)) +#example(lorem(5)) +#corollary(lorem(5)) + += 1 +#theorem(lorem(5)) +#lemma(lorem(5)) +#example(lorem(5)) +#corollary(lorem(5)) +== 2 +#theorem(lorem(5)) +#lemma(lorem(5)) +#example(lorem(5)) +#corollary(lorem(5)) +=== 3 +#theorem(lorem(5)) +#lemma(lorem(5)) +#example(lorem(5)) +#corollary(lorem(5)) + += 1 +#theorem(lorem(5)) +#lemma(lorem(5)) +#example(lorem(5)) +#corollary(lorem(5)) +== 2 +#theorem(lorem(5)) +#lemma(lorem(5)) +#example(lorem(5)) +#corollary(lorem(5)) +=== 3 +#theorem(lorem(5)) +#lemma(lorem(5)) +#example(lorem(5)) +#corollary(lorem(5)) +==================== 20 +#theorem(lorem(5)) +#lemma(lorem(5)) +#example(lorem(5)) +#corollary(lorem(5)) diff --git a/tests/references/basic.png b/tests/references/basic.png new file mode 100644 index 0000000..aa1165e Binary files /dev/null and b/tests/references/basic.png differ diff --git a/tests/references/last-heading.png b/tests/references/last-heading.png new file mode 100644 index 0000000..713d7d4 Binary files /dev/null and b/tests/references/last-heading.png differ diff --git a/tests/references/max-reset-level.png b/tests/references/max-reset-level.png new file mode 100644 index 0000000..805b6d7 Binary files /dev/null and b/tests/references/max-reset-level.png differ diff --git a/tests/references/show-rules.png b/tests/references/show-rules.png new file mode 100644 index 0000000..b8191c1 Binary files /dev/null and b/tests/references/show-rules.png differ diff --git a/tests/references/styles.png b/tests/references/styles.png new file mode 100644 index 0000000..975cbd7 Binary files /dev/null and b/tests/references/styles.png differ diff --git a/tests/show-rules.typ b/tests/show-rules.typ new file mode 100644 index 0000000..b8507c0 --- /dev/null +++ b/tests/show-rules.typ @@ -0,0 +1,18 @@ +#import "../src/export-lib.typ": theorem-kind, default-theorems, select-kind, select-group + +#let (theorem, lemma, proof, theorem-rules) = default-theorems( + max-reset-level: 2 +) +#let example = theorem-kind(group: "MyGroup", "Example") +#set page(width: 500pt, height: auto, margin: 10pt) +#show: theorem-rules + +#show select-group(example): box.with(stroke: purple + 1pt, inset: 1em) +#show select-kind(lemma): box.with(stroke: blue + 1pt, inset: 1em) +#show select-group(theorem): box.with(stroke: green + 1pt, inset: 1em) +#show select-group(proof): box.with(stroke: red + 1pt, inset: 1em) + +#theorem(lorem(20)) +#lemma(lorem(20)) +#proof(lorem(20)) +#example(lorem(20)) diff --git a/tests/styles.typ b/tests/styles.typ new file mode 100644 index 0000000..3847bf6 --- /dev/null +++ b/tests/styles.typ @@ -0,0 +1,31 @@ +#import "../src/export-lib.typ": theorem-kind, theorem-rules, style-simple, style-reversed, numbering-concat, numbering-proof, qed-box + +#let a = theorem-kind("a") +#let b = theorem-kind("b", style: style-simple.with(qed: qed-box)) +#let c = theorem-kind("c", style: style-reversed) +#let d = theorem-kind("d", style: style-reversed.with(qed: qed-box)) +#let e = theorem-kind("e", numbering: numbering-concat.with(seperator: "")) +#let f = theorem-kind("f", numbering: numbering-concat.with(seperator: text(red, "-"))) +#set page(width: 200pt, height: auto, margin: 10pt) +#show: theorem-rules + +#a(lorem(5)) +#b(lorem(5)) +#c(lorem(5)) +#d(lorem(5)) +#e(lorem(5)) +#f(lorem(5)) + +#set heading(numbering: "1.1") += Heading + +#a(lorem(5)) +#e(lorem(5)) +#f(lorem(5)) + +#set heading(numbering: "1.") += Heading + +#a(lorem(5)) +#e(lorem(5)) +#f(lorem(5)) diff --git a/tests/types.typ b/tests/types.typ new file mode 100644 index 0000000..b4e55df --- /dev/null +++ b/tests/types.typ @@ -0,0 +1,23 @@ +// Ref: false + +#import "../src/types.typ": * + +#assert.eq(types-to-string(array), "array") +#assert.eq(types-to-string(bool), "boolean") +#assert.eq(types-to-string(content), "content") +#assert.eq(types-to-string(dictionary), "dictionary") +#assert.eq(types-to-string(float), "float") +#assert.eq(types-to-string(function), "function") +#assert.eq(types-to-string(int), "integer") +#assert.eq(types-to-string(str), "string") +#assert.eq(types-to-string(type), "type") +#assert.eq(types-to-string(None), "none") +#assert.eq(types-to-string(None, int), "none or integer") +#assert.eq(types-to-string(int, None), "integer or none") +#assert.eq(types-to-string(None, int, bool), "none, integer or boolean") + +#assert-type(0, "", int) +#assert-type(0.0, "", float) +#assert(not check-type(0, float)) +#assert-type(0, "", int, float) +#assert-type(none, "", None, int, str) diff --git a/tools/check_readme.py b/tools/check_readme.py new file mode 100644 index 0000000..c5284ea --- /dev/null +++ b/tools/check_readme.py @@ -0,0 +1,42 @@ +from generate_readme import split_images, combine_markdown, IMAGE_FOLDER, query_to_markdown, README_TEMPLATE_PATH, README_PATH +from common import TypstRunner, ToolStatus, compare_files +import os + +def check_images(split_markdown, runner, status): + if type(split_markdown) == dict: + name, code, after = split_markdown["name"], split_markdown["code"], split_markdown["after"] + image_path = os.path.join(IMAGE_FOLDER, name + ".png") + + status.start_action("Checking " + image_path) + output_path = runner.tmp_file_path(name + ".png") + runner.compile_code(code, name + ".typ", output_path) + if status.check_runner(runner): + status.end_action(compare_files(image_path, output_path)) + else: + status.end_action(False) + + check_images(split_markdown["after"], runner, status) + +def main(): + with TypstRunner() as runner, ToolStatus("Readme is up to date", "Readme is not up to date") as status: + status.start_action("Compiling " + README_TEMPLATE_PATH) + output_path = runner.tmp_file_path("readme.pdf") + runner.compile_file(README_TEMPLATE_PATH, output_path) + status.end_action_assert(status.check_runner(runner)) + + status.start_action("Querying " + README_TEMPLATE_PATH) + query_result, query_success = runner.query_file(README_TEMPLATE_PATH, "") + status.end_action_assert(query_success) + + markdown_with_tags = query_to_markdown(query_result) + split_markdown = split_images(markdown_with_tags) + + check_images(split_markdown, runner, status) + + status.start_action("Checking " + README_PATH) + markdown = combine_markdown(split_markdown) + with open(README_PATH) as file: + status.end_action(file.read() == markdown) + +if __name__ == "__main__": + main() diff --git a/tools/check_tests.py b/tools/check_tests.py new file mode 100644 index 0000000..4c84cd6 --- /dev/null +++ b/tools/check_tests.py @@ -0,0 +1,19 @@ +from common import ToolStatus, TypstRunner, compare_files +from generate_references import list_tests, input_file_path, reference_file_path, has_reference_check + +def main(): + with TypstRunner() as runner, ToolStatus("All tests passed", "Some tests failed") as status: + for test in list_tests(): + status.start_action("Checking " + input_file_path(test)) + output_path = runner.tmp_file_path(test + ".png") + runner.compile_file(input_file_path(test), output_path) + if status.check_runner(runner): + if has_reference_check(test): + status.end_action(compare_files(output_path, reference_file_path(test))) + else: + status.end_action(True) + else: + status.end_action(False) + +if __name__ == "__main__": + main() diff --git a/tools/common.py b/tools/common.py new file mode 100644 index 0000000..a67fbaf --- /dev/null +++ b/tools/common.py @@ -0,0 +1,114 @@ +import os +import sys +import json +import filecmp +import subprocess + +class ToolStatus: + def __init__(self, msg_success, msg_fail): + self._msg_success = msg_success + self._msg_fail = msg_fail + + self._tool_success = True + self._action_success = True + + def _end_tool(self): + if self._tool_success: + print(self._msg_success + " ✓") + sys.exit(0) + else: + print(self._msg_fail + " ✗") + sys.exit(1) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_trace): + if exc_type is not None: + return False + self._end_tool() + + def start_action(self, msg): + self._action_success = True + print(msg, end="", flush=True) + + def update_action(self, value): + self._action_success &= value + self._tool_success &= value + if not value: + print(" ✗") + + def check_runner(self, runner): + runner_success = runner.last_returncode == 0 + self.update_action(runner_success) + if not runner_success: + print(runner.last_stdout) + print(runner.last_stderr) + return runner_success + + def end_action(self, value): + self.update_action(value) + if self._action_success: + print(" ✓") + return self._action_success + + def end_action_assert(self, value): + if not self.end_action(value): + self._end_tool() + + def only_success(self): + return self._tool_success + +class TypstRunner: + def __init__(self): + self._tmp_folder = os.path.join("tools", "tmp") + self.last_stderr = None + self.last_stdout = None + self.last_returncode = None + + def _delete_tmp_folder(self): + for file in os.listdir(self._tmp_folder): + os.remove(os.path.join(self._tmp_folder, file)) + os.rmdir(self._tmp_folder) + + def __enter__(self): + if not os.path.exists(self._tmp_folder): + os.makedirs(self._tmp_folder) + else: + self._delete_tmp_folder() + os.makedirs(self._tmp_folder) + return self + + def __exit__(self, exc_type, exc_value, exc_trace): + if exc_type is not None: + return False + + self._delete_tmp_folder() + + def tmp_file_path(self, filename): + return os.path.join(self._tmp_folder, filename) + + def compile_file(self, input_path, output_path): + completed_process = subprocess.run(["typst", "compile", input_path, "--root", ".", output_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + + self.last_returncode = completed_process.returncode + self.last_stderr = completed_process.stderr + self.last_stdout = completed_process.stdout + return self.last_returncode == 0 + + def compile_code(self, code, input_filename, output_path): + input_path = self.tmp_file_path(input_filename) + with open(input_path, mode="w") as file: + file.write(code) + return self.compile_file(input_path, output_path) + + def query_file(self, path, query): + completed_process = subprocess.run(["typst", "query", path, "--root", ".", query], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + self.last_returncode = completed_process.returncode + self.last_stderr = completed_process.stderr + self.last_stdout = completed_process.stdout + + return json.loads(completed_process.stdout), self.last_returncode == 0 + +def compare_files(file_a, file_b): + return os.path.exists(file_a) and os.path.exists(file_b) and filecmp.cmp(file_a, file_b) diff --git a/tools/generate_readme.py b/tools/generate_readme.py new file mode 100644 index 0000000..cff778e --- /dev/null +++ b/tools/generate_readme.py @@ -0,0 +1,79 @@ +from common import TypstRunner, ToolStatus +import os + +README_PATH = "README.md" +README_TEMPLATE_PATH = os.path.join("docs", "readme.typ") +README_PDF_PATH = os.path.join("docs", "readme.pdf") +IMAGE_FOLDER = os.path.join("docs", "images") + +def query_to_markdown(query_result): + return "\n".join([e["value"] for e in query_result]).lstrip() + +def split_open_tag(markdown): + open_tag_start_index = markdown.find("") + open_tag_start_index + + before = markdown[:open_tag_start_index] + name = markdown[open_tag_start_index:open_tag_end_index].removeprefix("") + + return before, after, name + +def split_close_tag(markdown): + close_tag_start_index = markdown.find("") + + before = markdown[:close_tag_start_index] + after = markdown[close_tag_start_index:].removeprefix("") + + return before, after + +def split_images(markdown): + if "") + status.end_action_assert(query_success) + + markdown_with_tags = query_to_markdown(query_result) + split_markdown = split_images(markdown_with_tags) + generate_images(split_markdown, runner, status) + if not status.only_success(): + return + + markdown = combine_markdown(split_markdown) + with open(README_PATH, mode="w") as file: + file.write(markdown) + +if __name__ == "__main__": + main() diff --git a/tools/generate_references.py b/tools/generate_references.py new file mode 100644 index 0000000..e88889f --- /dev/null +++ b/tools/generate_references.py @@ -0,0 +1,47 @@ +from common import ToolStatus, TypstRunner +import argparse +import sys +import os + +def list_tests(): + tests = [] + for file in os.listdir("tests"): + if file.endswith(".typ"): + testname = file.removesuffix(".typ") + tests.append(testname) + return tests + +def input_file_path(test_name): + return os.path.join("tests", test_name + ".typ") + +def reference_file_path(test_name): + return os.path.join("tests", "references", test_name + ".png") + +def has_reference_check(test_name): + with open(input_file_path(test_name)) as file: + test_input = file.read() + return not test_input.startswith("// Ref: false") + +def main(): + parser = argparse.ArgumentParser(description="Update the reference images of tests.") + parser.add_argument("--all", "-a", action="store_true", help="Update all reference images") + parser.add_argument("tests", nargs="*", help="List of tests to update") + + args = parser.parse_args() + + if args.all or args.tests: + tests = list_tests() if args.all else args.tests + + with TypstRunner() as runner, ToolStatus("Updated references", "Failed to update references") as status: + for test in tests: + if has_reference_check(test): + status.start_action("Compiling " + input_file_path(test)) + runner.compile_file(input_file_path(test), reference_file_path(test)) + status.end_action(status.check_runner(runner)) + else: + print("No tests provided. Use either '--all' or provide a list of test names.") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/typst.toml b/typst.toml index 64ebf29..5a7c75f 100644 --- a/typst.toml +++ b/typst.toml @@ -1,8 +1,10 @@ [package] name = "lemmify" -version = "0.1.4" -entrypoint = "src/lib.typ" +version = "0.2.0" +entrypoint = "src/export-lib.typ" authors = ["Marmare314"] license = "GPL-3.0-only" description = "Theorem typesetting library." repository = "https://github.com/Marmare314/lemmify" +compiler = "0.8.0" +exclude = ["docs/images/*", "docs/readme.pdf"]