diff --git a/README.md b/README.md index 66f0a5c..cf8482f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Lemmify +# lemmify Lemmify is a library for typesetting mathematical theorems in typst. It aims to be easy to use while @@ -7,15 +7,21 @@ 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 +[GitHub](https://github.com/Marmare314/lemmify). + ## Basic usage -1. Import the Lemmify library +1. Import lemmify: + ```typst #import "@preview/lemmify:0.2.0": default-theorems, select-kind ``` -2. Generate some common theorem kinds with pre-defined styling +2. Generate some common theorem kinds with pre-defined style: + ```typst #let ( theorem, lemma, corollary, @@ -24,22 +30,35 @@ But no functionality should be lost. ) = default-theorems(lang: "en") ``` -3. Apply the generated styling +3. Apply the generated style: + ```typst #show: theorem-rules ``` -4. Customize the styling using show rules. For example, to add a red box around proofs +4. Customize the theorems using show rules. For example, to add a block around proofs: + ```typst -#show select-kind(proof): box.with(stroke: red + 1pt, inset: 1em) +#show select-kind(proof): block.with( + breakable: true, + width: 100%, + fill: gray, + inset: 1em, + radius: 5pt +) ``` -5. Create theorems, lemmas, and proofs +5. Create theorems, lemmas, and proofs: + ```typst #theorem(name: "Some theorem")[ Theorem content goes here. ] +#theorem(numbering: none)[ + Another theorem. +] + #proof(link-to: )[ Complicated proof. ] @@ -47,13 +66,14 @@ But no functionality should be lost. @proof and @thm[theorem] ``` -The result should now look something like this +The result should now look something like this: ![image](docs/images/image_0.png) ## Examples This example shows how corollaries can be numbered after the last theorem. + ```typst #import "@preview/lemmify:0.2.0": theorem-rules, theorem-kind, select-kind, reset-counter @@ -73,7 +93,6 @@ This example shows how corollaries can be numbered after the last theorem. #corollary(lorem(5)) ``` - ![image](docs/images/image_1.png) ## Custom style example @@ -118,5 +137,6 @@ This example shows how corollaries can be numbered after the last theorem. ] ``` - ![image](docs/images/image_2.png) + +For a full documentation of all functions check [readme.pdf](docs/readme.pdf) diff --git a/docs/generate_readme.py b/docs/generate_readme.py index 572c1a8..1cba050 100644 --- a/docs/generate_readme.py +++ b/docs/generate_readme.py @@ -13,69 +13,71 @@ def file_name(): def image_name(): return os.path.join(IMAGE_FOLDER, "image_" + str(GENERATED_IMAGE_COUNT) + ".png") -def get_content(): - print("Querying " + README_FILE_PATH, end="", flush=True) - completed_process = subprocess.run(["typst", "query", README_FILE_PATH, "--root", ".", ""], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) +def create_file_folder(): + if not os.path.exists(TMP_FILE_FOLDER): + os.makedirs(TMP_FILE_FOLDER) + +def delete_file_folder(): + for file in os.listdir(TMP_FILE_FOLDER): + os.remove(os.path.join(TMP_FILE_FOLDER, file)) + os.rmdir(TMP_FILE_FOLDER) + +def compile_code(code): + global GENERATED_IMAGE_COUNT + + current_filename = file_name() + current_imagename = image_name() + GENERATED_IMAGE_COUNT += 1 + + with open(current_filename, mode="w") as file: + file.write(code) + + print("Compiling " + current_filename, end="", flush=True) + completed_process = subprocess.run(["typst", "compile", current_filename, "--root", ".", current_imagename], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if completed_process.returncode != 0: print(" ✗") print(completed_process.stderr) print(completed_process.stdout) exit(1) - print(" ✓") - return completed_process.stdout + else: + print(" ✓") + + return current_imagename -def compile_file(): - print("Compiling " + file_name(), end="", flush=True) - completed_process = subprocess.run(["typst", "compile", file_name(), "--root", ".", image_name()], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) +def query_exports(): + print("Querying " + README_FILE_PATH, end="", flush=True) + completed_process = subprocess.run(["typst", "query", README_FILE_PATH, "--root", ".", ""], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) if completed_process.returncode != 0: print(" ✗") print(completed_process.stderr) print(completed_process.stdout) exit(1) - else: - print(" ✓") + print(" ✓") + return completed_process.stdout -def create_file_folder(): - if not os.path.exists(TMP_FILE_FOLDER): - os.makedirs(TMP_FILE_FOLDER) +def exported_markdown(): + exports = json.loads(query_exports()) + return "\n".join([e["value"] for e in exports]).lstrip() -def delete_file_folder(): - for file in os.listdir(TMP_FILE_FOLDER): - os.remove(os.path.join(TMP_FILE_FOLDER, file)) - os.rmdir(TMP_FILE_FOLDER) +def generate_images(markdown): + open_tag_index = markdown.find("") + if open_tag_index >= 0: + close_tag_index = markdown.find("") + assert close_tag_index >= 0 -def convert_to_markdown(content): - global GENERATED_IMAGE_COUNT + before = markdown[:open_tag_index].rstrip() + "\n\n" + image_code = markdown[open_tag_index:close_tag_index].removeprefix("") + after = markdown[close_tag_index:].removeprefix("") - result = "" - for element in content: - if element["func"] == "heading": - if result != "": - result += "\n" - # assume unnumbered and text-only headings - result += "#" * element["level"] + " " + element["body"]["text"] + "\n\n" - elif element["func"] == "text": - result += element["text"] + "\n" - elif element["func"] == "raw": - result += "```" + element["lang"] + "\n" + element["text"] + "\n" + "```\n" - elif element["func"] == "metadata" and "code" in element["value"]: - with open(file_name(), mode="w") as file: - file.write(element["value"]["code"]) - compile_file() - result += f"\n![image]({image_name()})\n" - GENERATED_IMAGE_COUNT += 1 - elif element["func"] == "metadata" and "text" in element["value"]: - result += element["value"]["text"] + "\n" - elif element["func"] == "enum": - element = element["children"][0] - result += str(element["number"]) + ". " + element["body"]["text"] + "\n" - - return result + image_path = compile_code(image_code) + return before + f"![image]({image_path})" + generate_images(after) + else: + return markdown def main(): - content = json.loads(get_content()) + markdown = exported_markdown() create_file_folder() - markdown = convert_to_markdown(content) + markdown = generate_images(markdown) delete_file_folder() with open("README.md", mode="w") as file: file.write(markdown) diff --git a/docs/readme.pdf b/docs/readme.pdf index 337066a..322f0b5 100644 Binary files a/docs/readme.pdf and b/docs/readme.pdf differ diff --git a/docs/readme.typ b/docs/readme.typ index 698ffe1..5b9ed07 100644 --- a/docs/readme.typ +++ b/docs/readme.typ @@ -10,16 +10,25 @@ reset-counter: reset-counter ) +#let export-code(code, setup, imports) = { + imports = "#import \"../../src/export-lib.typ\": " + imports.join(", ") + code = imports + "\n" + setup + "\n" + code + + [#metadata((code: code)) ] +} + #let eval-raws(..raws, scope: (:), export-setup: "") = { assert(raws.named().len() == 0) let code = raws.pos().map(x => x.text + "\n").sum() - block( - fill: gray.lighten(50%), - inset: 1em, - radius: 5pt, - eval(code, mode: "markup", scope: scope) - ) - [#metadata((code: "#import \"../../src/export-lib.typ\": " + scope.keys().join(", ") + "\n" + export-setup + "\n" + code)) ] + [ + #block( + fill: gray.lighten(50%), + inset: 1em, + radius: 5pt, + eval(code, mode: "markup", scope: scope) + ) + ] + export-code(code, export-setup, scope.keys()) } #let code-with-import(..imports, code: raw("")) = { @@ -37,171 +46,233 @@ } #let export-only(text) = [ - #metadata((text: text)) + assert(type(text) == str) + #metadata(text) ] -#show enum: it => [#it ] +#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 +} -= Lemmify +#let content-to-markdown(con) = { + assert(type(con) == content) + if con.has("children") { + combine-spaces(con).map(content-to-markdown).join("") + } 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 { + "[" + 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 { + "```" + con.lang + "\n" + con.text + "\n" + "```" + } 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 { + panic("conversion to text not implemented for " + repr(con.func())) + } +} -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. +#let export(con) = { + con + export-only(content-to-markdown(con)) +} -#export-only("\nIf you are encountering any bugs, have questions or are missing features, feel free to open an issue on [GitHub](\"https://github.com/Marmare314/lemmify\").") +#show raw: block.with(stroke: 1pt + gray, fill: gray.lighten(70%), inset: 1em, width: 100%, radius: 5pt) -== Basic usage +#export[ + = lemmify -1. Import the Lemmify library + 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. -#let (basic-usage-scope, step1) = code-with-import("default-theorems", "select-kind") + 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]. -#step1 + == Basic usage -2. Generate some common theorem kinds with pre-defined styling + 1. Import lemmify: -#let step2 =```typst -#let ( - theorem, lemma, corollary, - remark, proposition, example, - proof, theorem-rules -) = default-theorems(lang: "en") -``` -#step2 + #let (basic-usage-scope, step1) = code-with-import("default-theorems", "select-kind") -3. Apply the generated styling + #step1 -#let step3 = ```typst -#show: theorem-rules -``` -#step3 + 2. Generate some common theorem kinds with pre-defined style: -4. Customize the styling using show rules. For example, to add a block around proofs + #let step2 =```typst + #let ( + theorem, lemma, corollary, + remark, proposition, example, + proof, theorem-rules + ) = default-theorems(lang: "en") + ``` + #step2 -#let step4 = ```typst -#show select-kind(proof): block.with( - breakable: true, - width: 100%, - fill: gray, - inset: 1em, - radius: 5pt -) -``` -#step4 + 3. Apply the generated style: -5. Create theorems, lemmas, and proofs + #let step3 = ```typst + #show: theorem-rules + ``` + #step3 -#let step5 = ```typst -#theorem(name: "Some theorem")[ - Theorem content goes here. -] + 4. Customize the theorems using show rules. For example, to add a block around proofs: -#theorem(numbering: none)[ - Another theorem. -] + #let step4 = ```typst + #show select-kind(proof): block.with( + breakable: true, + width: 100%, + fill: gray, + inset: 1em, + radius: 5pt + ) + ``` + #step4 -#proof(link-to: )[ - Complicated proof. -] + 5. Create theorems, lemmas, and proofs: -@proof and @thm[theorem] -``` -#step5 + #let step5 = ```typst + #theorem(name: "Some theorem")[ + Theorem content goes here. + ] -The result should now look something like this + #theorem(numbering: none)[ + Another theorem. + ] -#eval-raws( - step2, step3, step4, step5, - scope: basic-usage-scope, - export-setup: "#set page(width: 300pt, height: auto, margin: 10pt)" -) + #proof(link-to: )[ + Complicated proof. + ] -The default-theorems function has one more parameter max-reset-level -which controls on which headings the group counters are reset. -By default they will be reset on every heading. -To customize the look and behaviour of the theorems there -are some more parameters available. + @proof and @thm[theorem] + ``` + #step5 -== Examples + The result should now look something like this: -This example shows how corollaries can be numbered after the last theorem. + #eval-raws( + step2, step3, step4, step5, + scope: basic-usage-scope, + export-setup: "#set page(width: 300pt, height: auto, margin: 10pt)" + ) -#let example1 = ``` -#let theorem = theorem-kind("Theorem") -#let corollary = theorem-kind( - "Corollary", - group: "CorollaryGroup", - 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 (example1-scope, example1-with-import) = code-with-import("theorem-rules", "theorem-kind", "select-kind", "reset-counter", code: example1) -#example1-with-import - -#eval-raws( - example1, - scope: example1-scope, - export-setup: "#set page(width: 300pt, height: auto, margin: 10pt)" -) + == Examples -== Custom style example - -#let example2 = ```typst -#let my-style-func(thm, is-proof: false) = { - let params = get-theorem-parameters(thm) - let number = (params.numbering)(thm, false) - let content = grid( - columns: (1fr, 3fr), - column-gutter: 1em, - stack(spacing: .5em, strong(params.kind-name), number, emph(params.name)), - params.body + This example shows how corollaries can be numbered after the last theorem. + + #let example1 = ``` + #let theorem = theorem-kind("Theorem") + #let corollary = theorem-kind( + "Corollary", + group: "CorollaryGroup", + 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 (example1-scope, example1-with-import) = code-with-import("theorem-rules", "theorem-kind", "select-kind", "reset-counter", code: example1) + #example1-with-import + + #eval-raws( + example1, + scope: example1-scope, + export-setup: "#set page(width: 300pt, height: auto, margin: 10pt)" ) - if is-proof { - block(inset: 2em, content) - } else { - block(inset: 1em, block(fill: gray, inset: 1em, radius: 5pt, content)) + == Custom style example + + #let example2 = ```typst + #let my-style-func(thm, is-proof: false) = { + let params = get-theorem-parameters(thm) + let number = (params.numbering)(thm, false) + let content = grid( + columns: (1fr, 3fr), + column-gutter: 1em, + stack(spacing: .5em, strong(params.kind-name), number, emph(params.name)), + params.body + ) + + if is-proof { + block(inset: 2em, content) + } else { + block(inset: 1em, block(fill: gray, inset: 1em, radius: 5pt, content)) + } } -} -#let my-style = ( - style: my-style-func, - proof-style: my-style-func.with(is-proof: true) -) - -#let ( - theorem, proof, theorem-rules -) = default-theorems(lang: "en", ..my-style) -#show: theorem-rules + #let my-style = ( + style: my-style-func, + proof-style: my-style-func.with(is-proof: true) + ) -#lorem(20) -#theorem(name: "Some theorem")[ - #lorem(40) -] -#lorem(20) -#proof[ - #lorem(30) + #let ( + theorem, proof, theorem-rules + ) = default-theorems(lang: "en", ..my-style) + #show: theorem-rules + + #lorem(20) + #theorem(name: "Some theorem")[ + #lorem(40) + ] + #lorem(20) + #proof[ + #lorem(30) + ] + ``` + + #let (example2-scope, example2-with-import) = code-with-import("default-theorems", "get-theorem-parameters", code: example2) + #example2-with-import + + #eval-raws( + example2, + scope: example2-scope, + export-setup: "#set page(width: 500pt, height: auto, margin: 10pt)" + ) ] -``` - -#let (example2-scope, example2-with-import) = code-with-import("default-theorems", "get-theorem-parameters", code: example2) -#example2-with-import -#eval-raws( - example2, - scope: example2-scope, - export-setup: "#set page(width: 500pt, height: auto, margin: 10pt)" -) +#export-only("\nFor a full documentation of all functions check [readme.pdf](docs/readme.pdf)\n") #include "source_docs.typ"