Compare commits

..
100 changed files with 27 additions and 17499 deletions

View file

@ -1,22 +0,0 @@
name: Rust Test
on:
push:
branches: [ ]
pull_request:
branches: [ "indev" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

9
.gitignore vendored
View file

@ -1,10 +1 @@
# Build
/target /target
# VSCode
.vscode/settings.json
examples/advanced_ayano/sine_graph.png
examples/advanced_ayano/source/other_sine_graph.png
/**/.kyomato_tmp
/**/*.pdf

1030
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,41 +1,16 @@
workspace = { members = ["examples-util", "kyomato-cli", "kyomato-core"] } [workspace]
[package] members = [ "core"]
name = "kyomato-lib" resolver = "1"
version = "0.1.0"
edition = "2021"
[features] [workspace.lints.rust]
default = ["serde"] # TODO REMOVE AFTER TOKEN REWORK rust_2018_idioms = { level = "deny", priority = -1 }
serde = ["dep:serde", "dep:url_serde"] missing_debug_implementations = "deny"
missing_docs = "deny"
unsafe_code = "deny"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace.lints.rustdoc]
broken_intra_doc_links = "forbid"
[dependencies] [workspace.lints.clippy]
cached = "0.48.1" module_name_repetitions = "allow"
derive_more = "0.99.17"
itertools = "0.12.1"
nom = "7.1.3"
once_cell = "1.19.0"
pyo3 = { version = "0.20.2", features = ["auto-initialize"] }
rand = "0.8.5"
regex = "1.10.3"
thiserror = "1.0.56"
url = "2.5.0"
serde = { version = "1.0.197", features = ["derive"], optional = true }
url_serde = { version = "0.2.0", optional = true }
smart-default = "0.7.1"
pathdiff = "0.2.1"
[dev-dependencies]
text-diff = "0.4.0"
nom = { version = "7.1.3", features = ["alloc"] }
examples-util = { path = "examples-util" }
# source: https://stackoverflow.com/a/54842093
[profile.lowsize]
inherits = "release"
opt-level = 'z' # Optimize for size
lto = true # Enable link-time optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations
panic = 'abort' # Abort on panic
strip = true # Strip symbols from binary*

251
README.md
View file

@ -1,251 +0,0 @@
*Kyoko is being a tomato. Why? I don't know :idk:*
# Overview
`Kyomato` allows transformation of extended markdown subset into a $\LaTeX$ document, ready for compilation.
You can use helper CLI script or write your own, to then compile said $\LaTeX$, of transform in some other way.
### WHY NOT PANDOC???
Please, see the "**Why?**" paragraph at the very bottom about the reasons I developed this thing. I doubt you can effortlessly do all that with `pandoc`
(I'm not willing to die trying).
# Examples
For programmatic examples of usage, see [examples]
# CLI
`Kyomato` cli binary can be found at a nested `kyomato` crate. It can be built with usual
```sh
cd kyomato
cargo build --release
```
After than a binary will be at `./target/release/kyomato`.
There's a helper script at `./kyomato-scripts/kyomato-helper.bash` for simplified usage. Here's a quick walkthrough:
- Build the binary or download already built one
- Add it to the `PATH` envvar. In some shells, you can prepend your command with `PATH=$PATH:/path/to/kyomato` for that. Just make sure that provided path is absolute (you can run it through `realpath` for that)
- Run
```bash
kyomato-helper.bash ./path/to/input.md
```
For example, here's a command I actually tested it with at `kyomato-scripts` directory:
```fish
PATH=$PATH:$(realpath ../target/release) KYOMATO_IMAGES=$(realpath ./image_folder) ./kyomato-helper.bash test_input/input.md
```
After running it, result should be located at `test_input.input.pdf`.
# Walkthrough
**PLEASE NOTE**: rules described below are fairly strict, since there's no literal markdown parser inside. `Kyomato`'s design is "*un-optimal*" right now, since I need it to work **NOW**.
## Markdown-inspired syntax
There are couple of elements looking similar to markdown[^not_inherited], namely:
- **Indents are NOT SUPPORTED**: the parser is quite primitive now, so it explicitly relies on some elements having nothing in front of them.
- Headers:
- up to 6 deep
- only hash-variant (`### Head of 3rd order`) is supported
- current $\LaTeX$ representation is a placeholder
- Formatting
- **bold text**
- *italic text*
- ~~strikethrough~~ text
- **NOT** `monospace` text yet, because I kinda forgot about it
- Footnotes:
- (probably full support?)
- inserted **near the first use** in the output, instead of **where they are actually defined**.
- Lists
- limited item support - make sure to include only one-line-things as items
- (the above also means, there are no nested lists, for now)
- only bullet, arabic, latin and cyrillic enumerations are supported, for now.
- Inline and Display latex mathmode (not sure how it's properly called, it's the thing that allows for write fancy formulae)
- Tables: columns formatting is ignored, for now
- Figures: with obsidian-like insert (`![[path/to.image.png]]`), so that Obsidian would actively display them
- Separators: these act like page breaks in the resulting $\LaTeX$
- Hyper references (`href`): limited support, `href`s must be defined right near them~~, mainly because I forgot you can do that right up until right now~~
- Code blocks: implemented with [Minted]
## Latex
The are a couple capabilities, markdown does not usually provide:
- Assigning **identifiers** to `tables`, `figures` and `equations` (display math)
- Syntax (*in meta block*): `ref = REF`. `REF` cannot contain whitespace
- Example (*in meta block*): `ref = graph` will transform to one of
- `\label{tab:graph}`, if inserted after table
- `\label{fig:graph}`, if inserted after figure
- `\label{eq:graph}`, if inserted after equation
- there's no way to opt this behavior out, for now
- Referring to `tables`, `figures` and `equations` (display math)
- Syntax: `[@REF]`
- Example: `[@fig:graph]` transforms into $\LaTeX$'s `\ref{fig:graph}`
- Assigning captions for `figures` and `tables`:
- Syntax (*in meta block*): `caption = "CAPTION"`. `CAPTION` can only contain escaped double quotes (`\"`)
- Example: (*in meta block*): `caption = "Wow, this table is such data; Much amaze"`
- Assigning width to `figures`:
- Syntax (*in meta block*): `width = WIDTH`. `WIDTH` must be a valid decimal for your $\LaTeX$ engine
- Example (*in meta block*): `width = 0.9`
- Width is measured in terms out width of your document. So `1.0` will scare the figure to the width of the document
- `0.9` is the default value
### A meta block
This is a one-line braces-delimited block after `equation`, `figure` or `table`. It is expected to be placed on the next line. Comma-separated arguments described above can be placed there in any order and with any amount of whitespace between them.
Take a look at the [examples] for a better idea of this block's usage.
## Yaml
What's `yaml` doing here? Well, it's related to `pandoc` allowing you substitute stuff in a so-called "templates" with certain strings.
Although there's neither implemented `yaml` parser, nor fully-supported arbitrary pattern system yet, `Kyomato` allows to do a similar thing: check out corresponding [example][examples] (specifically, `title_info` one).
## Ayano
*because Ayano loves Kyoko*
`Kyomato` has a very special type of syntax, that actually makes it so useful to me: `Ayano` blocks.
Basically, `Kyomato` can execute arbitrary Python code during **output generation** (for example, to perform some computations, or `rm-rf`ing your entire hard drive, that's up to you, really). `Kyomato` is also able to insert results of said computations into the output (or refer to them, in case of figures, as they can't be inserted into latex directly). `Ayano` blocks are powered by [pyo3].
Through the following explanations, there will be a couple of *technical notes*, describing exact inner working of `Ayano` blocks.
There are two types of `Ayano` blocks:
- *Static blocks are executed all at once **just before** output starts actually generating.* There's nothing left in place of it in the output. All static blocks share the environment and their variables are visible to all function blocks.
- *Function blocks are **compiled** together with static blocks into a single Python module,* but are executed on demand, during the output generation process. Function blocks leave **something** instead of them in the output.
`Ayano` blocks are regular code blocks, with their language set to `Python, Ayano`. Once `Kyomato` encounters alike block, it may react in a couple of ways, depending on what's specified *after* `Ayano` keyword.
A general syntax: `Python, Ayano ! * "DESCRIPTION" ~ PATH`. It is comprised of three optional independent parts (in any order):
- Static block declaration (`!`):
- *Makes block static*
- *Omitting it makes block a function*
- Display declaration (`* "DESCRIPTION"`):
- Adds this block of code to a special section at the very end of the output
- Description is optional - you can just leave it as `*`
- Description must escape any double quotes
- If description is specified, it will be displayed under block's representation in a said special section
- Insert declaration (`~ PATH`):
- Must point to some Python script with path relative to the execution folder
- Said script is inserted at the beginning of the block, and will be executed and/or displayed with block's code
- *A block marked with that, will execute it's code in the directory of a pointed script*
- *Blocks without this mark, execute their scripts in the directory of the source, if available*
As mentioned prior, all function blocks are replaces with **something**. You can see them as functions that leave in the output their result. *Anything having a `__str__` Python representation can be a valid output.*
Additionally, block's last line can be a special `Ayano Syntax`. These are mostly shortcuts to certain already-existing $\LaTeX$ structures, like figures and tables:
- Trailing return: basically, you can omit the actual `return` keyword, and it will be appended for you by `Ayano`. That's jut about convenience - you can still put that `return` each time, `Ayano` shouldn't complain about that.
- Value-Error formatting syntax:
- Accepts a value and an error, outputting them with $\LaTeX$'s $\pm$ (`\pm`) in between
- Syntax: `@dev: VALUE, ERROR`. `VALUE` and `ERROR` must be valid Python objects that can be parsed into float
- Example: `@dev: 4.54535, 0.2342`
- Additionally, a certain manipulation is performed on parsed value and error:
- *All operations are performed in decimal, so no binary jumpscares*
- An error is rounded to
- Two digits, if it starts with `1` or `2`
- Single digit otherwise
- A value itself is rounded or appended to the same digit as error
- Figure syntax:
- Transforms into an inserted figure on output
- Syntax: `@fig: src = SRC, ident = IDENT, caption = CAPTION`
- Arguments can come in any order and with any whitespace
- All arguments must be something Python can `str()`
- `SRC` argument is mandatory, and is supposed to point to image file to include
- If `Ayano` detects `SRC` to be something starting and ending with (double) quotes, a special check will be performed to inform you, if file it points to does not exist. This should not halt `Kyomato`, as said file can be generated during block execution
- `IDENT` is optional, and specifies figure's `ref`
- `CAPTION` is optional, and specifies figure's `caption`
- `CAPTION`'s content is run though regular parser, *with minor limitations*.
- Example: `@fig: src="line.png", ident="line", caption="Figure caption"`
- CSV-Table syntax:
- Reads and inserts a CSV table into the output
- Syntax: `@csv_table: src = SRC, rows = ROWS, columns = COLUMNS, ident = IDENT, caption = CAPTION`
- Arguments can come in any order and with any whitespace
- `SRC`, `IDENT` and `CAPTION` arguments must be something Python can `str()`
- `SRC` argument is mandatory, and is supposed to point to csv table file. can actually not be a csv table, just some format that [python's csv module] can manage to read.
- If `Ayano` detects `SRC` to be something starting and ending with (double) quotes, a special check will be performed to inform you, if file it points to does not exist. This should not halt `Kyomato`, as said file can be generated during block execution
- `ROWS` must have a [Rust range format]. It defines rows of the table to include (0th row is considered a header, that's always included). *This argument it parsed and used internally by `Ayano`, so please follow the formatting strictly*
- Omitting `ROWS` will include all rows
- `COLUMNS` defines table columns to include. Here's an example of the syntax: `["column1", "column2", ("value_column", "error_column")]`:
- *This argument it parsed and used internally by `Ayano`, so please follow the formatting strictly*
- Syntax resembles an array of elements, where each element can be of two types:
- Single column include - representing a single column from the table, included under it's name into the output table
- Value-error column include - representing **value column** and **error column**, that will be merged using *value-error syntax* and included as a *single column* to the output, under **value column**'s name
- Order of columns in the output table will be the same as specified by this syntax
- Omitting `COLUMNS` will include all columns in some order, and without any fancy formatting
- `IDENT` is optional, and specifies table's `ref`
- `CAPTION` is optional, and specifies table's `caption`
- `CAPTION`'s content is run though regular parser, *with minor limitations*.
- *Table's cells are run trough regular parser too, so you can format text, add referenced and so on*
- This specific syntax expects Python to read the input file. This is important, if your block has an insert declaration - make sure that Python will be able to read the intended file without any fancy path searching, just as it is
- Generated-Table syntax:
- Transforms into a runtime-generated table, by calling a special generator object with table's row and column
- Syntax: `@gen_table: GENERATOR; rows=ROWS, columns=COLUMNS, caption=CAPTION, ident=IDENT`
- Arguments except `GENERATOR` can come in any order and with any whitespace
- `IDENT` and `CAPTION` arguments must be something Python can `str()`
- `IDENT` is optional, and specifies table's `ref`
- `CAPTION` is optional, and specifies table's `caption`
- `CAPTION`'s content is run though regular parser, *with minor limitations*.
- *Table's cells are run trough regular parser too, so you can format text, add referenced and so on*
- `ROWS` and `COLUMNS` arguments **must be** a plain decimal integers, specifying number of rows and columns respectively generated for table
- *`ROWS` and `COLUMNS` are parsed and used internally by `Ayano`, so please follow the formatting strictly* **(that's temporary, there are plans to do these runtime-determined too)**
- `GENERATOR` must be something that can be called by Python with two arguments: cell's row and column respectively. For example, it can name a function with fitting signature, or be a Python lambda: `lamba r,c f"That's cell ({r}, {c})"`
- `GENERATOR` must come first, and expected to have a semicolon after it
*There are ways to trigger explained behavior without actually using this fancy syntax. Internally, `Ayano` still converts it to a valid Python code, so if you happen to write the same code yourself, `Ayano` will happily detect and react to it. However, the only practical one I see is value-error formatting one, here's it's transformed syntax: `("err", value, error)`. You can, of course, use anything indexable by `0`, `1` and `2`, that should work fine too.*
For more info on `Ayano` blocks usage, check out [examples].
## Path engines
There are several directories your code can potentially refer to:
- If you're inside `Ayano` block with insert declaration, you might refer to stuff relative to insert declaration's directory
- Any point of source file can potentially point a file right next it
- In [Obsidian], there'a dedicated directory for pictures, so your source might refer to it
- Lastly, you might refer to a local file in the directory you actually run `Kyomato` from
To control and direct to all of the described locations, a `PathEngine` (`PE`) is used. Basically, it checks all of the locations in a certain order, dependant on the requested file type and optionally preforms an extra check, like "if file actually exists, and I can access it?" or "if path points to a valid directory", or "does path look **SUS**"?
See [examples] for more explanation on that part
# Ideology
Although it's annoying to admit, there is no way to do this sort of transformation as fully-streamed and O(1) memo. There are two reasons for that:
- Markdown allows to define footnote content at any point up until the end of the file. I couldn't find any way for $\LaTeX$ to look up ahead for this sort of stuff - it seems to always insert them at the bottom of the page definition was encountered.
- Some `Ayano` blocks are supposed to leave something behind them, meaning we must execute them at the time of output generation. This can be achieved with O(1) memo, but would likely involve recompiling Python module each time, leading to longer execution time
Thus, code must be fully collected first, that executed of the second, separate path.
The above means, that it would be wise to read the entire input first, and store it in a large buffer. Then, various struct will just refer a part of that buffer, and only create their new owned buffer, once data they refer to is not really relevant.
For the most of the codebase, that's the design I'm following right now, but there are a couple of hiccups here and there...
There's a plan for complete rework of data storage, that will allow us to use much smaller tokens (they are, like, `120b` each now, which is... BAD, REALLY BAD).
## Not Async
As much as I'd like all of the processes be nice and async, I see no option for that in case of continuous output generation.
Well, unless there's a way to efficiently write to a single output from multiple threads, while preserving output and `Ayano` block execution order.
Any sort of "collect" function would destroyed the purpose, so that's not an easy thing to design.
# Why?
This crate allows to transform [Obsidian]'s markdown files into a $\LaTeX$ document of the format I personally like.
The general idea is - markdown is by design much less descriptive than $\LaTeX$,
but I've established a common style with my $\LaTeX$, and found myself just plainly copying stuff all over the place.
Automatic source transformation provided by editor **can** be used to mitigate that,
but in the end you still end up with a giant lumps latex syntax, that mean nothing to you, while you're writing the document.
Guess what is also a pain to copy around and check for validity? Data!
I often need a couple of tables inside of the document I write. And the most painful thing to do is to search for each and every
location you've inserted the data in, and replacing it with new, updated value (this mostly relates to calculation error fixing).
Along with that, I'd like to have some sort of documentation on exact actions I've done with the data to get the result I display. And these places should be kept in mind, too! It's only logical to delegate all of that to the machine.
At times before that, I used `Kile` to write my $\LaTeX$, `Alphaplot` to plot data and approximate and, `Libreoffice` to create spreadsheets and calculate stuff. Some sort of mistype in any of those lead to my *physical pain*, un-sync info everywhere, and a couple of minutes of me intensely finding out every place in my work, that needs to be updated, while keeping in mind all of the workflow I had at the moment of said distraction. I hope to completely eliminate situations like these with this project, reducing number of programs I use to a single markdown editor (which is `Obsidian`, for now).
If you can relate to problems above, I hope this project will help you too.
[^not_inherited]: I can't say these are implemented properly, since that would be a lie - their implementation is quite primitive right now, and should be used with caution or th. Keep in mind, that document you write must be translated to $\LaTeX$.
[Obsidian]: https://obsidian.md/
[Minted]: https://github.com/gpoore/minted
[examples]: https://github.com/Dzuchun/kyomato/tree/indev/examples
[pyo3]: https://github.com/PyO3/pyo3
[python's csv module]: https://docs.python.org/3/library/csv.html
[Rust range format]: https://doc.rust-lang.org/std/ops/struct.Range.html#examples

View file

@ -1,6 +1,9 @@
[package] [package]
name = "kyomato-util" name = "kyomato"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
[lints]
workspace = true

9
core/README.md Normal file
View file

@ -0,0 +1,9 @@
So, here comes yet-another token redesign. Thing is, I've found a great Djot parser at <https://github.com/hellux/jotdown?tab=readme-ov-file>, and run into a problem: they produce tokens containing string references already, which does not work well with my previous design based on separate handling of source and tokens.
Futhermore - there's no REAL reason to do that, I just wanted to be fancy or th :idk:
So, here comes yet-another token redesign. This time, tokens DO contain some-sort-of reference to underlying code, but actual TYPE of this reference is being abstracted on. This way, I would be able to use [bytes](https://docs.rs/bytes/latest/bytes/)-based references, `&str`-based, or whatever crazy `Span`-like stuff I'll come up with later.
Thing is, `jotdown` uses both `&str` and `Cow<&str>`, depending on if the source should be sanitized, so I'll empose a restriction on a source type to be `ToOwned`, and will define my own wrapped around `&str` to resolve to `::beef::Cow<&str>` instead.
One more thing I'd like to account for - partial parsing. Also, I'd like for my tree to be fully-modifiable and easily-redactable, so there's that :idk:.

1
core/src/lib.rs Normal file
View file

@ -0,0 +1 @@
#![doc = include_str!("../README.md")]

View file

@ -1,9 +0,0 @@
[package]
name = "examples-util"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
kyomato-lib = {path = ".."}

View file

@ -1,19 +0,0 @@
use std::path::Path;
pub fn setup_io(
input_path: impl AsRef<Path>,
output_path: impl AsRef<Path>,
) -> Result<(String, impl std::fmt::Write + std::fmt::Debug), std::io::Error> {
let input = std::fs::read_to_string(input_path.as_ref())?;
// you must have a complete input to process it properly
// open the output file
let output = std::fs::OpenOptions::new()
.read(true)
.write(true)
.truncate(true)
.create(true)
.open(output_path.as_ref())?;
let output = kyomato_lib::FmtToIo::new(output);
Ok((input, output))
}

View file

@ -1 +0,0 @@
2

View file

@ -1,16 +0,0 @@
"First Name","Last Name","Age","Salary","Tenacity"
"Wilson","Bennett","19","5639","97155"
"Hailey","Robinson","21","8419","68689"
"Edith","Morris","24","8134","10937"
"Adison","Brown","20","6131","41745"
"Vivian","Cunningham","22","7834","35514"
"Heather","Roberts","24","4881","73425"
"Michael","Armstrong","23","5781","30899"
"Miranda","Martin","19","8283","33619"
"Alexander","Kelley","29","1990","98055"
"Chloe","Spencer","24","779","89818"
"Sam","Johnston","25","1147","71225"
"Alissa","Spencer","18","3877","64965"
"Abraham","Kelley","20","1622","65308"
"Edwin","Stewart","20","4376","01364"
"Adam","Grant","19","9616","38385"
1 First Name Last Name Age Salary Tenacity
2 Wilson Bennett 19 5639 97155
3 Hailey Robinson 21 8419 68689
4 Edith Morris 24 8134 10937
5 Adison Brown 20 6131 41745
6 Vivian Cunningham 22 7834 35514
7 Heather Roberts 24 4881 73425
8 Michael Armstrong 23 5781 30899
9 Miranda Martin 19 8283 33619
10 Alexander Kelley 29 1990 98055
11 Chloe Spencer 24 779 89818
12 Sam Johnston 25 1147 71225
13 Alissa Spencer 18 3877 64965
14 Abraham Kelley 20 1622 65308
15 Edwin Stewart 20 4376 01364
16 Adam Grant 19 9616 38385

View file

@ -1 +0,0 @@
3

View file

@ -1,5 +0,0 @@
'name' 'value' 'err' '%'
'tau' 4.3535223 0.0034342 8e-06
'pi' 4334.34434 10 2.3e-05
'chi' -2332.332424 1e-07 -0.0
'zeta' 4444.0 0.033 0.0
1 'name' 'value' 'err' '%'
2 'tau' 4.3535223 0.0034342 8e-06
3 'pi' 4334.34434 10 2.3e-05
4 'chi' -2332.332424 1e-07 -0.0
5 'zeta' 4444.0 0.033 0.0

View file

@ -1,42 +0,0 @@
import csv
# let's just create a bunch of variables and fill a local generated table with them
with open("calculations_table.csv", "w") as f:
writer = csv.DictWriter(
f,
fieldnames=[
"name",
"value",
"err",
"%",
],
quoting=csv.QUOTE_NONNUMERIC,
delimiter=" ",
quotechar="'",
)
writer.writeheader()
tau = (4.3535223, 0.0034342)
pi = (4334.34434, 10)
chi = (-2332.332424, 1e-7)
zeta = (4444.00, 0.033)
def write_var(writer, val, name):
writer.writerow(
{
"name": name,
"value": val[0],
"err": val[1],
"%": round(val[1] / val[0] / 100, ndigits=6),
}
)
write_var(writer, tau, 'tau')
write_var(writer, pi, 'pi')
write_var(writer, chi, 'chi')
write_var(writer, zeta, 'zeta')
# say, we do something undesirable in this script next, and we do not want kyomato to execute that
# TOSHINO KYOKO! <-- this terminates the execution
exit(-42)

View file

@ -1,3 +0,0 @@
three = None
with open("3", "r") as f:
three = int(f.readline())

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

View file

@ -1,40 +0,0 @@
use kyomato_lib::path_engine::EngineExt;
fn main() {
//! This is ayano-involving example of programmatic output generation
//!
//! Some advanced setup is used here
//!
//! You might want to use CLI interface instead
// we'll just assume that to demonstrate a certain thing, look it in the input
std::env::set_current_dir("examples/advanced_ayano").unwrap();
// that's a utility setup call; essentially, it reads input from one file, and opens output file for writing
let (input, mut output) = examples_util::setup_io("source/input.md", "output.tex").unwrap();
// construct path engine
// there are a couple of parts of the path engine. you should specify them in the increasing priority order
let path_engine = kyomato_lib::path_engine::base_gen()
// execution folder: you might want generator to check it, if all other options had failed
.with_execution_folder(".")
// source folder: you'll want to check it sometime. this is intended to be a folder where input markdown is located
.with_source_folder("source")
// image folder: a folder to exclusively check for images referred by the generator
.with_image_folder("images")
// this call will make sure that the engine actually respects ayano-insert directory supplied to it's methods
.with_ayano()
// this adds a check to the engine, that will ensure that returned path actually points to something in the system
// without it, first proposed variant will be used (in this case, this turns out to be ayano-insert directory)
.with_check(kyomato_lib::path_engine::fs_check)
// this last call is a no-op used to explicitly set error type. You may implement your own error too!
// (the one actually used here is kinda bad, consider using `kyomato::path_engine::MarkerError` for more memory-efficiency)
.with_error_type::<kyomato_lib::path_engine::InformativeError>();
// please note that most of the above is only used by actual latex engine you plan to use.
// That is, take care of paths being the same for kyomato and the engine, or use some sort of absolute paths.
// parse input and write to output
kyomato_lib::LabaLatex::parse_and_write(path_engine, &input, &mut output)
// this part is just for debug purposes
.map_err(|err| (err, output))
.unwrap();
}

View file

@ -1,544 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE
\LARGE
\vspace{100pt}
\LARGE
\Large
\Large
\Large
\huge
\vspace{50pt}
\large
\large
\large
\large
\vspace*{\fill}
\hspace{\fill}\large \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt
This is an example of advanced `Ayano` features.
\tbln First, there are insert blocks. These can load some code from specified python script, and insert it at the start:
\tbln Note that their displayed version at the end actually includes the loaded script. These blocks can be used for display too: \\
[0.0pt]e = 2.7182818284590455, pi = 3.141592653589793, pi != 2.7182818284590455
\tbln Sometimes you wish to only include part of the script, and do something else after some point. To achieve that, you can include `\# TOSHINO KYOKO!` comment (note the space and casing; you can append anything after it at that line). Once this comment is seen by `Ayano`, it stops inserting the file, and appends the actual code you've provided in the block (possibly none). Comment itself is not included: 2
\tbln Ayano blocks can actually interact with the filesystem themselves. The caveat is - where exactly python thinks it's current directory is.
\tbln Well, the rule of thumb turns out to be quite simple:
\begin{itemize}
\item if there is an insertion declared for a block, it's execution will be in the directory that file is in
\item if there is no insertion declared, python will continue it's execution at `kyomato`'s process directory
\end{itemize}5
\tbln Ayano can be used to insert csv tables (possibly generated at runtime too). Here's a special syntax for it:
\begin{table}[h!]
\fnt
\begin{center}
\begin{tabular}{|c|c|c|c|c|}\hline
First Name & Last Name & Age & Salary & Tenacity\\ \hline
\hline
Wilson & Bennett & 19 & 5639 & 97155\\ \hline
Hailey & Robinson & 21 & 8419 & 68689\\ \hline
Edith & Morris & 24 & 8134 & 10937\\ \hline
Adison & Brown & 20 & 6131 & 41745\\ \hline
Vivian & Cunningham & 22 & 7834 & 35514\\ \hline
Heather & Roberts & 24 & 4881 & 73425\\ \hline
Michael & Armstrong & 23 & 5781 & 30899\\ \hline
Miranda & Martin & 19 & 8283 & 33619\\ \hline
Alexander & Kelley & 29 & 1990 & 98055\\ \hline
Chloe & Spencer & 24 & 779 & 89818\\ \hline
Sam & Johnston & 25 & 1147 & 71225\\ \hline
Alissa & Spencer & 18 & 3877 & 64965\\ \hline
Abraham & Kelley & 20 & 1622 & 65308\\ \hline
Edwin & Stewart & 20 & 4376 & 01364\\ \hline
Adam & Grant & 19 & 9616 & 38385\\ \hline
\end{tabular}
\stepcounter{tabnum}
\caption{\capfnt cred: \href{http://randat.com/}{randat}}
\label{tab:random_table1}
\end{center}
\end{table}
\tb This can be combined with insertion blocks to achieve generated table insertion; for `csv-table` syntax, please always specify path relative to the inserted script folder:
\begin{table}[h!]
\fnt
\begin{center}
\begin{tabular}{|c|c|c|c|}\hline
name & value & err & \%\\ \hline
\hline
tau & 4.3535223 & 0.0034342 & 8e-06\\ \hline
pi & 4334.34434 & 10 & 2.3e-05\\ \hline
chi & -2332.332424 & 1e-07 & -0.0\\ \hline
zeta & 4444.0 & 0.033 & 0.0\\ \hline
\end{tabular}
\stepcounter{tabnum}\end{center}
\end{table}
\tb ... but you've not forced to do that; you may do execution-relative path too:
\begin{table}[h!]
\fnt
\begin{center}
\begin{tabular}{|c|c|c|c|}\hline
name & value & err & \%\\ \hline
\hline
tau & 4.3535223 & 0.0034342 & 8e-06\\ \hline
pi & 4334.34434 & 10 & 2.3e-05\\ \hline
chi & -2332.332424 & 1e-07 & -0.0\\ \hline
zeta & 4444.0 & 0.033 & 0.0\\ \hline
\end{tabular}
\stepcounter{tabnum}\end{center}
\end{table}
\tb An of course, once data was already generated (it happened twice here, twice, in fact), you can insert it without any script:
\begin{table}[h!]
\fnt
\begin{center}
\begin{tabular}{|c|c|c|c|}\hline
name & value & err & \%\\ \hline
\hline
tau & 4.3535223 & 0.0034342 & 8e-06\\ \hline
pi & 4334.34434 & 10 & 2.3e-05\\ \hline
chi & -2332.332424 & 1e-07 & -0.0\\ \hline
zeta & 4444.0 & 0.033 & 0.0\\ \hline
\end{tabular}
\stepcounter{tabnum}\end{center}
\end{table}
\tb You can also restrict the columns and rows you want, for example:
\begin{table}[h!]
\fnt
\begin{center}
\begin{tabular}{|c|}\hline
name\\ \hline
\hline
chi\\ \hline
\end{tabular}
\stepcounter{tabnum}
\caption{\capfnt That's a-bit-cut table!}
\end{center}
\end{table}
\tb There's also a special columns syntax to display some columns in value-error format:
\begin{table}[h!]
\fnt
\begin{center}
\begin{tabular}{|c|c|c|}\hline
name & value & \%\\ \hline
\hline
tau & 4.354$\pm$0.003 & 8e-06\\ \hline
pi & 4334$\pm$10 & 2.3e-05\\ \hline
chi & -2332.33242400$\pm$0.00000010 & -0.0\\ \hline
zeta & 4444.00$\pm$0.03 & 0.0\\ \hline
\end{tabular}
\stepcounter{tabnum}
\caption{\capfnt That's a table with value-error formatting!}
\end{center}
\end{table}
\tb That's pretty much it, regarding tables.
\tbln Ayano also allows you to include dynamically-generated figures. You can go two ways about that:
\subsubsection{1st way}
\tb Generate a figure in a static block, and save it somewhere. After that, include it as an image:
\begin{figure}[h!]
\centering
\includegraphics[width=0.9\textwidth]{source/sine_graph.png}
\caption{\capfnt that's a nice little sine graph!}
\label{fig:sine}
\end{figure}
\subsubsection{2nd way (more idiomatic, in my opinion)}
\tb Generate a figure in function block, and insert it with a special syntax:
\begin{figure}[h!]
\centering
\includegraphics[width=0.9\textwidth]{./source/sine_graph.png}
\caption{\capfnt That's another way to include a figure from Ayano!}
\label{fig:other_sine_graph}
\end{figure}
\tb The only real upside I can see here - you can generate-and-include figures with prior-unknown names or locations
\tbln Oh, and you also can specify width of the figure this way, if it matters to you, that is
\tbln And of course you can generate a figure with inserted block too!
\begin{figure}[h!]
\centering
\includegraphics[width=0.2\textwidth]{./source/other_sine_graph.png}
\caption{\capfnt The same sine. Again.}
\label{fig:yet_another_sine_graph}
\end{figure}
\tb Note, that in the last example, script actually saves sine into a folder near input markdown fine, and yet `Kyomato` will be able to find it (if configured properly, that is)
\tbln Note that essentially all of the paths used to identify files, are specified with quotes. That's because it can actually be dynamic, and determined at runtime!
\begin{figure}[h!]
\centering
\includegraphics[width=0.2\textwidth]{source/other_sine_graph.png}
\caption{\capfnt The same sine. Again.}
\label{fig:yet_another_sine_graph}
\end{figure}
\begin{table}[h!]
\fnt
\begin{center}
\begin{tabular}{|c|c|c|c|}\hline
name & value & err & \%\\ \hline
\hline
tau & 4.3535223 & 0.0034342 & 8e-06\\ \hline
pi & 4334.34434 & 10 & 2.3e-05\\ \hline
chi & -2332.332424 & 1e-07 & -0.0\\ \hline
zeta & 4444.0 & 0.033 & 0.0\\ \hline
\end{tabular}
\stepcounter{tabnum}\end{center}
\end{table}
\tb Note, of course, that this removes `Kyomato`'s file validity checking and other path processing. So if you happen to resort to that, make sure that Python is able to find the file by themselves, from the directory it executes in.
\begin{figure}[h!]
\centering
\includegraphics[width=0.5\textwidth]{images/nomi.jpg}
\caption{\capfnt We've actually completed Nomi some time ago, lol}
\label{fig:nomi}
\end{figure}
\subsection{Code blocks}
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
e = 1.0
f = 1.0
for i in range(1, 100):
f /= i
e += f
not_pi = e
\end{minted}
That's a way to calculate pi \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
e = 1.0
f = 1.0
for i in range(1, 100):
f /= i
e += f
from math import pi
f"e = {e}, pi = {pi}, pi != {not_pi}"
\end{minted}
Ooops, that actually turns out to be a formula for e :( \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
# You will see this comment
x = 2
# And you'll see this one
# you'll see this comment, and the 2
x
\end{minted}
the all-mighty \textbf{2}!!! \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
three = None
with open("3", "r") as f:
three = int(f.readline())
\end{minted}
Here you can see an empty static block displayed as inserted script \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
two = None
with open("../2", "r") as f:
two = int(f.readline())
\end{minted}
You can clearly see that this block has no insert statement, yet it knows \textbf{exactly} where to find the file \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
two + three
\end{minted}
This block here uses variables prior-defined in static blocks. \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
@csv_table: src="../data/20240327230336_8798.csv", caption="cred: [randat](http://randat.com/)", ident = "random_table1"
\end{minted}
table insertion example \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
import csv
# let's just create a bunch of variables and fill a local generated table with them
with open("calculations_table.csv", "w") as f:
writer = csv.DictWriter(
f,
fieldnames=[
"name",
"value",
"err",
"%",
],
quoting=csv.QUOTE_NONNUMERIC,
delimiter=" ",
quotechar="'",
)
writer.writeheader()
tau = (4.3535223, 0.0034342)
pi = (4334.34434, 10)
chi = (-2332.332424, 1e-7)
zeta = (4444.00, 0.033)
def write_var(writer, val, name):
writer.writerow(
{
"name": name,
"value": val[0],
"err": val[1],
"%": round(val[1] / val[0] / 100, ndigits=6),
}
)
write_var(writer, tau, 'tau')
write_var(writer, pi, 'pi')
write_var(writer, chi, 'chi')
write_var(writer, zeta, 'zeta')
# say, we do something undesirable in this script next, and we do not want kyomato to execute that
@csv_table: src="calculations_table.csv"
\end{minted}
An example of runtime-generated table insertion \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
import csv
# let's just create a bunch of variables and fill a local generated table with them
with open("calculations_table.csv", "w") as f:
writer = csv.DictWriter(
f,
fieldnames=[
"name",
"value",
"err",
"%",
],
quoting=csv.QUOTE_NONNUMERIC,
delimiter=" ",
quotechar="'",
)
writer.writeheader()
tau = (4.3535223, 0.0034342)
pi = (4334.34434, 10)
chi = (-2332.332424, 1e-7)
zeta = (4444.00, 0.033)
def write_var(writer, val, name):
writer.writerow(
{
"name": name,
"value": val[0],
"err": val[1],
"%": round(val[1] / val[0] / 100, ndigits=6),
}
)
write_var(writer, tau, 'tau')
write_var(writer, pi, 'pi')
write_var(writer, chi, 'chi')
write_var(writer, zeta, 'zeta')
# say, we do something undesirable in this script next, and we do not want kyomato to execute that
@csv_table: src="../data/calculations_table.csv"
\end{minted}
An example of runtime-generated table insertion with execution-relative path \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
@csv_table: src="../data/calculations_table.csv"
\end{minted}
An example of runtime-generated table insertion with execution-relative path \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
@csv_table: src="../data/calculations_table.csv", rows = 3..4, columns = ["name"], caption = "That's a-bit-cut table!"
\end{minted}
This is only a part of the table above, due to specified column and row restrictions \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
@csv_table: src="../data/calculations_table.csv", columns = ["name", ("value", "err"), "%"], caption = "That's a table with value-error formatting!"
\end{minted}
You can see values having error right beside them, and formatted accordingly! \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
import numpy as np
import matplotlib.pyplot as plt
from math import pi
x = np.arange(-pi, pi, 0.01)
y = np.sin(x)
plt.plot(x, y)
plt.savefig("sine_graph.png")
\end{minted}
some figure generation \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
import numpy as np
import matplotlib.pyplot as plt
from math import pi
x = np.arange(-pi, pi, 0.01)
y = np.sin(x)
plt.plot(x, y)
plt.savefig("sine_graph.png")
@fig: src = "sine_graph.png", ident = "other_sine_graph", caption = "That's another way to include a figure from Ayano!", width = 0.9
\end{minted}
that's a figure insertion syntax, all-in-one! \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
import numpy as np
import matplotlib.pyplot as plt
from math import pi
x = np.arange(-pi, pi, 0.01)
y = np.sin(x)
plt.plot(x, y)
plt.savefig("../source/other_sine_graph.png")
# let's say, that there's some debugging going on here:
@fig: src = "other_sine_graph.png", ident = "yet_another_sine_graph", caption = "The same sine. Again.", width = 0.2
\end{minted}
That's a third way to do the same thing! \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
fig_path= 'other_sine_graph.png'
@fig: src = fig_path, ident = "yet_another_sine_graph", caption = "The same sine. Again.", width = 0.2
\end{minted}
\tb ayano-unnamed \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
tab_path = '../data/calculations_table.csv'
@csv_table: src=tab_path
\end{minted}
\tb ayano-unnamed \\
\end{document}

View file

@ -1,5 +0,0 @@
e = 1.0
f = 1.0
for i in range(1, 100):
f /= i
e += f

View file

@ -1,12 +0,0 @@
import numpy as np
import matplotlib.pyplot as plt
from math import pi
x = np.arange(-pi, pi, 0.01)
y = np.sin(x)
plt.plot(x, y)
plt.savefig("../source/other_sine_graph.png")
# let's say, that there's some debugging going on here:
# TOSHINO KYOKO!
print("I love beeeeans")
exit(-2222222)

View file

@ -1,7 +0,0 @@
# You will see this comment
x = 2
# And you'll see this one
# TOSHINO KYOKO!
# But not this one
x = 3
# And the x variable will still be 2

View file

@ -1,131 +0,0 @@
This is an example of advanced `Ayano` features.
First, there are insert blocks. These can load some code from specified python script, and insert it at the start:
```python, Ayano ! ~ ../scripts/calculate_e.py * "That's a way to calculate pi"
not_pi = e
```
Note that their displayed version at the end actually includes the loaded script. These blocks can be used for display too:
```python, Ayano ~ ../scripts/calculate_e.py * "Ooops, that actually turns out to be a formula for e :("
from math import pi
f"e = {e}, pi = {pi}, pi != {not_pi}"
```
Sometimes you wish to only include part of the script, and do something else after some point. To achieve that, you can include `# TOSHINO KYOKO!` comment (note the space and casing; you can append anything after it at that line). Once this comment is seen by `Ayano`, it stops inserting the file, and appends the actual code you've provided in the block (possibly none). Comment itself is not included:
```python, Ayano ~ ../scripts/toshino_kyoko.py * "the all-mighty **2**!!!"
# you'll see this comment, and the 2
x
```
Ayano blocks can actually interact with the filesystem themselves. The caveat is - where exactly python thinks it's current directory is.
Well, the rule of thumb turns out to be quite simple:
- if there is an insertion declared for a block, it's execution will be in the directory that file is in
- if there is no insertion declared, python will continue it's execution at `kyomato`'s process directory
```python, Ayano ! ~ ../data/read3.py * "Here you can see an empty static block displayed as inserted script"
```
```python, Ayano ! * "You can clearly see that this block has no insert statement, yet it knows **exactly** where to find the file"
two = None
with open("../2", "r") as f:
two = int(f.readline())
```
```python, Ayano * "This block here uses variables prior-defined in static blocks."
two + three
```
Ayano can be used to insert csv tables (possibly generated at runtime too). Here's a special syntax for it:
```python, Ayano * "table insertion example"
@csv_table: src="../data/20240327230336_8798.csv", caption="cred: [randat](http://randat.com/)", ident = "random_table1"
```
This can be combined with insertion blocks to achieve generated table insertion; for `csv-table` syntax, please always specify path relative to the inserted script folder:
```python, Ayano * "An example of runtime-generated table insertion" ~ ../data/doing_stuff.py
@csv_table: src="calculations_table.csv"
```
... but you've not forced to do that; you may do execution-relative path too:
```python, Ayano * "An example of runtime-generated table insertion with execution-relative path" ~ ../data/doing_stuff.py
@csv_table: src="../data/calculations_table.csv"
```
An of course, once data was already generated (it happened twice here, twice, in fact), you can insert it without any script:
```python, Ayano * "An example of runtime-generated table insertion with execution-relative path"
@csv_table: src="../data/calculations_table.csv"
```
You can also restrict the columns and rows you want, for example:
```python, Ayano * "This is only a part of the table above, due to specified column and row restrictions"
@csv_table: src="../data/calculations_table.csv", rows = 3..4, columns = ["name"], caption = "That's a-bit-cut table!"
```
There's also a special columns syntax to display some columns in value-error format:
```python, Ayano * "You can see values having error right beside them, and formatted accordingly!"
@csv_table: src="../data/calculations_table.csv", columns = ["name", ("value", "err"), "%"], caption = "That's a table with value-error formatting!"
```
That's pretty much it, regarding tables.
Ayano also allows you to include dynamically-generated figures. You can go two ways about that:
### 1st way
Generate a figure in a static block, and save it somewhere. After that, include it as an image:
```python, Ayano ! * "some figure generation"
import numpy as np
import matplotlib.pyplot as plt
from math import pi
x = np.arange(-pi, pi, 0.01)
y = np.sin(x)
plt.plot(x, y)
plt.savefig("sine_graph.png")
```
![[sine_graph.png]]
{caption = "that's a nice little sine graph!", ref = sine}
### 2nd way (more idiomatic, in my opinion)
Generate a figure in function block, and insert it with a special syntax:
```python, Ayano * "that's a figure insertion syntax, all-in-one!"
import numpy as np
import matplotlib.pyplot as plt
from math import pi
x = np.arange(-pi, pi, 0.01)
y = np.sin(x)
plt.plot(x, y)
plt.savefig("sine_graph.png")
@fig: src = "sine_graph.png", ident = "other_sine_graph", caption = "That's another way to include a figure from Ayano!", width = 0.9
```
The only real upside I can see here - you can generate-and-include figures with prior-unknown names or locations
Oh, and you also can specify width of the figure this way, if it matters to you, that is
And of course you can generate a figure with inserted block too!
```python, Ayano * "That's a third way to do the same thing!" ~ ../scripts/generate_sine.py
@fig: src = "other_sine_graph.png", ident = "yet_another_sine_graph", caption = "The same sine. Again.", width = 0.2
```
Note, that in the last example, script actually saves sine into a folder near input markdown fine, and yet `Kyomato` will be able to find it (if configured properly, that is)
Note that essentially all of the paths used to identify files, are specified with quotes. That's because it can actually be dynamic, and determined at runtime!
```python, Ayano *
fig_path= 'other_sine_graph.png'
@fig: src = fig_path, ident = "yet_another_sine_graph", caption = "The same sine. Again.", width = 0.2
```
```python, Ayano *
tab_path = '../data/calculations_table.csv'
@csv_table: src=tab_path
```
Note, of course, that this removes `Kyomato`'s file validity checking and other path processing. So if you happen to resort to that, make sure that Python is able to find the file by themselves, from the directory it executes in.
![[nomi.jpg]]
{ref= nomi, caption = "We've actually completed Nomi some time ago, lol", width= 0.5}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View file

@ -1,41 +0,0 @@
import os
___cwd___ = os.getcwd()
os.chdir('scripts')
e = 1.0
f = 1.0
for i in range(1, 100):
f /= i
e += f
not_pi = e
os.chdir(___cwd___)
def f_10738591751140221069():
e = 1.0
f = 1.0
for i in range(1, 100):
f /= i
e += f
from math import pi
return f"e = {e}, pi = {pi}, pi != {not_pi}"
def f_13523737368002237989():
# You will see this comment
x = 2
# And you'll see this one
# you'll see this comment, and the 2
return x
import os
___cwd___ = os.getcwd()
os.chdir('data')
three = None
with open("3", "r") as f:
three = int(f.readline())
os.chdir(___cwd___)
import os
___cwd___ = os.getcwd()
os.chdir('')
two = None
with open("2", "r") as f:
two = int(f.readline())
os.chdir(___cwd___)
def f_5124826474091756568():
return two + three

View file

@ -1,56 +0,0 @@
Here's a basic Ayano explanation:
Essentially, Ayano blocks are regular code blocks, having `python, Ayano` marked as their language. This sort of codeblocks are treated completely differently by the parser - these blocks are supposed to allow arbitrary python code execution for data generation and analysis.
For this example, we'll only consider examples that do not interact with the filesystem.
Say, you have some sort of dataset, you wish to find a mean a deviation of. Here's a way to do that with Ayano (without filesystem interaction):
```python, Ayano ! * "That's a static block, declaring initial dataset"
# first, you declare the dataset itself in a *static* Ayano block
# (note the exclamation mark above)
data = [1, 1, 2, 3, 2, 5, 5, 6, 2, 1, 4, 9]
# Static block essentially translated into a code initially executed by python, when the entire module it loaded.
# Keep that in mind: ALL changes introduced by static blocks will be visible by ALL function blocks.
```
You might notice, that there's actually nothing displayed above in the document. That's due to *static blocks* leaving nothing behind them. To actually display the dataset, we would use a regular, non-static Ayano block:
```python, Ayano
data
```
Above you should see a python's `str` representation of initial dataset.
Now, let's try calculating mean and the deviation. To achieve that, I'll use `statistics` module:
```python, Ayano * "mean and deviation calculations"
from statistics import stdev, mean
mean_val = mean(data)
deviation = stdev(data)
@dev: mean_val, deviation
```
You should see dataset's mean value and deviation above. You might notice, that's it's in a good-looking format. That's because of special format used for value-deviation sort-of-values, that's described in the docs. If you wish to circumvent it, you absolutely can:
```python, Ayano * "mean and deviation calculations, but without value-error formatting"
from statistics import stdev, mean
mean_val = mean(data)
deviation = stdev(data)
f"{mean_val} $\pm$ {deviation}"
```
Ayano can generate tables too! For example, let's generate a table of our dataset, but also add information on if it's greater or less than the mean value, it's square and absolute deviation from the mean:
```python, Ayano * "that chaotic table generation example"
# look at the last line first
# that's a table generation syntax - it involves a cell generator, number of rows, number of columns and optional ident and caption arguments
# you could use python's function as generator too (a general requirement is for the thing to be callable with two integer args)
from statistics import mean
from math import sqrt
header = ["initial data", "cmp to mean", "square", "absdev"]
mean_val = mean(data)
cells = list([[("err", val, sqrt(val)), "greater" if val >= mean_val else "less", val * val, abs(val - mean_val)] for val in data])
@gen_table: lambda r,c: header[c] if r == 0 else cells[r-1][c]; rows = 13, columns = 4, ident="crazy_table", caption = "A crazy table containing a bunch of stuff"
```
Above you can see that crazy table, and you also can refer to it ([@tab:crazy_table]).
That's pretty much it for Ayano blocks that do not access the filesystem.

View file

@ -1,24 +0,0 @@
fn main() {
//! This is ayano-involving example of programmatic output generation
//!
//! You might want to use CLI interface instead
// that's a utility setup call; essentially, it reads input from one file, and opens output file for writing
let (input, mut output) = examples_util::setup_io(
"examples/basic_ayano/input.md",
"examples/basic_ayano/output.tex",
)
.unwrap();
// construct path engine
// in this example, no external files are accessed (i.e. there are no images or special Ayano syntax)
// so it's ok to pass in a blank primitive engine
let path_engine = kyomato_lib::path_engine::primitive_dbg();
// _dbg stands for "informative error", just in case
// parse input and write to output
kyomato_lib::LabaLatex::parse_and_write(path_engine, &input, &mut output)
// this part is just for debug purposes
.map_err(|err| (err, output))
.unwrap();
}

View file

@ -1,218 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE
\LARGE
\vspace{100pt}
\LARGE
\Large
\Large
\Large
\huge
\vspace{50pt}
\large
\large
\large
\large
\vspace*{\fill}
\hspace{\fill}\large \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt
Here's a basic Ayano explanation:
\tbln Essentially, Ayano blocks are regular code blocks, having `python, Ayano` marked as their language. This sort of codeblocks are treated completely differently by the parser - these blocks are supposed to allow arbitrary python code execution for data generation and analysis.
\tbln For this example, we'll only consider examples that do not interact with the filesystem.
\tbln Say, you have some sort of dataset, you wish to find a mean a deviation of. Here's a way to do that with Ayano (without filesystem interaction):
\tbln You might notice, that there's actually nothing displayed above in the document. That's due to \textit{static blocks} leaving nothing behind them. To actually display the dataset, we would use a regular, non-static Ayano block: \\
[0.0pt][1, 1, 2, 3, 2, 5, 5, 6, 2, 1, 4, 9]
\tbln Above you should see a python's `str` representation of initial dataset.
\tbln Now, let's try calculating mean and the deviation. To achieve that, I'll use `statistics` module: 3.4$\pm$2.5
\tbln You should see dataset's mean value and deviation above. You might notice, that's it's in a good-looking format. That's because of special format used for value-deviation sort-of-values, that's described in the docs. If you wish to circumvent it, you absolutely can: 3.4166666666666665 $\pm$ 2.4664414311581235
\tbln Ayano can generate tables too! For example, let's generate a table of our dataset, but also add information on if it's greater or less than the mean value, it's square and absolute deviation from the mean:
\begin{table}[h!]
\fnt
\begin{center}
\begin{tabular}{|c|c|c|c|}\hline
initial data & cmp to mean & square & absdev\\ \hline
\hline
1.0$\pm$1.0 & less & 1 & 2.4166666666666665\\ \hline
1.0$\pm$1.0 & less & 1 & 2.4166666666666665\\ \hline
2.0$\pm$1.4 & less & 4 & 1.4166666666666665\\ \hline
3.0$\pm$1.7 & less & 9 & 0.4166666666666665\\ \hline
2.0$\pm$1.4 & less & 4 & 1.4166666666666665\\ \hline
5.0$\pm$2.2 & greater & 25 & 1.5833333333333335\\ \hline
5.0$\pm$2.2 & greater & 25 & 1.5833333333333335\\ \hline
6.0$\pm$2.4 & greater & 36 & 2.5833333333333335\\ \hline
2.0$\pm$1.4 & less & 4 & 1.4166666666666665\\ \hline
1.0$\pm$1.0 & less & 1 & 2.4166666666666665\\ \hline
4.0$\pm$2.0 & greater & 16 & 0.5833333333333335\\ \hline
9$\pm$3 & greater & 81 & 5.583333333333334\\ \hline
\end{tabular}
\stepcounter{tabnum}
\caption{\capfnt A crazy table containing a bunch of stuff}
\label{tab:crazy_table}
\end{center}
\end{table}
\tb Above you can see that crazy table, and you also can refer to it (\ref{tab:crazy_table}).
\tbln That's pretty much it for Ayano blocks that do not access the filesystem.
\subsection{Code blocks}
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
# first, you declare the dataset itself in a *static* Ayano block
# (note the exclamation mark above)
data = [1, 1, 2, 3, 2, 5, 5, 6, 2, 1, 4, 9]
# Static block essentially translated into a code initially executed by python, when the entire module it loaded.
# Keep that in mind: ALL changes introduced by static blocks will be visible by ALL function blocks.
\end{minted}
That's a static block, declaring initial dataset \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
from statistics import stdev, mean
mean_val = mean(data)
deviation = stdev(data)
@dev: mean_val, deviation
\end{minted}
mean and deviation calculations \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
from statistics import stdev, mean
mean_val = mean(data)
deviation = stdev(data)
f"{mean_val} $\pm$ {deviation}"
\end{minted}
mean and deviation calculations, but without value-error formatting \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
# look at the last line first
# that's a table generation syntax - it involves a cell generator, number of rows, number of columns and optional ident and caption arguments
# you could use python's function as generator too (a general requirement is for the thing to be callable with two integer args)
from statistics import mean
from math import sqrt
header = ["initial data", "cmp to mean", "square", "absdev"]
mean_val = mean(data)
cells = list([[("err", val, sqrt(val)), "greater" if val >= mean_val else "less", val * val, abs(val - mean_val)] for val in data])
@gen_table: lambda r,c: header[c] if r == 0 else cells[r-1][c]; rows = 13, columns = 4, ident="crazy_table", caption = "A crazy table containing a bunch of stuff"
\end{minted}
that chaotic table generation example \\
\end{document}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

View file

@ -1,108 +0,0 @@
Here's a quick rundown of supported markdown syntax:
# Headers
###### Up to 6 deep
Inline math: $y = x^2\qquad \text{- is a parabola equation}$
Display math:
$$
\Gamma(z) = \int\limits_{0}^{+\infty} \exp(-t) t^{z-1} dt \qquad \text{- це Euler's Gamma-function definition}
$$
{ref = gamma_function}
Page breaks:
------
This text must appear on the next page
Figures (take note of the caption and ident):
![[loss_meme.png]]
{ref = loss_meme, caption = "These are all loss functions, technically making this image a loss meme", width = 0.4}
Tables:
| Number of appendages | Name |
| :------------------: | :----------: |
| 1 | Monopedal |
| 2 | Bipedal |
| 3 | Tripedal |
| 4 | Quadropedal |
| 5 | Pentapedal |
| 6 | Hexapedal |
| 7 | Heptopedal |
| 8 | Octopedal |
| 9 | why. |
| 1001 | are you sure |
| $\omega_0$ | are you ok? |
| $\zeta_1$ | cracker |
{ref = pedals, caption= "Source: trust **me**"}
Hyper references: [some website](https://www.somewebsite.com)
Footnotes: say, I need to explain this text, but not include the actual explanation right where I write it[^1]
Object references: this refers to table: [@tab:pedals], this refers to figure: [@fig:loss_meme], an this one to the equation at the beginning: [@eq:gamma_function].
Lists are supported too; Here's a bullet list:
- First item
- Second item
- Third item
Here's an enumeration:
1. First item
2. Second item
3. Third item
Here are alphabetic enumerations (latin and cyrillic are only supported, for now)
a. First item
b. Second item
c. Third item
а. First item
б. Second item
в. Third item
Cyrillic implementation detail: наразі у якості порядку елементів береться буквально 'а'..'я'. Тобто, згідно з [таблицею юнікоду](https://en.wikipedia.org/wiki/Cyrillic_(Unicode_block\)), тут поки що реалізовано терористичний алфавіт:
а. Перший елемент
б. Другий елемент
в. Третій елемент
г. Четвертий елемент
д. Шостий елемент (не вистачає ґ)
Це може змінитись у майбутньому, тож краще поки що утриматись від використання кириличних списків.
Finally, code blocks are supported:
```lua
print("Hello lua!")
function fact (n)
if n == 0 or n == 1 then
return 1
else
return n * fact(n-1)
end
end
print(fact(7))
```
```rust
#[repr(transparent)]
#[derive(Debug, derive_more::From, derive_more::Deref, derive_more::DerefMut)]
pub struct Equivalent<T>(pub T);
impl<T> PartialEq for Equivalent<T> {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl<T> Eq for Equivalent<T> {}
// This is a real type used in this project's codebase to allow error comparison for testing
// Although I might decide to remove it in the future
```
These blocks are powered by [minted](https://github.com/gpoore/minted), so to get full list of supported languages, check their docs out.
This is all of the markdown syntax supported for now. However, there's an *otherside* to `kyomato`'s capabilities - namely, `ayano` blocks.
------
[^1]: You can absolutely do this! *Note*, that despite this footnote definition is on a separate page at the end, actual document output includes the footnote in a more convenient spot.

View file

@ -1,33 +0,0 @@
use kyomato_lib::path_engine::EngineExt;
fn main() {
//! This is a simple example of programmatic output generation
//!
//! You might want to use CLI interface instead
// that's a utility setup call; essentially, it reads input from one file, and opens output file for writing
let (input, mut output) = examples_util::setup_io(
"examples/basic_syntax/input.md",
"examples/basic_syntax/output.tex",
)
.unwrap();
// construct path engine
// this thing is responsible for file path resolution in the generated document
let path_engine = kyomato_lib::path_engine::base_gen()
// in our case, there's a dedicated folder for images, so we inform engine about that
.with_image_folder("examples/basic_syntax/images")
// this sort of call is required - with it, you specify an error type the engine will return
// it's a no-op; so you may specify engine's type instead or th
.with_error_type::<kyomato_lib::path_engine::InformativeError>()
// this adds file checking to the engine.
// basically, this will ensure that any file engine happens to refer to, actually exists on the disk
// you may omit this call, if you don't want that
.with_check(kyomato_lib::path_engine::fs_check);
// parse input and write to output
kyomato_lib::LabaLatex::parse_and_write(path_engine, &input, &mut output)
// this part is just for debug purposes
.map_err(|err| (err, output))
.unwrap();
}

View file

@ -1,259 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE
\LARGE
\vspace{100pt}
\LARGE
\Large
\Large
\Large
\huge
\vspace{50pt}
\large
\large
\large
\large
\vspace*{\fill}
\hspace{\fill}\large \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt
Here's a quick rundown of supported markdown syntax:
\section{Headers}
\textbf{Up to 6 deep}
\tb Inline math: $y = x^2\qquad \text{- is a parabola equation}$
\tbln Display math:
\begin{equation}
\Gamma(z) = \int\limits_{0}^{+\infty} \exp(-t) t^{z-1} dt \qquad \text{- це Euler's Gamma-function definition}
\label{eq:gamma_function}
\end{equation}
\tb Page breaks:
\clearpage
\tb This text must appear on the next page
\tbln Figures (take note of the caption and ident):
\begin{figure}[h!]
\centering
\includegraphics[width=0.4\textwidth]{examples/basic_syntax/images/loss_meme.png}
\caption{\capfnt These are all loss functions, technically making this image a loss meme}
\label{fig:loss_meme}
\end{figure}
\tb Tables:
\begin{table}[h!]
\fnt
\begin{center}
\begin{tabular}{|c|c|}\hline
Number of appendages & Name\\ \hline
\hline
1 & Monopedal\\ \hline
2 & Bipedal\\ \hline
3 & Tripedal\\ \hline
4 & Quadropedal\\ \hline
5 & Pentapedal\\ \hline
6 & Hexapedal\\ \hline
7 & Heptopedal\\ \hline
8 & Octopedal\\ \hline
9 & why.\\ \hline
1001 & are you sure\\ \hline
$\omega_0$ & are you ok?\\ \hline
$\zeta_1$ & cracker\\ \hline
\end{tabular}
\stepcounter{tabnum}
\caption{\capfnt Source: trust \textbf{me}}
\label{tab:pedals}
\end{center}
\end{table}
\tb Hyper references: \href{https://www.somewebsite.com/}{some website}
\tbln Footnotes: say, I need to explain this text, but not include the actual explanation right where I write it\footnotemark[1]\footnotetext[1]{You can absolutely do this! \textit{Note}, that despite this footnote definition is on a separate page at the end, actual document output includes the footnote in a more convenient spot.}
\tbln Object references: this refers to table: \ref{tab:pedals}, this refers to figure: \ref{fig:loss_meme}, an this one to the equation at the beginning: \ref{eq:gamma_function}.
\tbln Lists are supported too; Here's a bullet list:
\begin{itemize}
\item First item
\item Second item
\item Third item
\end{itemize}
\tb Here's an enumeration:
\begin{enumerate}
\item First item
\item Second item
\item Third item
\end{enumerate}
\tb Here are alphabetic enumerations (latin and cyrillic are only supported, for now)
\begin{enumerate}
\item[a.] First item
\item[b.] Second item
\item[c.] Third item
\end{enumerate}
\begin{enumerate}
\item[а.] First item
\item[б.] Second item
\item[в.] Third item
\end{enumerate}
\tb Cyrillic implementation detail: наразі у якості порядку елементів береться буквально 'а'..'я'. Тобто, згідно з \href{https://en.wikipedia.org/wiki/Cyrillic_(Unicode_block)}{таблицею юнікоду}, тут поки що реалізовано терористичний алфавіт:
\begin{enumerate}
\item[а.] Перший елемент
\item[б.] Другий елемент
\item[в.] Третій елемент
\item[г.] Четвертий елемент
\item[д.] Шостий елемент (не вистачає ґ)
\end{enumerate}
\tb Це може змінитись у майбутньому, тож краще поки що утриматись від використання кириличних списків.
\tbln Finally, code blocks are supported:
\begin{minted}[linenos, mathescape, autogobble, breaklines]{lua}
print("Hello lua!")
function fact (n)
if n == 0 or n == 1 then
return 1
else
return n * fact(n-1)
end
end
print(fact(7))
\end{minted}
\begin{minted}[linenos, mathescape, autogobble, breaklines]{rust}
#[repr(transparent)]
#[derive(Debug, derive_more::From, derive_more::Deref, derive_more::DerefMut)]
pub struct Equivalent<T>(pub T);
impl<T> PartialEq for Equivalent<T> {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl<T> Eq for Equivalent<T> {}
// This is a real type used in this project's codebase to allow error comparison for testing
// Although I might decide to remove it in the future
\end{minted}
\tb These blocks are powered by \href{https://github.com/gpoore/minted}{minted}, so to get full list of supported languages, check their docs out.
\tbln This is all of the markdown syntax supported for now. However, there's an \textit{otherside} to `kyomato`'s capabilities - namely, `ayano` blocks.
\clearpage
\end{document}

View file

@ -1,23 +0,0 @@
---
header-line1: Open source community
header-line2: rustacean division
document-type: ARTICLE
title-line1: on correct programming socks selection
title-line2: to post at undisclosed subreddit
title-line3: (practical guide)
title-line4: Part 2: coping with your actual biological gender
author-line1: authored:
author-line2: Dzuchun at all or th, idk what that means lol
author-line3: (perfectly still)
date: Mashle 2024
prof: me, I guess
code-section-title: Code blocks
---
# This is a complete disaster
In the source above, you can see an example of title info. The fields defined there can be of any form, and any of them can be omitted.
I have no idea what `yaml` actually looks like, so that's probably not and actual `yaml`, but a curious format to supply some text to `Kyomato`.
The fields list might expand in the future.

View file

@ -1,24 +0,0 @@
fn main() {
//! This is title info showing example of programmatic output generation
//!
//! You might want to use CLI interface instead
// that's a utility setup call; essentially, it reads input from one file, and opens output file for writing
let (input, mut output) = examples_util::setup_io(
"examples/title_info/input.md",
"examples/title_info/output.tex",
)
.unwrap();
// construct path engine
// in this example, no external files are accessed (i.e. there are no images or special Ayano syntax)
// so it's ok to pass in a blank primitive engine
let path_engine = kyomato_lib::path_engine::primitive_dbg();
// _dbg stands for "informative error", just in case
// parse input and write to output
kyomato_lib::LabaLatex::parse_and_write(path_engine, &input, &mut output)
// this part is just for debug purposes
.map_err(|err| (err, output))
.unwrap();
}

View file

@ -1,143 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE Open source community
\LARGE rustacean division
\vspace{100pt}
\LARGE ARTICLE
\Large on correct programming socks selection
\Large to post at undisclosed subreddit
\Large (practical guide)
\huge Part 2: coping with your actual biological gender
\vspace{50pt}
\large authored:
\large Dzuchun at all or th, idk what that means lol
\large (perfectly still)
\large Mashle 2024
\vspace*{\fill}
\hspace{\fill}\large me, I guess \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt
\section{This is a complete disaster}
\tb In the source above, you can see an example of title info. The fields defined there can be of any form, and any of them can be omitted.
\tbln I have no idea what `yaml` actually looks like, so that's probably not and actual `yaml`, but a curious format to supply some text to `Kyomato`.
\tbln The fields list might expand in the future.
\end{document}

View file

@ -1,11 +0,0 @@
[package]
name = "kyomato"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.5.4", features = ["derive"] }
derive_more = "0.99.17"
kyomato-lib = { version = "0.1.0", path = ".." }

View file

@ -1,277 +0,0 @@
use std::{
io::{Read, Write},
path::{Path, PathBuf},
};
use clap::{Parser, ValueEnum};
use kyomato_lib::path_engine::{
base_gen, fs_check, EngineError, EngineExt, InformativeError, MarkerError, PathEngine,
};
#[derive(Debug, ValueEnum, Clone, Default, derive_more::Display)]
enum Generator {
#[default]
#[display(fmt = "lab-protocol")]
LabProtocol,
#[display(fmt = "compact")]
Compact,
}
#[derive(Debug, Parser)]
#[command(
author,
version,
about,
long_about = "A cli interface for Kyomato Markdown->Latex transformer"
)]
struct Config {
/// Generator to be used
#[arg(short, long, default_value_t = Generator::LabProtocol)]
generator: Generator,
/// File to read input from. If omitted, `stdin` will be used
#[arg(short, long = "in")]
input_file: Option<PathBuf>,
/// Sets a dedicated image directory. If omitted, "KYOMATO_IMAGES" envvar will be checked.
#[arg(short, long = "im")]
image_directory: Option<PathBuf>,
/// Sets path engine errors to `InformativeError`, providing a breakdown on path search
#[arg(long)]
debug_paths: bool,
/// Current directory a valid path for files. If provided without a value, will get current directory from environment.
///
/// This is intended to be set a directory of the initial command caller, in case they rely on some file local to them personally
#[arg(long = "exec")]
execution_directory: Option<PathBuf>,
}
fn main() {
// parse the arguments
let mut config = Config::parse();
if config.image_directory.is_none() {
config.image_directory = std::env::var_os("KYOMATO_IMAGES").map(PathBuf::from);
}
// read the input
let mut input = String::new();
let input_file_directory = config
.input_file
.as_deref()
.map(|path| path.parent().unwrap_or(Path::new("./")).to_owned());
if let Some(input_file) = config.input_file {
input = std::fs::read_to_string(input_file).expect("Error while reading input from file");
} else {
std::io::stdin()
.read_to_string(&mut input)
.expect("Error while reading input from stdin");
}
// now, about path engine...
// their behavior is *encoded* in types, so all of them will have a different type
// I wouldn't like to build them with trait objects, since
// 1. this will add dynamic dispatch at each nested call (a lot)
// 2. there's no option to do that anyway, as error type is a generic argument of the trait
// so...
// yes, this IS ugly and results in much bigger binary
// but it's static dispatch everywhere, yay!
// tested:
// - on lowsize, single branch is 2.4M against 2.6M with all branches
// - on release, single branch is 8.1M against 8.6M with all branches
// so it does indeed has an impact, but it's like 500KB :idk:
match (
config.debug_paths,
config.execution_directory,
input_file_directory,
config.image_directory,
) {
(false, None, None, None) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_ayano()
.with_error_type::<MarkerError>(),
config.generator,
),
(false, None, None, Some(image_directory)) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_image_folder(image_directory)
.with_ayano()
.with_error_type::<MarkerError>(),
config.generator,
),
(false, None, Some(input_file_dir), None) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_source_folder(input_file_dir)
.with_ayano()
.with_error_type::<MarkerError>(),
config.generator,
),
(false, None, Some(input_file_dir), Some(image_directory)) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_source_folder(input_file_dir)
.with_image_folder(image_directory)
.with_ayano()
.with_error_type::<MarkerError>(),
config.generator,
),
(false, Some(execution_dir), None, None) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_execution_folder(execution_dir)
.with_ayano()
.with_error_type::<MarkerError>(),
config.generator,
),
(false, Some(execution_dir), None, Some(image_directory)) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_execution_folder(execution_dir)
.with_image_folder(image_directory)
.with_ayano()
.with_error_type::<MarkerError>(),
config.generator,
),
(false, Some(execution_dir), Some(input_file_dir), None) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_execution_folder(execution_dir)
.with_source_folder(input_file_dir)
.with_ayano()
.with_error_type::<MarkerError>(),
config.generator,
),
(false, Some(execution_dir), Some(input_file_dir), Some(image_directory)) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_execution_folder(execution_dir)
.with_source_folder(input_file_dir)
.with_image_folder(image_directory)
.with_ayano()
.with_error_type::<MarkerError>(),
config.generator,
),
(true, None, None, None) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_ayano()
.with_error_type::<InformativeError>(),
config.generator,
),
(true, None, None, Some(image_directory)) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_image_folder(image_directory)
.with_ayano()
.with_error_type::<InformativeError>(),
config.generator,
),
(true, None, Some(input_file_dir), None) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_source_folder(input_file_dir)
.with_ayano()
.with_error_type::<InformativeError>(),
config.generator,
),
(true, None, Some(input_file_dir), Some(image_directory)) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_source_folder(input_file_dir)
.with_image_folder(image_directory)
.with_ayano()
.with_error_type::<InformativeError>(),
config.generator,
),
(true, Some(execution_dir), None, None) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_execution_folder(execution_dir)
.with_ayano()
.with_error_type::<InformativeError>(),
config.generator,
),
(true, Some(execution_dir), None, Some(image_directory)) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_execution_folder(execution_dir)
.with_image_folder(image_directory)
.with_ayano()
.with_error_type::<InformativeError>(),
config.generator,
),
(true, Some(execution_dir), Some(input_file_dir), None) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_execution_folder(execution_dir)
.with_source_folder(input_file_dir)
.with_ayano()
.with_error_type::<InformativeError>(),
config.generator,
),
(true, Some(execution_dir), Some(input_file_dir), Some(image_directory)) => main_inner(
input,
base_gen()
.with_check(fs_check)
.with_execution_folder(execution_dir)
.with_source_folder(input_file_dir)
.with_image_folder(image_directory)
.with_ayano()
.with_error_type::<InformativeError>(),
config.generator,
),
}
}
fn main_inner<PEErr: EngineError, PE: PathEngine<PEErr>>(
input: String,
path_engine: PE,
generator: Generator,
) {
// we'll always write into stdout
// std opting-out was relevant due to directory of source file should be considered by path engine
// output can go anywhere and nothing should depend on it
// also, I'll use buffered writer, cause idk seems fitting
let output = std::io::BufWriter::new(std::io::stdout());
// that's a transformer, since `Kyomato` works with `std::fmt::Write`
// it's able to recover IO errors, you'll see
let mut fmt_output = kyomato_lib::FmtToIo::new(output);
let res = match generator {
Generator::LabProtocol => {
kyomato_lib::LabaLatex::parse_and_write(path_engine, &input, &mut fmt_output)
}
Generator::Compact => {
kyomato_lib::Generator::parse_and_write(path_engine, &input, &mut fmt_output)
}
};
if let Err(error) = res {
panic!("Failed to write output; Error debug: {error:?}\nError display: {error}\n");
}
// now, try recovering errors from that fancy writer
let (mut output, errors) = fmt_output.into_parts();
let mut errors = errors.into_iter();
if let Some(first_error) = errors.next() {
// if there were any errors, errprint them, and terminate (panic)
eprintln!("{first_error:?}");
for error in errors {
eprintln!("{error:?}");
}
panic!("There were writing errors");
}
// lastly, explicitly flush the buffered writer, as it's advised by it's docs
output.flush().expect("Error while flushing the output");
}

View file

@ -1,18 +0,0 @@
[package]
name = "kyomato-core"
version = "0.1.0"
edition = "2021"
[dependencies]
deranged = "0.3.11"
derive-getters = "0.5.0"
derive_more = { version = "1.0.0", features = ["deref", "display", "is_variant", "try_unwrap"], default_features = false }
tracing = { version = "0.1.40", default-features = false, features = [ "attributes", "log" ] }
kyomato-util = { path = "../kyomato-util/" }
miden-miette = { version = "7.1.1", default-features = false, features = ["derive"] }
miden-thiserror = { version = "1.0.59", default_features = false }
[features]
default = []
ayano = []
ayano-python = [ "ayano" ]

View file

@ -1,51 +0,0 @@
#![no_std]
extern crate alloc;
extern crate miden_miette as miette;
extern crate miden_thiserror as thiserror;
/// Here's a yet-another token redesign.
///
/// Several months later, I decide to finally ditch the idea of tokens directly referring to or
/// owning underlying text in any way. This sure would make them reliable and stuff, but tokens
/// just noting the position in the source instead of actually owning it, opens a lot of
/// possibilities.
///
/// I have no idea why tokens were essentially even called that. Instead, they are "objects", or
/// "generation points", etc.
///
/// From now on, they are to be called `Selci` ("selshi", lojban for cell).
///
/// All `Selci` are Collected into a `SelciTree` which represents a valid set of interrefering
/// `Selci`. `Selci` cannot be modified after insertion into the tree.
///
/// `SelciTree` is essentially immutable, and can only be modified by offsetting contained `Selci`,
/// attaching another `SelciTree` in the end and a couple more that don't disrupt `SelciTree`
/// structure.
///
/// `Selci` presented to the user code *appear* to have code references, but it's only as illution.
/// To create a `Selci` user code **must** use `SelciTree` methods that involve *seeding*.
///
/// *seeding* is a process of providing a `SelciTree` with the actual data they are
/// supposed to refer to. This allows you to (for example) split the source into several parts and
/// generate `SelciTree`s separately, merging them afterwards.
///
/// To support ASTs with recursive structure, actually stored `InternalSelci` fields
/// corresponding to child `Selci` contain instead
/// - a logic number of children `Selci`
/// - a physical offset to reach them
/// - a physical offset to skip the [Selci]
///
/// These can be used to efficient access and iteration over `Selci`, while requiring a **single**
/// heap allocation.
#[allow(unused)]
mod comment {}
mod selci;
mod selci_tree;
mod span;
pub const TRACING_MSG: &str = "msg";

View file

@ -1,15 +0,0 @@
#[derive(Debug)]
pub struct Selci {
// TODO
}
#[derive(Debug)]
pub(crate) struct InnerSelci {
// TODO
}
impl InnerSelci {
pub fn seed(&self, source: &str, offset: isize) -> Result<Selci, SpanOpError> {
todo!()
}
}

View file

@ -1,31 +0,0 @@
use crate::{
selci_tree::{SeededSelciTree, TreeSeedingError},
span::Span,
};
#[derive(Debug)]
pub struct Selci<'tree> {
/// Language the code is written in
///
/// Optional - code can be generic, or pseudocode or whatever. No syntax highlighting is
/// not be expected in this case
language: Option<&'tree str>,
/// The code itself
code: &'tree str,
}
#[derive(Debug)]
pub(crate) struct InnerSelci {
language: Option<Span>,
code: Span,
}
impl InnerSelci {
pub fn seed<'tree>(
&self,
position: usize,
tree: &'tree SeededSelciTree<'tree>,
) -> Result<Selci<'tree>, TreeSeedingError> {
todo!()
}
}

View file

@ -1,39 +0,0 @@
use kyomato_util::immutable::Immutable;
use crate::{
selci_tree::{ChildrenSelci, SeededSelciTree, TreeSeedingError},
span::{Idt, Span},
};
use super::ChildrenCount;
#[derive(Debug)]
pub struct Selci<'seed> {
pub ident: Idt<'seed>,
inner_content: Immutable<ChildrenSelci<'seed>>,
}
impl<'tree> Selci<'tree> {
pub fn content(&self) -> impl Iterator<Item = super::Selci<'tree>> {
self.inner_content.clone().unwrap()
}
}
#[derive(Debug)]
pub(crate) struct InnerSelci {
ident: Span,
content_length: ChildrenCount,
}
impl InnerSelci {
pub fn seed<'tree>(
&self,
position: usize,
tree: &'tree SeededSelciTree<'tree>,
) -> Result<Selci<'tree>, TreeSeedingError> {
Ok(Selci {
ident: tree.seed_ident_span(&self.ident)?,
inner_content: tree.children_iter(position, self.content_length).into(),
})
}
}

View file

@ -1,51 +0,0 @@
use kyomato_util::immutable::Immutable;
use crate::selci_tree::{ChildrenSelci, SeededSelciTree, TreeSeedingError};
use super::ChildrenCount;
#[derive(Debug, Clone, Copy)]
/// Represents text formatting
pub enum Kind {
/// Bold format, represented by `**` or `<b>` tag
Bold,
/// Italic format, represented by `*` or `<i>` tag
Italic,
/// Strikethrough formatting, where text is expected to be crossed out
StrikeThrough,
/// Monospace formatting, where text is expected to use monospace font usually used to write
/// source code.
MonoSpace,
}
#[derive(Debug)]
pub struct Selci<'tree> {
pub kind: Kind,
inner_content: Immutable<ChildrenSelci<'tree>>,
}
impl<'tree> Selci<'tree> {
/// Possibly multiple [super::Selci] to apply formatting to
pub fn content(&self) -> impl Iterator<Item = super::Selci<'tree>> {
self.inner_content.clone().unwrap()
}
}
#[derive(Debug)]
pub(crate) struct InnerSelci {
kind: Kind,
content_length: ChildrenCount,
}
impl InnerSelci {
pub fn seed<'tree>(
&self,
position: usize,
tree: &'tree SeededSelciTree<'tree>,
) -> Result<Selci<'tree>, TreeSeedingError> {
Ok(Selci {
kind: self.kind,
inner_content: tree.children_iter(position, self.content_length).into(),
})
}
}

View file

@ -1,55 +0,0 @@
use kyomato_util::immutable::Immutable;
use crate::{
selci_tree::{ChildrenSelci, SeededSelciTree, TreeSeedingError},
span::Span,
};
use super::{ChildrenCount, Idt};
#[derive(Debug)]
pub struct Selci<'tree> {
/// Path where to get the image from
///
/// No specification yet, so this way or may not be a filesystem path, a url or some
/// special unspecified syntax used to hack missile launch codes, I don't know :idk:
pub src: &'tree str,
/// Ident for referencing
pub ident: Option<Idt<'tree>>,
/// With of the figure, as a fraction of full document width
pub width: Option<f32>,
inner_caption: Immutable<ChildrenSelci<'tree>>,
}
impl<'tree> Selci<'tree> {
pub fn caption(&self) -> impl Iterator<Item = super::Selci<'tree>> {
self.inner_caption.clone().unwrap()
}
}
#[derive(Debug)]
pub(crate) struct InnerSelci {
src: Span,
ident: Option<Span>,
width: Option<f32>,
caption_length: ChildrenCount,
}
impl InnerSelci {
pub fn seed<'tree>(
&self,
position: usize,
tree: &'tree SeededSelciTree<'tree>,
) -> Result<Selci<'tree>, TreeSeedingError> {
Ok(Selci {
src: tree.seed_span(&self.src)?,
ident: self
.ident
.as_ref()
.map(|id| tree.seed_ident_span(id))
.transpose()?,
width: self.width,
inner_caption: tree.children_iter(position, self.caption_length).into(),
})
}
}

View file

@ -1,45 +0,0 @@
use kyomato_util::immutable::Immutable;
use crate::{
selci_tree::{ChildrenSelci, SeededSelciTree, TreeSeedingError},
span::Span,
};
use super::ChildrenCount;
#[derive(Debug)]
pub struct Selci<'tree> {
/// Actual link
pub link: &'tree str,
/// If set, represents a wikilink
pub is_wikilink: bool,
inner_content: Immutable<ChildrenSelci<'tree>>,
}
impl<'tree> Selci<'tree> {
pub fn content(&self) -> impl Iterator<Item = super::Selci<'tree>> {
self.inner_content.clone().unwrap()
}
}
#[derive(Debug)]
pub(crate) struct InnerSelci {
link: Span,
is_wikilink: bool,
content_length: ChildrenCount,
skip: usize,
}
impl InnerSelci {
pub fn seed<'tree>(
&self,
position: usize,
tree: &'tree SeededSelciTree<'tree>,
) -> Result<Selci<'tree>, TreeSeedingError> {
Ok(Selci {
link: tree.seed_span(&self.link)?,
is_wikilink: self.is_wikilink,
inner_content: tree.children_iter(position, self.content_length).into(),
})
}
}

View file

@ -1,76 +0,0 @@
use deranged::RangedU8;
use crate::selci_tree::{SeededSelciTree, TreeSeedingError};
use super::ChildrenCount;
const MAX_LIST_ORDER: u8 = 4;
/// List hierarchy order
///
/// There's no real reason to make it bounded, I just want to feel like I do the right thing (what?)
///
/// Anyway, if you somehow need a more-nested list, let me know.
pub type Order = RangedU8<1, MAX_LIST_ORDER>;
#[derive(Debug)]
/// Types of item lists and enumerations
pub enum Kind {
/// A bullet (unordered list). Usually represented by `<ul>` or `-` at the beginning of each
/// item
Bullet,
/// Items numbered with arabic numerals: 1-2-3-4...
Num,
/// Items numbered with roman numerals: i-ii-iii-iv...
Roman {
/// If capital characters should be used
is_capital: bool,
},
/// Items numbered with latin characters: a-b-c-d...
Latin {
/// If capital characters should be used
is_capital: bool,
},
/// Items numbered with Cyrillic characters: а-б-в-г...
Cyrillic {
/// If capital characters should be used
is_capital: bool,
},
}
#[derive(Debug)]
pub struct Selci<'tree> {
kind: Kind,
/// How nested this list is
order: Order,
// TODO: items()
}
#[derive(Debug)]
pub(crate) struct InnerSelci {
kind: Kind,
order: Order,
item_count: ChildrenCount,
skip: usize,
}
/// Item of list or enumeration
pub struct Item {
// TODO: content()
}
pub(crate) struct InnerItem {
content_len: ChildrenCount,
skip: usize,
}
struct Items {}
impl InnerSelci {
pub fn seed<'tree>(
&self,
position: usize,
tree: &'tree SeededSelciTree<'tree>,
) -> Result<Selci<'tree>, TreeSeedingError> {
todo!()
}
}

View file

@ -1,218 +0,0 @@
use alloc::{format, vec};
/// Type of the reference represented by [Selci::NumRef]
///
/// Intended to expand into some sort of enumeration, with interactive element to bring reader to
/// referenced thing
#[derive(Debug, Clone, Copy)]
pub enum RefKind {
/// Reference to [Selci::SectionStart]
Section,
/// Reference to [Selci::DisplayEquation]
Equation,
/// Reference to [Selci::Image]
Image,
/// Reference to [Selci::Diagram]
Diagram,
/// Reference to [Selci::Table]
Table,
/// Refernce to [Selci::Ayano]
#[cfg(feature = "ayano")]
Ayano,
}
mod section;
use alloc::vec::Vec;
use derive_more::derive::{IsVariant, TryUnwrap};
use kyomato_util::immutable::Immutable;
use miette::Diagnostic;
pub(crate) use section::InnerStart as InnerSectionStart;
pub use section::{Order as SectionOrder, Start as SectionStart};
mod formatting;
pub(crate) use formatting::InnerSelci as InnerFormatting;
pub use formatting::{Kind as FormattingKind, Selci as Formatting};
mod link;
pub(crate) use link::InnerSelci as InnerLink;
pub use link::Selci as Link;
mod footnote;
pub(crate) use footnote::InnerSelci as InnerFootnote;
pub use footnote::Selci as Footnote;
mod list;
pub(crate) use list::{InnerItem as InnerListItem, InnerSelci as InnerList};
pub use list::{Item as ListItem, Kind as ListKind, Order as ListOrder, Selci as List};
mod table;
pub(crate) use table::InnerSelci as InnerTable;
pub use table::Selci as Table;
mod image;
pub(crate) use image::InnerSelci as InnerImage;
pub use image::Selci as Image;
mod code;
pub(crate) use code::InnerSelci as InnerCode;
pub use code::Selci as Code;
#[cfg(feature = "ayano")]
mod ayano;
#[cfg(feature = "ayano")]
pub(crate) use ayano::InnerSelci as InnerAyano;
#[cfg(feature = "ayano")]
pub use ayano::Selci as Ayano;
use thiserror::Error;
use crate::span::Idt;
use crate::{
selci_tree::{SeededSelciTree, TreeSeedingError},
span::Span,
};
/// A single unit/cell (lojban - selci, "selshi") of output generation.
///
/// Represents logical parts or actions on the document you'll want to see
#[derive(Debug)]
pub struct Selci<'tree> {
/// Tree this [Selci] belongs to
pub(crate) tree: SeededSelciTree<'tree>,
/// Position this [Selci] is at
pub(crate) position: usize,
}
impl Selci<'_> {
#[inline]
pub fn spec(&self) -> Result<SelciSpec<'_>, TreeSeedingError> {
self.tree.seed_at_pos(self.position)
}
}
#[derive(Debug, IsVariant, TryUnwrap)]
pub enum SelciSpec<'tree> {
/// Following output should be directed to the next "page"
PageDiv,
/// Following output should be visually separated from the previous
ParagraphDiv,
/// Following output should come on the next line
///
/// Intended to represent line break (i.e. `'\n'`)
LineDiv,
/// Following output should be visually major-separated from the previous.
///
/// Intended to represent for sort of tab whitespace, like `'\t'` or `" "`
Tab,
/// Following output should be visually minor-space from the previous
///
/// Intended to represent, well, space character, like `' '`
Space,
/// A start for document section
SectionStart(SectionStart<'tree>),
/// End of the document section
///
/// Intended to have a corresponding [Selci::SectionStart] somewhere before
SectionEnd(SectionOrder),
/// A plain text to be inserted into the output. Note that this does not include any sort of
/// formatting, links, formulas, etc
PlainText(&'tree str),
/// Indicates that all [Selci::PlainText] inside it must be formatted in some way
///
/// Note that usually formatting is not applied to non-plaintext [Selci], and is not propagated
/// to captions, labels, etc
Formatted(Formatting<'tree>),
/// A link, `[]()` in markdown or `<a>` in html
///
/// Outputs may generate different things depending on supported link types. For example, in
/// markdown this could be a hyperlink or a local link to some other document
Link(Link<'tree>),
/// A reference to some other type of [Selci], see [RefKind]
NumRef(RefKind, Idt<'tree>),
/// A footnote
///
/// Represents some information that's odd to insert into the text itself, but may be useful
/// to the reader immediately
Footnote(Footnote<'tree>),
/// A list or enumeration
List(List<'tree>),
/// A latex formula inserted into a text
///
/// Usually displayed in a thinner, smaller fashion
///
/// TODO: represent formula syntax as [Selci] too!
InlineEquation(&'tree str),
/// A latex formula displayed in the document
///
/// Usually inserted in full-height form, disrupting text flow
DisplayEquation(&'tree str, Option<Idt<'tree>>),
/// A table with rows an columns
Table(Table<'tree>),
/// An image to be inserted into the document
Image(Image<'tree>),
/// Represents a diagram
///
/// TODO: WIP
Diagram,
/// A code blocks to be displayed in the output
///
/// Intended to use monospace font and (optionally) highlight syntax
Code(Code<'tree>),
/// A code block in the source, something else in the output
///
/// *because Ayano loves Kyoko*
#[cfg(feature = "ayano")]
Ayano(Ayano),
}
/// This has some implications - there can be no more than 256 children of each [Selci]
pub(crate) type ChildrenCount = u8;
#[derive(Debug)]
pub(crate) struct InnerSelci {
pub skip: usize,
pub spec: InnerSelciSpec,
}
#[derive(Debug)]
pub(crate) enum InnerSelciSpec {
PageDiv,
ParagraphDiv,
LineDiv,
Tab,
Space,
SectionStart(InnerSectionStart),
SectionEnd(SectionOrder),
PlainText(Span),
Formatted(InnerFormatting),
Link(InnerLink),
NumRef(RefKind, Span),
Footnote(InnerFootnote),
List(InnerList),
InlineEquation(Span),
DisplayEquation(Span, Option<Span>),
Table(InnerTable),
Image(InnerImage),
Diagram,
Code(InnerCode),
#[cfg(feature = "ayano")]
Ayano(InnerAyano),
}

View file

@ -1,60 +0,0 @@
use deranged::RangedU8;
use kyomato_util::immutable::Immutable;
use crate::{
selci_tree::{ChildrenSelci, SeededSelciTree},
span::{Span, SpanOpError},
};
use super::{ChildrenCount, Idt};
const MAX_SECTION_ORDER: u8 = 6;
/// Section hierarchy order.
///
/// Inspired by the fact that both markdown and html have 6 header insets, while latex have even
/// less (citation needed)
///
/// If you need even more nested sections - please let me know. It would be easy to change as long
/// as your nesting is less than 255.
pub type Order = RangedU8<1, MAX_SECTION_ORDER>;
#[derive(Debug)]
pub struct Start<'tree> {
/// How deep into the hierarchy the section is, 1 being the most outer one
pub orser: Order,
/// Ident for referencing
pub ident: Option<Idt<'tree>>,
name_inner: Immutable<ChildrenSelci<'tree>>,
}
impl<'tree> Start<'tree> {
/// Possibly multiple [super::Selci] representing section name
pub fn name(&self) -> impl Iterator<Item = super::Selci<'tree>> {
self.name_inner.clone().unwrap()
}
}
#[derive(Debug)]
pub(crate) struct InnerStart {
pub orser: Order,
pub ident: Option<Span>,
name_length: ChildrenCount,
}
impl InnerStart {
pub fn seed<'seed>(
&self,
position: usize,
tree: &'seed SeededSelciTree<'seed>,
) -> Result<Start<'seed>, SpanOpError> {
Ok(Start {
orser: self.orser,
ident: self
.ident
.as_ref()
.map(|id| tree.seed_ident_span(id))
.transpose()?,
name_inner: tree.children_iter(position, self.name_length).into(),
})
}
}

View file

@ -1,65 +0,0 @@
use crate::selci_tree::{SeededSelciTree, TreeSeedingError};
use super::{ChildrenCount, Idt};
/// A row of a table [super::Selci]
pub struct Row {}
struct Rows {}
/// A column of table [super::Selci]
pub struct Column {}
struct Columns {}
/// Formatting applied to the entire table [super::Selci]
pub enum Formatting {}
struct Formattings {}
/// Formatting applied to individual [Cell]
pub enum CellFormatting {}
struct CellFormattings {}
/// A cell of table [super::Selci]
pub struct Cell {
// Inner [Selci] populating the cell
// pub content(): ChildrenSelci<'tree>,
// Formattings to apply to this cell
// pub fomattings()
}
/// A cell of table [super::Selci]
pub struct InnerCell {
// Inner [Selci] populating the cell
// pub content(): ChildrenSelci<'tree>,
// Formattings to apply to this cell
// pub fomattings()
}
#[derive(Debug)]
pub struct Selci<'tree> {
// TODO: rows()
// TODO: columns()
// TODO: cells()
// TODO: caption()
/// Ident for referencing
ident: Option<Idt<'tree>>,
}
#[derive(Debug)]
pub(crate) struct InnerSelci {
rows: ChildrenCount,
columns: ChildrenCount,
cells_len: ChildrenCount,
ident_offset: usize,
ident_len: ChildrenCount,
skip: usize,
}
impl InnerSelci {
pub fn seed<'tree>(
&self,
position: usize,
tree: &'tree SeededSelciTree<'tree>,
) -> Result<Selci<'tree>, TreeSeedingError> {
todo!()
}
}

View file

@ -1,181 +0,0 @@
use miette::Diagnostic;
use thiserror::Error;
use tracing::{error, info, instrument, trace, warn};
use crate::{
selci::{ChildrenCount, InnerSelci, InnerSelciSpec, Selci, SelciSpec},
span::{Idt, Span, SpanOpError},
TRACING_MSG,
};
#[derive(Debug, Error, Diagnostic)]
pub enum TreeSeedingError {
#[error(transparent)]
Span(
#[from]
#[diagnostic_source]
SpanOpError,
),
#[error("Error while indexing tree: index {0}, but length {1}")]
#[diagnostic(code(kyomato_core::tree_overindex), severity(Error))]
TreeIndex(usize, usize),
}
type Res<R = ()> = Result<R, TreeSeedingError>;
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub struct SelciTree {
/// Offset to be applied to the interpreted source before it's used to seed the tree
pub offset: isize,
/// Buffer of
buffer: alloc::vec::Vec<InnerSelci>,
/// A pointer to next `SelciTree` in the chain
pub next: Option<alloc::boxed::Box<SelciTree>>,
}
impl SelciTree {
/// Seed the `SelciTree` with a source
///
/// This is required for tree traversal
///
/// Seeded tree lives as long as both tree and source do
pub fn seed<'seed>(&'seed self, source: &'seed str) -> SeededSelciTree<'seed> {
self.seed_offset(source, 0)
}
fn seed_offset<'seed>(
&'seed self,
source: &'seed str,
offset: isize,
) -> SeededSelciTree<'seed> {
SeededSelciTree {
offset: self.offset + offset,
source,
buffer: self.buffer.as_slice(),
next: self.next.as_deref(),
}
}
}
/// A [SelciTree] that was properly seeded and can be used to traverse [super::selci::Selci]
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone)]
pub struct SeededSelciTree<'seed> {
offset: isize,
source: &'seed str,
buffer: &'seed [InnerSelci],
next: Option<&'seed SelciTree>,
}
impl<'tree> SeededSelciTree<'tree> {
pub fn traverse(&self) -> OuterSelci<'_> {
OuterSelci {
next_token: 0,
current_tree: self.clone(),
}
}
pub(crate) fn seed_at_pos(&self, position: usize) -> Res<SelciSpec<'_>> {
Ok(match &self.buffer[position].spec {
InnerSelciSpec::PageDiv => SelciSpec::PageDiv,
InnerSelciSpec::ParagraphDiv => SelciSpec::ParagraphDiv,
InnerSelciSpec::LineDiv => SelciSpec::LineDiv,
InnerSelciSpec::Tab => SelciSpec::Tab,
InnerSelciSpec::Space => SelciSpec::Space,
InnerSelciSpec::SectionStart(inner) => {
SelciSpec::SectionStart(inner.seed(position, &self)?)
}
InnerSelciSpec::SectionEnd(order) => SelciSpec::SectionEnd(*order),
InnerSelciSpec::PlainText(span) => SelciSpec::PlainText(self.seed_span(span)?),
InnerSelciSpec::Formatted(inner) => SelciSpec::Formatted(inner.seed(position, self)?),
InnerSelciSpec::Link(inner) => SelciSpec::Link(inner.seed(position, &self)?),
InnerSelciSpec::NumRef(kind, ident) => {
SelciSpec::NumRef(*kind, self.seed_ident_span(ident)?)
}
InnerSelciSpec::Footnote(inner) => SelciSpec::Footnote(inner.seed(position, self)?),
InnerSelciSpec::List(inner) => SelciSpec::List(inner.seed(position, &self)?),
InnerSelciSpec::InlineEquation(content) => {
SelciSpec::InlineEquation(self.seed_span(content)?)
}
InnerSelciSpec::DisplayEquation(content, ident) => SelciSpec::DisplayEquation(
self.seed_span(content)?,
ident
.as_ref()
.map(|id| self.seed_ident_span(id))
.transpose()?,
),
InnerSelciSpec::Table(inner) => SelciSpec::Table(inner.seed(position, self)?),
InnerSelciSpec::Image(inner) => SelciSpec::Image(inner.seed(position, self)?),
InnerSelciSpec::Diagram => SelciSpec::Diagram,
InnerSelciSpec::Code(inner) => SelciSpec::Code(inner.seed(position, self)?),
#[cfg(feature = "ayano")]
InnerSelciSpec::Ayano(inner) => SelciSpec::Ayano(inner.seed(position, self)?),
})
}
#[inline]
pub(crate) fn seed_span(&self, span: &Span) -> Result<&str, SpanOpError> {
span.seed(self.source, self.offset)
}
#[inline]
pub(crate) fn seed_ident_span(&self, span: &Span) -> Result<Idt<'_>, SpanOpError> {
span.seed_ident(self.source, self.offset)
}
#[inline]
pub(crate) fn children_iter(&self, position: usize, count: ChildrenCount) -> ChildrenSelci<'_> {
OuterSelci {
next_token: position + 1,
current_tree: self.clone(),
}
.take(count.into())
}
}
#[derive(Debug, Clone)]
pub(crate) struct OuterSelci<'tree> {
next_token: usize,
current_tree: SeededSelciTree<'tree>,
}
impl<'tree> Iterator for OuterSelci<'tree> {
type Item = Selci<'tree>;
#[instrument(
skip(self),
name = "next selci search",
level = "trace",
fields(
tree_pos = format_args!("{}/{}", self.next_token, self.current_tree.buffer.len() - 1),
next_tree = self.current_tree.next.is_some(),
offset = self.current_tree.offset,
),
)]
fn next(&mut self) -> Option<Self::Item> {
let skip = loop {
if let Some(next_selci) = self.current_tree.buffer.get(self.next_token) {
trace!(
?next_selci,
next_selci_index = self.next_token,
{ TRACING_MSG } = "Found next selci"
);
break next_selci.skip;
}
let next_tree = self.current_tree.next?; // `None` returned here
info!("Current tree has run out of tokens, using next one");
self.next_token -= self.current_tree.buffer.len();
self.current_tree =
next_tree.seed_offset(self.current_tree.source, self.current_tree.offset);
};
let next_selci = Selci {
tree: self.current_tree.clone().into(),
position: self.next_token.into(),
};
self.next_token += skip;
return Some(next_selci);
}
}
pub(crate) type ChildrenSelci<'tree> = core::iter::Take<OuterSelci<'tree>>;

View file

@ -1,216 +0,0 @@
use core::num::NonZeroUsize;
use alloc::{format, vec, vec::Vec};
use derive_getters::Getters;
use derive_more::derive::{Deref, Display};
use miette::Diagnostic;
use thiserror::Error;
use tracing::{error, info, trace};
use crate::TRACING_MSG;
/// A span of source
///
/// Invariant: `self.offset + self.length` do not overflow
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Copy, Getters, Display)]
#[display("@[{offset}-:-{}]", length.checked_add(*offset).unwrap())]
pub struct Span {
/// Offset of the span, in bytes
offset: usize,
/// Length of the span, in bytes
length: NonZeroUsize,
}
/// A text that's guaranteed to be a valid ident
///
/// Valid idents only use alphanumeric characters, underscores and dashes, so [A-Za-z0-9_\-]
#[derive(Debug, Deref)]
pub struct Idt<'r>(&'r str);
#[derive(Debug, Error, Diagnostic)]
#[error("Attempt to use bad text as identifier")]
#[diagnostic(
code(kyomato_core::bad_ident_chars),
severity(Error),
help("Only A..Z, a..z, 0..9, '_' and '-' are permitted in identifiers")
)]
pub struct InvalidIdent {
#[label("Valid identifier expected here")]
span: Span,
#[label(collection, "These symbol(s) are not valid for identifiers")]
invalid_characters: Vec<usize>,
}
#[derive(Debug, Error, Diagnostic)]
#[diagnostic(code(kyomato_core::span_overflow), severity(Error))]
pub enum SpanOpError {
#[error("Attempt to shift span past 0; span: {_0}, shift: {_1}")]
OffsetUnderflow(Span, isize),
#[error("Attempt to shift span past usize::MAX; span: {_0}, shift: {_1}")]
OffsetOverflow(Span, isize),
#[error("Attempt to seed the span out of bounds; span: {_0}, source length: {_1}")]
SeedOutOfBounds(Span, usize),
#[error("Attempt to seed char-misaligned span")]
CharBoundary {
span: Span,
faulty_index: usize,
#[label("Attempt to slice this character here")]
char_start: usize,
},
#[error(transparent)]
Ident(
#[diagnostic(source)]
#[from]
InvalidIdent,
),
}
type Res<R = ()> = Result<R, SpanOpError>;
enum CheckError {
Underflow(isize),
Overflow(isize),
}
impl CheckError {
fn with_span(self, span: Span) -> SpanOpError {
match self {
CheckError::Underflow(shift) => SpanOpError::OffsetUnderflow(span, shift),
CheckError::Overflow(shift) => SpanOpError::OffsetOverflow(span, shift),
}
}
}
#[inline]
fn check_overflows(initial: usize, shift: isize) -> Result<usize, CheckError> {
let Some(result) = initial.checked_add_signed(shift) else {
trace!("Shift overflows the span!");
return Err(match shift.cmp(&0) {
core::cmp::Ordering::Equal => {
error!("Shift is zero, but overflowed the span");
panic!("Shift is zero, but overflowed the span");
}
core::cmp::Ordering::Less => {
trace!("Bound underflow");
CheckError::Underflow(shift)
}
core::cmp::Ordering::Greater => {
trace!("Bound overflow");
CheckError::Overflow(shift)
}
});
};
Ok(result)
}
#[inline]
fn check_char_boundary(source: &str, candidate: usize) -> Result<(), (usize, usize)> {
if source.is_char_boundary(candidate) {
return Ok(());
}
error!("Attempt to seed a char-misaligned span");
let mut char_start = candidate;
while !source.is_char_boundary(char_start) {
char_start = char_start
.checked_sub(1)
.expect("Could not find char boundary at the start");
}
Err((candidate, char_start))
}
impl Span {
/// Return position of span end
#[inline]
pub fn end(&self) -> NonZeroUsize {
self.length.checked_add(self.offset).unwrap()
}
/// Return a span of same length, but moved by provided value
#[tracing::instrument(level = "trace")]
pub fn shift(self, sh: isize) -> Res<Self> {
let merr = |err: CheckError| err.with_span(self);
let offset = check_overflows(self.offset, sh).map_err(merr)?;
let _ = check_overflows(self.end().get(), sh).map_err(merr)?;
Ok(Self {
offset,
length: self.length,
})
}
/// Return a span containing both provided spans
#[tracing::instrument(level = "trace")]
fn combine(self, other: Span) -> Self {
let from = core::cmp::min(self.offset, other.offset);
let to = core::cmp::max(self.end(), other.end()).get();
let Some(length) = to.checked_sub(from).and_then(NonZeroUsize::new) else {
error!("Span end is before or at the start");
panic!("Span end is before or at the start");
};
Self {
offset: from,
length,
}
}
/// Return a string slice of the source this span is supposed to represent
#[tracing::instrument(level = "trace", skip(source))]
pub(crate) fn seed<'source>(&self, source: &'source str, offset: isize) -> Res<&'source str> {
let shifted = self.shift(offset)?;
let end = shifted.end().get();
let source_len = source.len();
if source_len < end {
error!("Attempt to seed a span out of bounds");
return Err(SpanOpError::SeedOutOfBounds(shifted, source_len));
}
let merr = |(faulty_index, char_start)| SpanOpError::CharBoundary {
span: *self,
faulty_index,
char_start,
};
check_char_boundary(source, shifted.offset).map_err(merr)?;
check_char_boundary(source, end).map_err(merr)?;
Ok(&source[shifted.offset..end])
}
#[tracing::instrument(level = "trace", skip(source))]
pub(crate) fn seed_ident<'source>(
&self,
source: &'source str,
offset: isize,
) -> Res<Idt<'source>> {
let s = self.seed(source, offset)?;
info!(text = s, { TRACING_MSG } = "Parsed text");
let mut invalid_characters = Vec::new();
let Ok(offset) = check_overflows(self.offset, offset) else {
error!(%self, %offset,
{ TRACING_MSG } =
"Failed to offset the span, when it was supposedly already offset successfully"
);
panic!("Failed to offset the span, when it was supposedly already offset successfully");
};
for (i, c) in s.char_indices() {
if !matches!(c, 'A'..='Z' | 'a'..='z' | '0'..='0' | '_' | '-') {
trace!(character = %c, {TRACING_MSG}= "Invalid character detected");
invalid_characters.push(offset + i);
}
}
if !invalid_characters.is_empty() {
info!("Invalid identifier");
return Err(InvalidIdent {
span: *self,
invalid_characters,
}
.into());
}
trace!("Identifier is valid, creating ident now");
Ok(Idt(s))
}
}
impl From<Span> for miette::SourceSpan {
fn from(value: Span) -> Self {
let Span { offset, length } = value;
miette::SourceSpan::new(miette::SourceOffset::from(offset), length.get())
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

View file

@ -1,65 +0,0 @@
#!/bin/bash
# This script aims to help with kyomato usage, boiling it to a single command
# You can try running it as
# > KYOMATO_IMAGES=$(realpath ./image_folder) ./kyomato-helper.bash test_input/input.md
# From the directory it's located in
# It's functions are as follows:
# - Create a hidden directory near specified input file
# - Clear it, so that build is always clean
# - Run kyomato to transform input file into `.tex`
# - Run pdflatex twice on resulting `.tex`, to generate all tables correctly
# - Move resulting `.pdf` right near input file. Temporary hidden folder can be deleted manually, or used for debug
# check if kyomato exists:
( kyomato -V > /dev/null ) || { echo "can't reach kyomato"; exit -1; }
# check if pdflatex exists:
( pdflatex -v > /dev/null ) || { echo "can't reach pdflatex"; exit -1; }
# input file path is expected at first argument
input_file=$1
if [[ -z $input_file ]] then
echo "input file must be supplied";
exit -1;
fi
if [[ ! -f $input_file ]] then
echo "File is expected as input";
exit -1;
fi
echo "input file is ${input_file}"
# Determine working directory
source_directory=$( dirname $input_file )
if [[ -z source_directory ]] then
echo "dirname had failed. please contact me";
exit -1;
fi
input_name=$( basename $input_file | cut -f 1 -d '.')
working_directory=$source_directory/.kyomato_tmp
echo "working directory is ${working_directory}"
# Ensure working directory exists
mkdir -p -v $working_directory
if [[ ! -z "$(dir $working_directory)" ]] then
# Clean up the working directory from previous runs
rm -v -r --preserve-root=all $working_directory/*;
fi
# Actually run kyomato
execution_dir=$(pwd)
cd $working_directory
echo "running kyomato..."
( kyomato --in ../$( basename $input_file ) --debug-paths --exec $execution_dir > source.tex 2> kyomato_err.log ) || { echo "kyomato had failed, please contact me"; exit -1; }
# run pdflatex
echo "running pdflatex(1/2) ..."
( pdflatex -halt-on-error -file-line-error -interaction=nonstopmode -output-format=pdf -output-directory=. -shell-escape source.tex &> pdflatex1.log ) || { echo "pdflatex had failed, please contact me"; exit -1; }
echo "running pdflatex(2/2) ..."
( pdflatex -halt-on-error -file-line-error -interaction=nonstopmode -output-format=pdf -output-directory=. -shell-escape source.tex &> pdflatex2.log ) || { echo "pdflatex had failed, on the second run! please contact me"; exit -1; }
# yep, latex must be run twice
# because latex is weird
# at the end, move out generated pdf
mv -v -u source.pdf ../$input_name.pdf

View file

@ -1,56 +0,0 @@
---
header-line1: A document generation example
code-section-title: Code blocks
---
# First part
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lectus mauris ultrices eros in cursus turpis massa tincidunt dui. Et netus et malesuada fames ac turpis egestas. Malesuada fames ac turpis egestas sed tempus urna. Curabitur gravida arcu ac tortor dignissim convallis. Enim ut sem viverra aliquet eget sit amet. Ultricies leo integer malesuada nunc vel risus commodo viverra maecenas. Quam adipiscing vitae proin sagittis nisl rhoncus mattis. Sem integer vitae justo eget. Lobortis mattis aliquam faucibus purus in massa tempor nec. Vitae congue mauris rhoncus aenean vel elit. Laoreet sit amet cursus sit amet dictum. Et netus et malesuada fames. Sed augue lacus viverra vitae. Porttitor leo a diam sollicitudin tempor. Nulla aliquet enim tortor at.
$$
\tau = 1.04 \cdot 10^{-6} \cdot \dfrac{1-\cos(\theta)}{\ln\left\{ \dfrac{[N(0) - N(90)] \cdot \cos ^{1.6}(\theta)}{N(\theta) - N(90)} \right\}}=
$$
{ref = tau}
# Second part
Velit scelerisque in dictum non consectetur a erat nam at[@eq:tau]. A diam maecenas sed enim ut sem viverra. Aliquam faucibus purus in massa tempor nec. Magnis dis parturient montes nascetur ridiculus. Accumsan sit amet nulla facilisi morbi tempus. Eu tincidunt tortor aliquam nulla facilisi cras fermentum. Volutpat lacus laoreet non curabitur gravida arcu ac. Ut tristique et egestas quis ipsum suspendisse ultrices gravida. Donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Iaculis eu non diam phasellus vestibulum lorem sed. Amet consectetur adipiscing elit duis. Mi bibendum neque egestas congue quisque egestas. In pellentesque massa placerat duis. Felis imperdiet proin fermentum leo vel orci porta non.
![[meme.png]]
{caption = "Lol why?", ref = meme}
Senectus et netus et malesuada fames ac turpis egestas integer[@fig:meme]. Et sollicitudin ac orci phasellus egestas. Id interdum velit laoreet id donec ultrices tincidunt arcu non. Odio tempor orci dapibus ultrices. Sed lectus vestibulum mattis ullamcorper. Nulla pellentesque dignissim enim sit amet venenatis. Lacus laoreet non curabitur gravida arcu ac. Sed odio morbi quis commodo odio. Dui id ornare arcu odio ut. Mauris ultrices eros in cursus.
```python, Ayano * "Ac turpis egestas sed tempus urna et pharetra pharetra massa."
# Lorem dolor sed viverra ipsum nunc aliquet. Arcu dictum varius duis at consectetur lorem donec massa sapien. Eu feugiat pretium nibh ipsum consequat nisl vel pretium lectus.
x = [1,2, 3, 4, 5]
@dev: x[2], 0.1
```
------
# Conclusion
Ut aliquam purus sit amet luctus venenatis lectus magna fringilla. Egestas purus viverra accumsan in. Odio euismod lacinia at quis risus sed vulputate odio ut. Cursus euismod quis viverra nibh cras pulvinar mattis nunc sed. Volutpat maecenas volutpat blandit aliquam etiam. Sodales ut eu sem integer vitae justo eget magna fermentum.
```python, Ayano * "That's a table!"
@csv_table: src="results.csv", rows=2..=10, caption = "That's a caption!"
```
In eu mi bibendum neque egestas. Mattis vulputate enim nulla aliquet. Sit amet nulla facilisi morbi tempus iaculis urna. Aliquam purus sit amet luctus venenatis lectus. Convallis a cras semper auctor neque vitae tempus quam pellentesque. Vel turpis nunc eget lorem dolor sed. Mauris pellentesque pulvinar pellentesque habitant morbi. Ligula ullamcorper malesuada proin libero nunc. Tortor at auctor urna nunc id cursus metus aliquam eleifend. Mi tempus imperdiet nulla malesuada. Tellus in metus vulputate eu scelerisque. Quis lectus nulla at volutpat diam ut venenatis tellus.
Generate a figure in function block, and insert it with a special syntax:
```python, Ayano * "that's a figure insertion syntax, all-in-one!"
import numpy as np
import matplotlib.pyplot as plt
from math import pi
x = np.arange(-pi, pi, 0.01)
y = np.sin(x)
plt.plot(x, y)
plt.savefig("sine_graph.png")
@fig: src = "sine_graph.png", ident = "other_sine_graph", caption = "That's another way to include a figure from Ayano!", width = 0.9
```
And of course you can generate a figure with inserted block too!
```python, Ayano * "That's a third way to do the same thing!" ~ scripts/generate_sine.py
@fig: src = "other_sine_graph.png", ident = "yet_another_sine_graph", caption = "The same sine. Again.", width = 0.2
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View file

@ -1,11 +0,0 @@
'dataset' 'theta' 'tau' 'tau_err' '%'
'counts' 30.0 6.695212475440718e-07 1.2899671973307503e-06 192.67
'counts' 60.0 4.2402616158686483e-07 5.142772156895383e-07 121.28
'counts' 45.0 -8.045091470679962e-07 6.618084062607329e-07 -82.26
'provided1' 45.0 3.29268327957351e-07 2.0471885401181245e-07 62.17
'provided2' 30.0 3.0219144232940006e-06 2.3786429056933766e-05 787.13
'provided2' 40.0 2.8245329154178373e-07 2.6019976229797025e-07 92.12
'provided2' 60.0 1.114681732370397e-06 2.1996000386518054e-06 197.33
'provided3' 30.0 2.1550690414157404e-06 9.543180301947427e-06 442.82
'provided3' 45.0 7.516418500767245e-07 8.770054083968072e-07 116.68
'provided3' 60.0 5.477416674543067e-07 5.261473464560824e-07 96.06
1 'dataset' 'theta' 'tau' 'tau_err' '%'
2 'counts' 30.0 6.695212475440718e-07 1.2899671973307503e-06 192.67
3 'counts' 60.0 4.2402616158686483e-07 5.142772156895383e-07 121.28
4 'counts' 45.0 -8.045091470679962e-07 6.618084062607329e-07 -82.26
5 'provided1' 45.0 3.29268327957351e-07 2.0471885401181245e-07 62.17
6 'provided2' 30.0 3.0219144232940006e-06 2.3786429056933766e-05 787.13
7 'provided2' 40.0 2.8245329154178373e-07 2.6019976229797025e-07 92.12
8 'provided2' 60.0 1.114681732370397e-06 2.1996000386518054e-06 197.33
9 'provided3' 30.0 2.1550690414157404e-06 9.543180301947427e-06 442.82
10 'provided3' 45.0 7.516418500767245e-07 8.770054083968072e-07 116.68
11 'provided3' 60.0 5.477416674543067e-07 5.261473464560824e-07 96.06

View file

@ -1,12 +0,0 @@
import numpy as np
import matplotlib.pyplot as plt
from math import pi
x = np.arange(-pi, pi, 0.01)
y = np.sin(x)
plt.plot(x, y)
plt.savefig("../other_sine_graph.png")
# let's say, that there's some debugging going on here:
# TOSHINO KYOKO!
print("I love beeeeans")
exit(-2222222)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View file

@ -1,59 +0,0 @@
use std::{borrow::Borrow, ops::Deref};
/// Prevents mutable access to the underlying value.
///
/// To grain full access, you must destroy the value.
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[repr(transparent)]
pub struct Immutable<T: ?Sized>(T);
impl<T: ?Sized> Deref for Immutable<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> AsRef<T> for Immutable<T> {
fn as_ref(&self) -> &T {
&self.0
}
}
impl<T> Borrow<T> for Immutable<T> {
fn borrow(&self) -> &T {
&self.0
}
}
impl<T> From<T> for Immutable<T> {
fn from(value: T) -> Self {
Self(value)
}
}
macro_rules! impl_fmt {
($trait_name:ident) => {
impl<T: core::fmt::$trait_name> core::fmt::$trait_name for Immutable<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
};
}
impl_fmt! {Debug}
impl_fmt! {Display}
impl_fmt! {Binary}
impl_fmt! {LowerHex}
impl_fmt! {UpperHex}
impl_fmt! {LowerExp}
impl_fmt! {UpperExp}
impl_fmt! {Octal}
impl_fmt! {Pointer}
impl<T> Immutable<T> {
pub fn unwrap(self) -> T {
self.0
}
}

View file

@ -1,7 +0,0 @@
//! These are set of random utility types/functions that felt generic enough to completely separate
//! from `kyomato` itself.
//!
//! I would appreciate any advise on alternative to use instead
/// A wrapper type that does not allow mutation of inner value
pub mod immutable;

View file

@ -1,514 +0,0 @@
use std::{borrow::Cow, ops::Deref, path::Path, rc::Rc};
use url::Url;
use crate::util::StaticDebug;
#[derive(Debug, Default, PartialEq, Clone)]
pub struct TitleInfo<'source> {
pub header_line1: Option<Tx<'source>>,
pub header_line2: Option<Tx<'source>>,
pub document_type: Option<Tx<'source>>,
pub title_line1: Option<Tx<'source>>,
pub title_line2: Option<Tx<'source>>,
pub title_line3: Option<Tx<'source>>,
pub title_line4: Option<Tx<'source>>,
pub author_line1: Option<Tx<'source>>,
pub author_line2: Option<Tx<'source>>,
pub author_line3: Option<Tx<'source>>,
pub date: Option<Tx<'source>>,
pub prof: Option<Tx<'source>>,
pub code_section_title: Option<Tx<'source>>,
}
// For whatever reason, std library Cow requires ToOwned for it's contained value to be cloned
// That makes NO SENSE in my case.
// I do not need a functionality to obtain owned value here,
// but rather be able to refer to the same data from multiple instances.
// Also, vectors of parsed tokens are obviously not intended to live longer than the source.
// In fact, for any possible appliance they can be assumed to life for as long as source does.
// Hope is for lifetime variance to help with lifetime casting, or th
#[derive(Debug, PartialEq, Clone)]
// #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Tokens<'source>(Rc<[Token<'source>]>);
impl<'source> Deref for Tokens<'source> {
type Target = [Token<'source>];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'r, 'source: 'r> IntoIterator for &'r Tokens<'source> {
type Item = &'r Token<'source>;
type IntoIter = std::slice::Iter<'r, Token<'source>>;
fn into_iter(self) -> Self::IntoIter {
self.get_slice().iter()
}
}
impl<'source> Tokens<'source> {
pub fn new(value: impl Into<Box<[Token<'source>]>>) -> Self {
Self(Rc::from(value.into()))
}
pub fn get_slice<'r>(&'r self) -> &'r [Token<'source>]
where
'source: 'r,
{
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum DisplayState<'code> {
NotDisplayed,
DisplayedNoCaption,
Caption(Box<Token<'code>>),
}
impl<'code> DisplayState<'code> {
fn to_static(&self) -> DisplayState<'static> {
match self {
DisplayState::NotDisplayed => DisplayState::NotDisplayed,
DisplayState::DisplayedNoCaption => DisplayState::DisplayedNoCaption,
DisplayState::Caption(caption) => {
DisplayState::Caption(Box::new(caption.to_static_token()))
}
}
}
}
#[derive(Debug, Clone)]
// #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AyanoBlock<'code> {
pub ident: StaticDebug<u64>,
pub display_state: DisplayState<'code>,
pub spaces_before: usize,
pub is_static: bool,
pub code: Tx<'code>,
pub insert_path: Option<Cow<'code, Path>>,
}
impl PartialEq for AyanoBlock<'_> {
fn eq(&self, other: &Self) -> bool {
self.display_state == other.display_state
&& self.spaces_before == other.spaces_before
&& self.is_static == other.is_static
&& self.code == other.code
&& self.insert_path == other.insert_path
}
}
// an abandoned idea. this would result in weird behavior, if underlying data happened to be moved.
// WARN! note this semantics: AyanoBlocks are deemed the same, if they are literally referencing the same data,
/*
impl PartialEq for AyanoBlock<'_> {
fn eq(&self, other: &Self) -> bool {
self.is_display == other.is_display
&& self.is_static == other.is_static
&& self.code.as_ptr() == other.code.as_ptr()
&& self.code.len() == other.code.len()
&& self.insert_path == other.insert_path
}
}
*/
#[derive(Debug, Clone, PartialEq)]
// #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ListType {
Bullet,
Num,
Latin,
Cyrillic,
Roman,
}
#[derive(Debug, Clone, PartialEq)]
// #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Formatting {
Bold,
Italic,
StrikeThrough,
MonoSpace,
}
type Tx<'source> = Cow<'source, str>;
type Pth<'source> = Cow<'source, Path>;
#[derive(Debug, PartialEq, Clone)]
// #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Token<'source> {
PageDiv,
Header {
// 0..6
order: usize,
content: Tx<'source>,
},
DisplayMath {
content: Tx<'source>,
ident: Option<Tx<'source>>,
},
Table {
header: Tokens<'source>,
cells: Tokens<'source>,
caption: Option<Box<Token<'source>>>,
ident: Option<Tx<'source>>,
},
Figure {
src_name: Pth<'source>,
caption: Option<Box<Token<'source>>>,
ident: Option<Tx<'source>>,
width: Option<f32>,
},
Href {
space_before: bool,
// #[cfg_attr(feature = "serde", serde(with = "url_serde"))]
url: Url,
display: Tx<'source>,
},
Ayano {
data: AyanoBlock<'source>,
},
List {
list_type: ListType,
content: Tokens<'source>,
},
Multiple {
tokens: Tokens<'source>,
},
// TODO move font and formatting exclusively to here
// TODO add a flag to indicate, if this paragraph contains escaped chars (if it was parsed from caption, basically)
Paragraph {
is_newline: bool,
space_before: bool,
formatting: Option<Formatting>,
content: Tx<'source>,
},
InlineMath {
space_before: bool,
content: Tx<'source>,
},
Reference {
space_before: bool,
ident: Tx<'source>,
},
FootnoteReference {
space_before: bool,
ident: Tx<'source>,
},
FootnoteContent {
content: Box<Token<'source>>,
ident: Tx<'source>,
},
CodeBlock {
code: Tx<'source>,
language: Option<Tx<'source>>,
},
Error {
message: String,
},
}
trait ToStaticExt {
type AsStatic;
fn to_static(&self) -> Self::AsStatic;
}
impl ToStaticExt for Cow<'_, str> {
type AsStatic = Cow<'static, str>;
fn to_static(&self) -> Self::AsStatic {
Cow::Owned(self.to_string())
}
}
impl ToStaticExt for Cow<'_, Path> {
type AsStatic = Cow<'static, Path>;
fn to_static(&self) -> Self::AsStatic {
Cow::Owned(self.to_path_buf())
}
}
impl ToStaticExt for Token<'_> {
type AsStatic = Token<'static>;
fn to_static(&self) -> Self::AsStatic {
self.to_static_token()
}
}
impl ToStaticExt for Tokens<'_> {
type AsStatic = Tokens<'static>;
fn to_static(&self) -> Self::AsStatic {
let slice = self.get_slice();
Tokens::new(slice.iter().map(Token::to_static_token).collect::<Box<_>>())
}
}
impl ToStaticExt for Box<Token<'_>> {
type AsStatic = Box<Token<'static>>;
fn to_static(&self) -> Self::AsStatic {
Box::new((**self).to_static())
}
}
impl ToStaticExt for AyanoBlock<'_> {
type AsStatic = AyanoBlock<'static>;
fn to_static(&self) -> Self::AsStatic {
let AyanoBlock {
ident,
code,
insert_path,
display_state,
is_static,
spaces_before,
} = self;
AyanoBlock {
ident: ident.clone(),
code: code.to_static(),
insert_path: insert_path.as_ref().map(Cow::to_static),
is_static: *is_static,
display_state: display_state.to_static(),
spaces_before: *spaces_before,
}
}
}
impl<'source> Token<'source> {
pub fn text(text: impl Into<Cow<'source, str>>) -> Self {
Self::Paragraph {
space_before: false,
is_newline: false,
content: text.into(),
formatting: None,
}
}
pub fn borrow_ref(&self) -> Token<'source> {
match self {
Token::PageDiv => Token::PageDiv,
Token::Header { order, content } => Token::Header {
order: *order,
content: content.clone(),
},
Token::DisplayMath { content, ident } => Token::DisplayMath {
content: content.clone(),
ident: ident.clone(),
},
Token::Table {
header,
cells,
caption,
ident,
} => Token::Table {
header: header.clone(),
cells: cells.clone(),
caption: caption.as_ref().map(|t| Box::new(Token::borrow_ref(t))),
ident: ident.clone(),
},
Token::Figure {
src_name,
caption,
ident,
width,
} => Token::Figure {
src_name: src_name.clone(),
caption: caption.clone(),
ident: ident.clone(),
width: *width,
},
Token::Href {
url,
display,
space_before,
} => Token::Href {
url: url.clone(),
display: display.clone(),
space_before: *space_before,
},
Token::Ayano { data } => Token::Ayano { data: data.clone() },
Token::List { list_type, content } => Token::List {
list_type: list_type.clone(),
content: content.clone(),
},
Token::Multiple { tokens } => Token::Multiple {
tokens: tokens.clone(),
},
Token::Paragraph {
is_newline,
formatting,
content,
space_before,
} => Token::Paragraph {
is_newline: *is_newline,
formatting: formatting.clone(),
content: content.clone(),
space_before: *space_before,
},
Token::InlineMath {
content,
space_before,
} => Token::InlineMath {
content: content.clone(),
space_before: *space_before,
},
Token::Reference {
ident,
space_before,
} => Token::Reference {
ident: ident.clone(),
space_before: *space_before,
},
Token::FootnoteReference {
ident,
space_before,
} => Token::FootnoteReference {
ident: ident.clone(),
space_before: *space_before,
},
Token::FootnoteContent { content, ident } => Token::FootnoteContent {
content: content.clone(),
ident: ident.clone(),
},
Token::CodeBlock { code, language } => Token::CodeBlock {
code: code.clone(),
language: language.clone(),
},
Token::Error { message } => Token::Error {
message: message.clone(),
},
}
}
pub fn to_static_token<'r>(&'r self) -> Token<'static>
where
'source: 'r,
{
match self {
Token::PageDiv => Token::PageDiv,
Token::Header { order, content } => Token::Header {
order: *order,
content: content.to_static(),
},
Token::DisplayMath { content, ident } => Token::DisplayMath {
content: content.to_static(),
ident: ident.as_ref().map(Cow::to_static),
},
Token::Table {
header,
cells,
caption,
ident,
} => Token::Table {
header: header.to_static(),
cells: cells.to_static(),
caption: caption.as_ref().map(Box::to_static),
ident: ident.as_ref().map(Cow::to_static),
},
Token::Figure {
src_name,
caption,
ident,
width,
} => Token::Figure {
src_name: src_name.to_static(),
caption: caption.as_ref().map(Box::to_static),
ident: ident.as_ref().map(Cow::to_static),
width: *width,
},
Token::Href {
url,
display,
space_before,
} => Token::Href {
url: url.clone(),
display: display.to_static(),
space_before: *space_before,
},
Token::Ayano { data } => Token::Ayano {
data: data.to_static(),
},
Token::List { list_type, content } => Token::List {
list_type: list_type.clone(),
content: content.to_static(),
},
Token::Multiple { tokens } => Token::Multiple {
tokens: tokens.to_static(),
},
Token::Paragraph {
is_newline,
space_before,
formatting,
content,
} => Token::Paragraph {
is_newline: *is_newline,
formatting: formatting.clone(),
content: content.to_static(),
space_before: *space_before,
},
Token::InlineMath {
content,
space_before,
} => Token::InlineMath {
content: content.to_static(),
space_before: *space_before,
},
Token::Reference {
ident,
space_before,
} => Token::Reference {
ident: ident.to_static(),
space_before: *space_before,
},
Token::FootnoteReference {
ident,
space_before,
} => Token::FootnoteReference {
ident: ident.to_static(),
space_before: *space_before,
},
Token::FootnoteContent { content, ident } => Token::FootnoteContent {
content: content.to_static(),
ident: ident.to_static(),
},
Token::CodeBlock { code, language } => Token::CodeBlock {
code: code.to_static(),
language: language.as_ref().map(Cow::to_static),
},
Token::Error { message } => Token::Error {
message: message.clone(),
},
}
}
/// Defines if newline after this token will cause problems
///
/// None means that it depends on previous tokens
pub fn is_newline_allergic(&self) -> Option<bool> {
match self {
Token::Href { .. }
| Token::Paragraph { .. }
| Token::InlineMath { .. }
| Token::Reference { .. }
| Token::FootnoteReference { .. }
| Token::Error { .. } => Some(false),
Token::PageDiv
| Token::Header { .. }
| Token::DisplayMath { .. }
| Token::Table { .. }
| Token::Figure { .. }
| Token::List { .. }
| Token::CodeBlock { .. } => Some(true),
Token::Ayano { .. } | Token::Multiple { .. } | Token::FootnoteContent { .. } => None,
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,241 +0,0 @@
use std::{
fmt::{Debug, Display},
path::Path,
};
use pyo3::prelude::*;
use rand::random;
static MODULE_NAME: &str = "ayano";
#[derive(Debug)]
pub struct InitToken {
module_name: Box<str>,
code: Box<str>, // FIX: idk, probably improve on that
}
fn module_name() -> Box<str> {
format!("{}_{}", MODULE_NAME, random::<u32>()).into_boxed_str()
}
impl InitToken {
fn new(module_name: Box<str>, code: &str) -> Self {
Self {
module_name,
code: Box::from(code),
}
}
fn module_name(&self) -> &str {
&self.module_name
}
fn module<'py>(&self, py: Python<'py>) -> Result<&'py PyModule, PyErr> {
PyModule::import(py, self.module_name())
}
}
pub fn init(code: &str) -> Result<InitToken, TracedPyErr> {
Python::with_gil(move |py| {
let module_name = module_name();
PyModule::from_code(py, code, "", &module_name)
.map_err(|err| TracedPyErr::from_regular(err, py, code))?;
let token = InitToken::new(module_name, code);
Ok(token)
})
}
pub fn append_static(target: &mut String, code: &str, execution_path: impl AsRef<Path>) {
target.push_str(
format!(
"\nimport os\n___cwd___ = os.getcwd()\nos.chdir('{}')",
execution_path.as_ref().to_string_lossy()
)
.as_str(),
);
for line in code.lines() {
target.push_str(format!("\n{line}").as_str());
}
target.push_str("\nos.chdir(___cwd___)");
}
pub fn append_function(target: &mut String, name: &str, code: &str) {
// First, declare a function
target.push_str(format!("\ndef {name}():").as_str());
for line in code.lines() {
target.push_str(format!("\n {line}").as_str());
}
}
#[derive(derive_more::Deref)]
struct CodeString(String);
impl Debug for CodeString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("<some some :skull:>")
}
}
impl Display for CodeString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("\n")?;
for (i, line) in self.0.lines().enumerate() {
writeln!(f, "{i:5}:\t |{line}")?;
}
f.write_str("\n")?;
Ok(())
}
}
// TODO that's a temporary solution to actually be able to debug my code
#[derive(Debug, thiserror::Error)]
#[error("Head: {}, Trace: {:?}, Code: {}", .head, .trace, .code)]
pub struct TracedPyErr {
head: PyErr,
trace: Option<PyResult<String>>,
code: CodeString, // FIX: please change that :skull:
}
impl TracedPyErr {
fn from_regular(err: PyErr, py: Python<'_>, code: &str) -> Self {
let trace_string = err.traceback(py).map(pyo3::types::PyTraceback::format);
Self {
head: err,
trace: trace_string,
code: CodeString(code.to_owned()), // FIX: please change that
}
}
}
pub fn call_function<Out, Err, TErr, Transform>(
token: &InitToken,
name: &str,
transform: Transform,
execution_path: impl AsRef<Path>,
) -> Result<Out, Err>
where
Err: From<TracedPyErr> + From<TErr>,
for<'py> Transform: FnOnce(&'py PyAny, Python<'py>) -> Result<Out, TErr>,
{
fn call_inner<'py>(
token: &InitToken,
name: &str,
execution_path: impl AsRef<Path>,
py: Python<'py>,
) -> Result<&'py PyAny, PyErr> {
let main_module = token.module(py)?;
let os = PyModule::import(py, "os")?;
let cwd = os.getattr("getcwd")?.call0()?;
let chdir = os.getattr("chdir")?;
chdir.call1((execution_path.as_ref(),))?;
let function = main_module.getattr(name)?;
let res = function.call((), None)?;
chdir.call1((cwd,))?;
Ok(res)
}
Python::with_gil::<_, Result<_, Err>>(|py| {
let res = call_inner(token, name, execution_path, py)
.map_err(|err| TracedPyErr::from_regular(err, py, &token.code))?;
Ok(transform(res, py)?)
})
}
#[cfg(test)]
mod tests {
use crate::util::Equivalent;
use super::*;
#[derive(Debug, derive_more::From, PartialEq)]
enum DummyError {
Python(Equivalent<PyErr>),
TracedPython(Equivalent<TracedPyErr>),
}
impl From<PyErr> for DummyError {
fn from(value: PyErr) -> Self {
Self::Python(Equivalent(value))
}
}
impl From<TracedPyErr> for DummyError {
fn from(value: TracedPyErr) -> Self {
Self::TracedPython(Equivalent(value))
}
}
macro_rules! init_functions {
($code:expr, [$(($fun_name:expr, $fun_code:expr)), *]) => {{
let mut code = $code.to_string();
for (fun_name, fun_code) in [$(($fun_name, $fun_code)), *] {
super::append_function(&mut code, fun_name, fun_code);
}
init(code.as_str())
}};
}
#[test]
fn create_empty_should_ok() {
// arrange
let code = "";
// act + assert
let _token = init_functions!(code, []).expect("Should be able to create empty module");
}
#[test]
fn create_valid_should_ok() {
// arrange
let code = "x = 5\ny = x ** 2\nz = y / 10.0";
// act + assert
let _token =
init_functions!(code, []).expect("Should be able to create module with valid code");
}
#[test]
fn create_invalid_should_err() {
// arrange
let code = "x = 5;\ny = **5;\nfn a() {{\n return &'_ 0;\n}}";
// act + assert
let _err = init_functions!(code, []).expect_err("Should deny bad code");
}
#[test]
fn function_absent_should_err() {
// arrange
let code = "x = 5";
let token = init_functions!(code, []).expect("Should be able to create module");
// act + assert
let _res: DummyError = call_function(
&token,
"absent_function",
|res, _| Ok::<_, super::PyErr>(res.str()?.to_string()),
".",
)
.expect_err("Should get error for non-existent function call");
}
#[test]
fn function_present_should_ok() {
// arrange
let code = "x = 5";
let token = init_functions!(code, [("ok_function", "return x")])
.expect("Should be able to create module");
// act
let res: Result<_, DummyError> = call_function(
&token,
"ok_function",
|res, _| Ok::<_, super::PyErr>(res.str()?.to_string()),
".",
);
// assert
assert_eq!(
res.expect("Should be able to execute existing function"),
"5"
);
}
}

View file

@ -1,5 +0,0 @@
x = 5
y = x ** 10
# TOSHINO KYOKO!
x, y = y, x
print(x, y)

View file

@ -1,9 +0,0 @@
a, a_err, b, c, d, d_err
1.0, 0.001, 3, 4, -5, 0.001
1.0, 0.001, 3, 4, -5, 0.001
1.0, 0.001, 3, 4, -5, 0.001
1.0, 0.001, 3, 4, -5, 0.001
1.0, 0.001, 3, 4, -5, 0.001
1.0, 0.001, 3, 4, -5, 0.001
1.0, 0.001, 3, 4, -5, 0.001
1.0, 0.001, 3, 4, -5, 0.001
1 a a_err b c d d_err
2 1.0 0.001 3 4 -5 0.001
3 1.0 0.001 3 4 -5 0.001
4 1.0 0.001 3 4 -5 0.001
5 1.0 0.001 3 4 -5 0.001
6 1.0 0.001 3 4 -5 0.001
7 1.0 0.001 3 4 -5 0.001
8 1.0 0.001 3 4 -5 0.001
9 1.0 0.001 3 4 -5 0.001

View file

@ -1,6 +0,0 @@
#[derive(Debug, smart_default::SmartDefault)]
pub struct Context {
pub inside_caption: bool,
#[default(true)]
pub allergic_to_newline: bool,
}

View file

@ -1,271 +0,0 @@
mod context;
pub use context::Context;
use crate::{
data::{self, Formatting, Token},
path_engine::PathEngine,
};
use std::{borrow::Borrow, fmt::Write, marker::PhantomData};
use super::{GenerationError, OutputGenerator, Res};
#[derive(Debug)]
pub struct Generator<'source, PE, PEErr> {
path_engine: PE,
_phantom: PhantomData<(&'source (), PEErr)>,
}
impl<'source, PE: PathEngine<PEErr>, PEErr> Generator<'source, PE, PEErr> {
pub fn new(path_engine: PE) -> Self {
Self {
path_engine,
_phantom: PhantomData,
}
}
#[allow(unused, clippy::unnecessary_wraps, clippy::unused_self)] // TODO: implement tables in compact format!
fn write_table_row<'meta, 'context, 'token: 'context, W: Write + ?Sized>(
&self,
_output: &mut W,
_context: &'context mut Context,
_cells: impl IntoIterator<Item = &'token Token<'source>>,
) -> Res<'source>
where
'source: 'meta + 'context + 'token,
PE: PathEngine<PEErr>,
PEErr: std::error::Error + 'source,
{
Ok(())
}
pub fn parse_and_write<W: Write + ?Sized>(
path_engine: PE,
input: &'source str,
output: &mut W,
) -> Result<(), Box<dyn std::error::Error + 'source>>
where
PE: PathEngine<PEErr>,
PEErr: std::error::Error + 'source,
{
let (_, tokens) = crate::parse_all(input)?;
let generator: Generator<'source, PE, PEErr> = Self::new(path_engine);
generator.write_preamble(output, &())?;
let mut context = Context::default();
generator.write_to(output, &(), &mut context, &tokens)?;
generator.write_postamble(output, &(), &mut context)?;
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
#[error("A dummy error, should be never thrown")]
pub struct DummyMetaError;
impl<'source, PE, PEErr> OutputGenerator<'source, (), Context> for Generator<'source, PE, PEErr>
where
PE: PathEngine<PEErr>,
PEErr: std::error::Error + 'source,
{
type MetaError = DummyMetaError;
fn write_to<'meta, 'context, 'token, W: Write + ?Sized>(
&self,
output: &mut W,
(): &(),
context: &'context mut Context,
token: &'token Token<'source>,
) -> Res<'source>
where
'source: 'meta + 'context + 'token,
'token: 'context,
{
match token {
Token::PageDiv => {
output.write_str("\n\\clearpage\n")?;
}
Token::Header { order, content } => {
let command = match order {
0..=4 => "subparagraph",
_ => "textbf",
};
write!(output, "\n\\{command}{{{content}}}\n")?;
}
Token::DisplayMath { content, .. } => {
write!(output, "\n$$\n{content}\n$$\n")?;
}
Token::Figure {
src_name, width, ..
} => {
output.write_str("\n\\begin{figure}[h!]\n\\centering\n\\includegraphics[width=")?;
if let Some(width) = width.as_ref() {
output.write_str(&width.to_string())?;
} else {
output.write_str("0.3")?;
}
output.write_str("\\textwidth]{")?;
let src_path = self
.path_engine
.image(src_name.as_ref(), None)
.map_err(|err| GenerationError::Path(Box::new(err)))?;
output.write_str(src_path.to_string_lossy().borrow())?; // FIXME non-utf paths *might* cause an issue here
output.write_str("}\n\\end{figure}\n")?;
}
Token::List { list_type, content } => {
let start: &str = match list_type {
crate::data::ListType::Bullet => "\\begin{itemize}",
crate::data::ListType::Num
| crate::data::ListType::Cyrillic
| crate::data::ListType::Latin => "\\begin{enumerate}",
crate::data::ListType::Roman => {
unimplemented!("Roman numeral lists are not supported for now")
}
};
let end: &str = match list_type {
crate::data::ListType::Bullet => "\\end{itemize}",
crate::data::ListType::Num
| crate::data::ListType::Cyrillic
| crate::data::ListType::Latin => "\\end{enumerate}",
crate::data::ListType::Roman => {
unimplemented!("Roman numeral lists are not supported for now")
}
};
let mut ident_producer = match list_type {
data::ListType::Bullet | data::ListType::Num => None,
t @ (data::ListType::Latin
| data::ListType::Cyrillic
| data::ListType::Roman) => Some(crate::lexer::_list_item_generator(t)),
};
output.write_str("\n")?;
output.write_str(start)?;
for item in content {
if let Some(ident_producer) = ident_producer.as_mut() {
output.write_str("\n\\item[")?;
output.write_str(ident_producer.next().expect("List should not have more elements than there are items in the iterator"))?;
output.write_str("] ")?;
} else {
output.write_str("\n\\item ")?;
}
self.write_to(output, &(), context, item)?;
}
output.write_str("\n")?;
output.write_str(end)?;
}
Token::InlineMath {
content,
space_before,
} => {
if *space_before {
output.write_char(' ')?;
}
write!(output, "${content}$")?;
}
Token::Error { message } => {
eprintln!("{message}");
// TODO add representation in the document
}
Token::Paragraph {
is_newline,
formatting,
content,
space_before,
} => {
let mut content_inner = content.clone();
if *is_newline {
if context.allergic_to_newline {
output.write_str("\n\\tb ")?;
} else {
output.write_str("\n\\tbln ")?;
}
} else if *space_before {
output.write_char(' ')?;
}
let (start, end) = match formatting {
None => ("", ""),
Some(Formatting::Bold) => ("\\textbf{", "}"),
Some(Formatting::Italic) => ("\\textit{", "}"),
Some(Formatting::StrikeThrough) => ("\\st{", "}"),
Some(Formatting::MonoSpace) => ("\\texttt{", "}"),
};
output.write_str(start)?;
if content_inner.contains(['%', '_', '#', '[', ']']) {
// there are special symbols in the paragraph
// most certainly they were not intended to be special latex symbols (because why?)
// TODO add an option to opt this out
// TODO add multiple replacements at once
let mut content_str = content_inner.replace('\\', "\\\\");
content_str = content_str.replace('%', "\\%");
content_str = content_str.replace('#', "\\#");
content_inner = content_str.replace('_', "\\_").into();
}
if context.inside_caption {
// We are generating text that was inside table/figure caption
// meaning, we must un-escape double quotes
let mut last_ind = 0;
for (ind, _) in content_inner.match_indices("\\\"") {
output.write_str(&content_inner[last_ind..ind])?;
output.write_char('\"')?;
last_ind = ind + 2;
}
output.write_str(&content_inner[last_ind..])?;
} else {
output.write_str(&content_inner)?;
}
output.write_str(end)?;
}
Token::Href {
url,
display,
space_before,
} => {
if *space_before {
output.write_char(' ')?;
}
write!(output, "\\href{{{url}}}{{{display}}}")?;
}
Token::Multiple { tokens } => self.write_tokens_to(output, &(), context, tokens)?,
Token::Table { .. }
| Token::Ayano { .. }
| Token::FootnoteReference { .. }
| Token::FootnoteContent { .. }
| Token::CodeBlock { .. }
| Token::Reference { .. } => {
// ignoring all of these (for now)
}
};
if let Some(new_value) = token.is_newline_allergic() {
context.allergic_to_newline = new_value;
}
Ok(())
}
fn write_preamble<'meta, W: Write + ?Sized>(&self, output: &mut W, (): &()) -> Res<'source> {
const PREAMBLE: &str = include_str!("preamble.tex");
output.write_str(PREAMBLE)?;
Ok(())
}
fn write_postamble<'meta, 'context, W: Write + ?Sized>(
&self,
output: &mut W,
(): &(),
_context: &'context mut Context,
) -> Res<'source> {
output.write_str("\n\n\\end{document}")?;
Ok(())
}
fn collect_context<'token>(
&self,
_tokens: &'token Token<'source>,
) -> Result<(), Self::MetaError>
where
'source: 'token,
{
Ok(())
}
fn default_context(&self) -> Context {
context::Context::default()
}
}

View file

@ -1,114 +0,0 @@
\documentclass[a4paper, twocolumn]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 4mm, right = 4mm, top=4mm, bottom=4mm, bindingoffset=0cm]{geometry}
% change line spacing
\renewcommand{\baselinestretch}{0.5}
\usepackage{enumitem}
\setlist{nosep} % or \setlist{noitemsep} to leave space around whole list
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{8pt} {8pt}
\linespread{0.05}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{1mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{0.2ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{-4pt}\setlength{\parskip}{-4pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
% \usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
% bra and ket from QM
\usepackage{braket}
% multicol magic
\usepackage{multicol}
\begin{document}
\fnt

View file

@ -1,9 +0,0 @@
use std::collections::HashMap;
#[derive(Debug, smart_default::SmartDefault)]
pub struct Context {
pub encountered_footnotes: HashMap<String, usize>,
pub inside_caption: bool,
#[default(true)]
pub allergic_to_newline: bool,
}

View file

@ -1,138 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
% bra and ket from QM
\usepackage{braket}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE $header-line1$
\LARGE $header-line2$
\vspace{100pt}
\LARGE $document-type$
\Large $title-line1$
\Large $title-line2$
\Large $title-line3$
\huge $title-line4$
\vspace{50pt}
\large $author-line1$
\large $author-line2$
\large $author-line3$
\large $date$
\vspace*{\fill}
\hspace{\fill}\large $prof$ \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt

View file

@ -1,147 +0,0 @@
use std::{
borrow::{BorrowMut, Cow},
collections::{HashMap, HashSet},
};
use itertools::Itertools;
use crate::{
data::{TitleInfo, Token},
gen::ayano::TracedPyErr,
path_engine::PathEngine,
};
use super::super::ayano::{self, Builder};
#[derive(Debug, thiserror::Error)]
pub enum Error<'l> {
#[error("Failed to recognize Ayano syntax: {}", .0)]
AyanoSyntax(ayano::SyntaxError<'l>),
}
#[derive(Debug, Default)]
pub struct SourceData<'source, Ayano> {
pub footnotes: HashMap<Cow<'source, str>, Token<'source>>,
pub refs: HashSet<Cow<'source, str>>,
pub ayano: Ayano,
pub title_info: TitleInfo<'source>,
}
impl<'source, Ayano: Default> SourceData<'source, Ayano> {
fn empty() -> Self {
Self::default()
}
}
impl<'source> SourceData<'source, Builder> {
pub fn collect<'meta, PEErr: std::error::Error + 'source, PE: PathEngine<PEErr>>(
token: &'meta Token<'source>,
path_engine: &PE,
) -> Result<Self, Error<'source>>
where
'source: 'meta,
{
let mut res = SourceData::empty();
collect_single(token, &mut res, path_engine)?;
Ok(res)
}
pub fn init_ayano(self) -> Result<SourceData<'source, ayano::Executor>, TracedPyErr> {
Ok(SourceData {
ayano: self.ayano.initialize()?,
footnotes: self.footnotes,
refs: self.refs,
title_info: self.title_info,
})
}
}
fn collect_single<'meta, 'r, 'source, PEErr: std::error::Error + 'source, PE: PathEngine<PEErr>>(
token: &'meta Token<'source>,
dest: &'r mut SourceData<'source, Builder>,
path_engine: &PE,
) -> Result<(), Error<'source>>
where
'source: 'meta,
'meta: 'r,
{
match token {
Token::DisplayMath {
ident: Some(ident), ..
} => {
dest.refs.borrow_mut().insert(ident.clone());
}
Token::Table {
header,
ident,
cells,
caption,
} => {
if let Some(ident) = ident {
dest.refs.borrow_mut().insert(ident.clone());
}
collect_iterable(header, dest, path_engine)?;
collect_iterable(cells, dest, path_engine)?;
if let Some(caption) = caption {
collect_single(caption, dest, path_engine)?;
}
}
Token::Figure {
ident: Some(ident),
caption,
src_name: _src_name,
width: _width,
} => {
dest.refs.insert(ident.clone());
if let Some(caption) = caption {
collect_single(caption, dest, path_engine)?;
}
}
Token::Multiple { tokens, .. } => {
collect_iterable(tokens, dest, path_engine)?;
}
Token::FootnoteContent { content, ident } => {
collect_single(content, dest, path_engine)?;
dest.footnotes.insert(ident.clone(), content.borrow_ref());
}
Token::Ayano { data } => {
dest.ayano
.add_block(data, path_engine)
.map_err(Error::AyanoSyntax)?;
}
Token::PageDiv
| Token::Header { .. }
| Token::Href { .. }
| Token::List { .. }
| Token::Paragraph { .. }
| Token::InlineMath { .. }
| Token::Reference { .. }
| Token::FootnoteReference { .. }
| Token::Figure { .. }
| Token::DisplayMath { .. }
| Token::CodeBlock { .. }
| Token::Error { .. } => {}
}
Ok(())
}
fn collect_iterable<
'r,
'source: 'meta,
'meta: 'r,
I,
PEErr: std::error::Error + 'source,
PE: PathEngine<PEErr>,
>(
tokens: I,
dest: &'r mut SourceData<'source, Builder>,
path_engine: &PE,
) -> Result<(), Error<'source>>
where
I: IntoIterator<Item = &'meta Token<'source>>,
{
tokens
.into_iter()
.map(|token| collect_single(token, dest, path_engine))
.try_collect()
}

View file

@ -1,956 +0,0 @@
mod context;
mod meta;
pub use context::Context;
pub use meta::SourceData;
use crate::{
data::{self, Formatting, TitleInfo, Token},
gen::ayano::DisplayInfo,
path_engine::PathEngine,
};
use itertools::Itertools;
use std::{
borrow::{Borrow, Cow},
fmt::Write,
marker::PhantomData,
};
use super::{ayano::Executor, GenerationError, OutputGenerator, Res};
#[derive(Debug)]
pub struct LabaLatex<'source, PE, PEErr> {
path_engine: PE,
_phantom: PhantomData<(&'source (), PEErr)>,
}
impl<'source, PE: PathEngine<PEErr>, PEErr> LabaLatex<'source, PE, PEErr> {
pub fn new(path_engine: PE) -> Self {
Self {
path_engine,
_phantom: PhantomData,
}
}
fn write_table_row<'meta, 'context, 'token: 'context, W: Write + ?Sized>(
&self,
output: &mut W,
meta: &'meta SourceData<'source, Executor>,
context: &'context mut Context,
cells: impl IntoIterator<Item = &'token Token<'source>>,
) -> Res<'source>
where
'source: 'meta + 'context + 'token,
PE: PathEngine<PEErr>,
PEErr: std::error::Error + 'source,
{
let mut cells = cells.into_iter();
let Some(first_cell) = cells.next() else {
return Ok(());
};
// first one must be written separately
self.write_to(output, meta, context, first_cell)?;
for cell in cells {
output.write_str(" & ")?;
self.write_to(output, meta, context, cell)?;
}
output.write_str("\\\\ \\hline\n")?;
Ok(())
}
pub fn parse_and_write<W: Write + ?Sized>(
path_engine: PE,
input: &'source str,
output: &mut W,
) -> Result<(), Box<dyn std::error::Error + 'source>>
where
PE: PathEngine<PEErr>,
PEErr: std::error::Error + 'source,
{
let (title_info, tokens) = crate::parse_all(input)?;
let mut meta: SourceData<'source, Executor> = SourceData::collect(&tokens, &path_engine)
.map_err(|err| GenerationError::Meta(Box::new(err)))?
.init_ayano()?;
let generator: LabaLatex<'source, PE, PEErr> = Self::new(path_engine);
meta.title_info = title_info;
generator.write_preamble(output, &meta)?;
let mut context = Context::default();
generator.write_to(output, &meta, &mut context, &tokens)?;
generator.write_postamble(output, &meta, &mut context)?;
Ok(())
}
}
impl<'source, PE, PEErr> OutputGenerator<'source, SourceData<'source, Executor>, Context>
for LabaLatex<'source, PE, PEErr>
where
PE: PathEngine<PEErr>,
PEErr: std::error::Error + 'source,
{
// TODO: rename to `CollectionError`, or th
type MetaError = Box<dyn std::error::Error + 'source>;
fn write_to<'meta, 'context, 'token, W: Write + ?Sized>(
&self,
output: &mut W,
meta: &'meta SourceData<'source, Executor>,
context: &'context mut Context,
token: &'token Token<'source>,
) -> Res<'source>
where
'source: 'meta + 'context + 'token,
'token: 'context,
{
match token {
Token::PageDiv => {
output.write_str("\n\\clearpage\n")?;
}
Token::Header { order, content } => {
let command = match order {
0 => "section",
1 => "subsection",
2 => "subsubsection",
3 => "paragraph",
4 => "subparagraph",
_ => "textbf",
};
write!(output, "\n\\{command}{{{content}}}\n")?;
}
Token::DisplayMath { content, ident } => {
let label = ident
.as_ref()
.map_or_else(String::new, |ident| format!("\\label{{eq:{}}}\n", ident));
write!(
output,
"\n\\begin{{equation}}\n{content}\n{label}\\end{{equation}}\n"
)?;
}
Token::Table {
header,
cells,
caption,
ident,
} => {
let columns = header.len();
let column_format =
itertools::intersperse(std::iter::repeat("c").take(columns), "|");
write!(output, "\n\\begin{{table}}[h!]\n\\fnt\n\\begin{{center}}\n")?;
output.write_str("\\hskip-1.5cm\\begin{tabular}{|")?;
column_format
.map(|s| {
output.write_str(s)?;
Ok::<(), GenerationError>(())
})
.try_collect()?;
output.write_str("|}\\hline\n")?;
// write header line
self.write_table_row(output, meta, context, header)?;
output.write_str("\\hline\n")?;
'cells: {
let mut rows = cells.chunks(columns);
let Some(first_row) = rows.next() else {
break 'cells;
};
self.write_table_row(output, meta, context, first_row)?;
for row in rows {
self.write_table_row(output, meta, context, row)?;
}
}
output.write_str("\\end{tabular}\n\\stepcounter{tabnum}")?;
if let Some(caption) = caption {
output.write_str("\n\\caption{\\capfnt ")?;
context.inside_caption = true;
self.write_to(output, meta, context, caption)?;
context.inside_caption = false;
output.write_str("}\n")?;
}
if let Some(ident) = ident.as_ref() {
writeln!(output, "\\label{{tab:{ident}}}")?;
}
output.write_str("\\end{center}\n\\end{table}\n")?;
}
Token::Figure {
src_name,
caption,
ident,
width,
} => {
output.write_str("\n\\begin{figure}[h!]\n\\centering\n\\includegraphics[width=")?;
if let Some(width) = width.as_ref() {
output.write_str(&width.to_string())?;
} else {
output.write_str("0.9")?;
}
output.write_str("\\textwidth]{")?;
let src_path = self
.path_engine
.image(src_name.as_ref(), None)
.map_err(|err| GenerationError::Path(Box::new(err)))?;
output.write_str(src_path.to_string_lossy().borrow())?; // FIXME non-utf paths *might* cause an issue here
output.write_str("}\n")?;
if let Some(caption) = caption.as_ref() {
output.write_str("\\caption{\\capfnt ")?;
context.inside_caption = true;
self.write_to(output, meta, context, caption)?;
context.inside_caption = false;
output.write_str("}\n")?;
}
if let Some(ident) = ident.as_ref() {
writeln!(output, "\\label{{fig:{ident}}}")?;
}
output.write_str("\\end{figure}\n")?;
}
Token::Ayano { data } => {
if !data.is_static {
if data.spaces_before == 1 {
output.write_char(' ')?;
} else if data.spaces_before >= 2 && !context.allergic_to_newline {
output.write_str(" \\\\\n[0.0pt]")?;
}
let token = meta.ayano.display_token(data, &self.path_engine)?;
self.write_to(output, meta, context, &token)?;
}
}
Token::List { list_type, content } => {
let start: &str = match list_type {
crate::data::ListType::Bullet => "\\begin{itemize}",
crate::data::ListType::Num
| crate::data::ListType::Cyrillic
| crate::data::ListType::Latin => "\\begin{enumerate}",
crate::data::ListType::Roman => {
unimplemented!("Roman numeral lists are not supported for now")
}
};
let end: &str = match list_type {
crate::data::ListType::Bullet => "\\end{itemize}",
crate::data::ListType::Num
| crate::data::ListType::Cyrillic
| crate::data::ListType::Latin => "\\end{enumerate}",
crate::data::ListType::Roman => {
unimplemented!("Roman numeral lists are not supported for now")
}
};
let mut ident_producer = match list_type {
data::ListType::Bullet | data::ListType::Num => None,
t @ (data::ListType::Latin
| data::ListType::Cyrillic
| data::ListType::Roman) => Some(crate::lexer::_list_item_generator(t)),
};
output.write_str("\n")?;
output.write_str(start)?;
for item in content {
if let Some(ident_producer) = ident_producer.as_mut() {
output.write_str("\n\\item[")?;
output.write_str(ident_producer.next().expect("List should not have more elements than there are items in the iterator"))?;
output.write_str("] ")?;
} else {
output.write_str("\n\\item ")?;
}
self.write_to(output, meta, context, item)?;
}
output.write_str("\n")?;
output.write_str(end)?;
}
Token::InlineMath {
content,
space_before,
} => {
if *space_before {
output.write_char(' ')?;
}
write!(output, "${content}$")?;
}
Token::Reference {
ident,
space_before,
} => {
if *space_before {
output.write_char(' ')?;
}
write!(output, "\\ref{{{ident}}}")?;
}
Token::FootnoteReference {
ident,
space_before,
} => {
if *space_before {
output.write_char(' ')?;
}
let (encountered, i) = {
// TODO probably should move this to separate function
let encountered_map = &mut context.encountered_footnotes;
let encountered = encountered_map.get(ident.borrow() as &str).copied();
let i = if let Some(i) = encountered {
i
} else {
let i: usize = encountered_map.len();
// let ident: &'context Cow<'source, str> = ident;
encountered_map.insert(ident.to_string(), i);
i
};
(encountered.is_some(), i)
};
// write out mark (always done)
write!(output, "\\footnotemark[{}]", i + 1)?;
if !encountered {
// write out content (done only on first occurrence)
if let Some(content) = meta.footnotes.get(ident) {
write!(output, "\\footnotetext[{}]{{", i + 1)?;
self.write_to(output, meta, context, content)?;
output.write_str("}")?;
}
}
}
// might seem weird at first, but these are basically useless at this point, as their content was presumably collected into context
Token::FootnoteContent { .. } => {}
Token::Error { message } => {
eprintln!("{message}");
// TODO add representation in the document
}
Token::Paragraph {
is_newline,
formatting,
content,
space_before,
} => {
let mut content_inner = content.clone();
if *is_newline {
if context.allergic_to_newline {
output.write_str("\n\\tb ")?;
} else {
output.write_str("\n\\tbln ")?;
}
} else if *space_before {
output.write_char(' ')?;
}
let (start, end) = match formatting {
None => ("", ""),
Some(Formatting::Bold) => ("\\textbf{", "}"),
Some(Formatting::Italic) => ("\\textit{", "}"),
Some(Formatting::StrikeThrough) => ("\\st{", "}"),
Some(Formatting::MonoSpace) => ("\\texttt{", "}"),
};
output.write_str(start)?;
if content_inner.contains(['%', '_', '#', '[', ']']) {
// there are special symbols in the paragraph
// most certainly they were not intended to be special latex symbols (because why?)
// TODO add an option to opt this out
// TODO add multiple replacements at once
let mut content_str = content_inner.replace('\\', "\\\\");
content_str = content_str.replace('%', "\\%");
content_str = content_str.replace('#', "\\#");
content_inner = content_str.replace('_', "\\_").into();
}
if context.inside_caption {
// We are generating text that was inside table/figure caption
// meaning, we must un-escape double quotes
let mut last_ind = 0;
for (ind, _) in content_inner.match_indices("\\\"") {
output.write_str(&content_inner[last_ind..ind])?;
output.write_char('\"')?;
last_ind = ind + 2;
}
output.write_str(&content_inner[last_ind..])?;
} else {
output.write_str(&content_inner)?;
}
output.write_str(end)?;
}
Token::Href {
url,
display,
space_before,
} => {
if *space_before {
output.write_char(' ')?;
}
write!(output, "\\href{{{url}}}{{{display}}}")?;
}
Token::Multiple { tokens } => self.write_tokens_to(output, meta, context, tokens)?,
Token::CodeBlock { code, language } => {
let language = language.as_deref().unwrap_or("text");
writeln!(
output,
"\n\\begin{{minted}}[linenos, mathescape, autogobble, breaklines]{{{language}}}\n{code}\n\\end{{minted}}\n"
)?;
}
};
if let Some(new_value) = token.is_newline_allergic() {
context.allergic_to_newline = new_value;
}
Ok(())
}
fn write_preamble<'meta, W: Write + ?Sized>(
&self,
output: &mut W,
meta: &'meta SourceData<'source, Executor>,
) -> Res<'source> {
const PATTERNS: [&str; 12] = [
"$header-line1$",
"$header-line2$",
"$document-type$",
"$title-line1$",
"$title-line2$",
"$title-line3$",
"$title-line4$",
"$author-line1$",
"$author-line2$",
"$author-line3$",
"$date$",
"$prof$",
];
let mut preamble = include_str!("lab_preamble.tex").to_string();
let TitleInfo {
header_line1,
header_line2,
document_type,
title_line1,
title_line2,
title_line3,
title_line4,
author_line1,
author_line2,
author_line3,
date,
prof,
code_section_title: _code_section_title,
} = &meta.title_info;
let replacements = [
header_line1,
header_line2,
document_type,
title_line1,
title_line2,
title_line3,
title_line4,
author_line1,
author_line2,
author_line3,
date,
prof,
];
for i in 0..PATTERNS.len() {
let pat = PATTERNS[i];
if let Some(ind) = preamble.find(pat) {
preamble.replace_range(
ind..ind + pat.len(),
replacements[i].as_ref().unwrap_or(&"".into()),
);
}
}
output.write_str(&preamble)?;
Ok(())
}
fn write_postamble<'meta, 'context, W: Write + ?Sized>(
&self,
output: &mut W,
meta: &'meta SourceData<'source, Executor>,
context: &'context mut Context,
) -> Res<'source> {
let mut display_ayano_blocks = meta.ayano.display_blocks().into_iter();
if let Some(first) = display_ayano_blocks.next() {
fn write_display_block<
'source,
'meta,
'context,
C,
G: OutputGenerator<'source, SourceData<'source, Executor>, C>,
W: Write + ?Sized,
>(
generator: &G,
output: &mut W,
meta: &'meta SourceData<'source, Executor>,
context: &'context mut C,
block: &'meta DisplayInfo,
) -> Res<'source>
where
'source: 'meta + 'context,
{
let block_code_block = Token::CodeBlock {
code: Cow::Owned(block.code.clone()),
language: Some("python".into()),
};
generator.write_to(output, meta, context, &block_code_block)?;
generator.write_to(output, meta, context, &block.caption)?;
output.write_str(" \\\\\n")?;
Ok(())
}
self.write_to(output, meta, context, &Token::PageDiv)?;
let blocks_section_header = Token::Header {
order: 1,
content: meta
.title_info
.code_section_title
.as_ref()
.unwrap_or(&"Code blocks".into())
.clone(),
};
self.write_to(output, meta, context, &blocks_section_header)?;
write_display_block(self, output, meta, context, first)?;
for block in display_ayano_blocks {
write_display_block(self, output, meta, context, block)?;
}
}
output.write_str("\n\n\\end{document}")?;
Ok(())
}
fn collect_context<'token>(
&self,
tokens: &'token Token<'source>,
) -> Result<SourceData<'source, Executor>, Self::MetaError>
where
'source: 'token,
{
Ok(SourceData::collect(tokens, &self.path_engine)?.init_ayano()?)
}
fn default_context(&self) -> Context {
context::Context::default()
}
}
#[cfg(test)]
#[allow(clippy::needless_raw_string_hashes)]
mod tests {
use std::path::Path;
use rand::Rng;
use crate::{
data::{AyanoBlock, DisplayState, Formatting, ListType},
util::StaticDebug,
};
use super::*;
macro_rules! text {
($text:literal) => {
Token::Paragraph {
space_before: false,
is_newline: false,
formatting: None,
content: $text.into(),
}
};
}
macro_rules! tex_sim {
($a:expr, $b:expr) => {
let real = $a
.chars()
.filter(|c| !c.is_ascii_whitespace())
.collect::<String>();
let expected = $b
.chars()
.filter(|c| !c.is_ascii_whitespace())
.collect::<String>();
text_diff::assert_diff(real.as_str(), expected.as_str(), "", 0);
};
}
macro_rules! tokens {
[$($token:expr), *] => {
$crate::data::Tokens::new([$($token), *])
};
}
macro_rules! test {
{$name:ident, $token:expr, $b:expr} => {
#[test]
fn $name() {
use $crate::path_engine::primitive as primitive_engine;
// arrange
let token = $token;
let meta = SourceData::collect(&token, &$crate::path_engine::primitive())
.expect("Should be able to collect")
.init_ayano()
.expect("Should be able to init Ayano");
let generator = LabaLatex::new(primitive_engine());
let mut output_target = String::new();
// act
generator.write_to(&mut output_target, &meta, &mut Context::default(), &token).expect("Should be able to write");
// assert
tex_sim!(output_target, $b);
}
};
}
// Page Divider
test! {clearpage, Token::PageDiv, r"\clearpage"}
// Headers
test! {header0, Token::Header { order: 0, content: "HeaderText".into() }, r"\section{HeaderText}"}
test! {header1, Token::Header { order: 1, content: "HeaderText".into() }, r"\subsection{HeaderText}"}
test! {header2, Token::Header { order: 2, content: "HeaderText".into() }, r"\subsubsection{HeaderText}"}
test! {header3, Token::Header { order: 3, content: "HeaderText".into() }, r"\paragraph{HeaderText}"}
test! {header4, Token::Header { order: 4, content: "HeaderText".into() }, r"\subparagraph{HeaderText}"}
test! {header5, Token::Header { order: 5, content: "HeaderText".into() }, r"\textbf{HeaderText}"}
// Paragraphs
test! {para1, Token::Paragraph{is_newline: false, formatting: None, content: "content, content!!!".into(), space_before: false}, "content, content!!!"}
test! {para2, Token::Paragraph{is_newline: true, formatting: None, content: "content, content!!!".into(), space_before: false}, "\\tb content, content!!!"}
// Formatting
test! {formatting1, Token::Paragraph{is_newline: false, formatting: Some(Formatting::Bold), content: "some text, idk".into(), space_before: false}, "\\textbf{some text, idk}"}
test! {formatting2, Token::Paragraph{is_newline: false, formatting: Some(Formatting::Italic), content: "some text, idk".into(), space_before: false}, "\\textit{some text, idk}"}
test! {formatting3, Token::Paragraph{is_newline: false, formatting: Some(Formatting::StrikeThrough), content: "some text, idk".into(), space_before: false}, "\\st{some text, idk}"}
// Hrefs
test! {href1, Token::Href { url: "https://www.github.com/Dzuchun".parse().unwrap(), display: "My gh page".into(), space_before: false }, r"\href{https://www.github.com/Dzuchun}{My gh page}"}
test! {href2, Token::Href { url: "https://github.com/Dzuchun".parse().unwrap(), display: "Моя сторінка на гітхабі".into(), space_before: false }, r"\href{https://github.com/Dzuchun}{Моя сторінка на гітхабі}"}
// Equations
test! {equations_1, Token::DisplayMath { content: r"B(\alpha, \beta) = \int \limits_{0}^{1} x^{\alpha - 1} (1-x)^{\beta-1} dx".into(), ident: None },
r"
\begin{equation}
B(\alpha, \beta) = \int \limits_{0}^{1} x^{\alpha - 1} (1-x)^{\beta-1} dx
\end{equation}
"}
test! {equations_2, Token::DisplayMath { content: r"B(\alpha, \beta) = \int \limits_{0}^{1} x^{\alpha - 1} (1-x)^{\beta-1} dx".into(), ident: Some("beta_def".into()) },
r"
\begin{equation}
B(\alpha, \beta) = \int \limits_{0}^{1} x^{\alpha - 1} (1-x)^{\beta-1} dx
\label{eq:beta_def}
\end{equation}
"}
// Inline math
test! {inline_mathmode, Token::InlineMath{content:r"\dfrac{\partial x}{\partial t}".into(), space_before: false}, r"$ \dfrac{\partial x}{\partial t} $"}
// Figures
test! {fig1, Token::Figure { src_name: Path::new("apple.jpg").into(), caption: Some(Box::new(text!("a very realistic-looking apple"))), ident: None, width: None }, r"
\begin{figure}[h!]
\centering
\includegraphics[width = 0.9 \textwidth]{apple.jpg}
\caption{\capfnt a very realistic-looking apple}
\end{figure}
"}
test! {fig2, Token::Figure { src_name: Path::new("schema1.jpg").into(), caption: Some(Box::new(text!("A schema providing a bunch of very important info on quantum refurbalidzer function"))), ident: Some("schema-1".into()), width: None }, r"
\begin{figure}[h!]
\centering
\includegraphics[width = 0.9 \textwidth]{schema1.jpg}
\caption{\capfnt A schema providing a bunch of very important info on quantum refurbalidzer function}
\label{fig:schema-1}
\end{figure}
"}
test! {fig3_width, Token::Figure { src_name: Path::new("schema1.jpg").into(), caption: Some(Box::new(text!("A schema providing a bunch of very important info on quantum refurbalidzer function"))), ident: Some("schema-1".into()), width: Some(0.25) }, r"
\begin{figure}[h!]
\centering
\includegraphics[width = 0.25 \textwidth]{schema1.jpg}
\caption{\capfnt A schema providing a bunch of very important info on quantum refurbalidzer function}
\label{fig:schema-1}
\end{figure}
"}
test! {fig4_width, Token::Figure { src_name: Path::new("schema1.jpg").into(), caption: Some(Box::new(text!("This caption contains \\\"double quotes\\\""))), ident: None, width: None }, r#"
\begin{figure}[h!]
\centering
\includegraphics[width = 0.9 \textwidth]{schema1.jpg}
\caption{\capfnt This caption contains "double quotes"}
\end{figure}
"#}
// Tables
test! {tab1, Token::Table {
header:
tokens![
text!("header1"), text!("header2"), text!("header3")
],
cells: tokens![
text!("cell_11"),text!("cell_12"),text!("cell_13"),
text!("cell_21"),text!("cell_22"),text!("cell_23")
],
caption: Some(Box::new(text!("caption..."))),
ident: Some("example".into()) },
r"
\begin{table}[h!]
\fnt
\begin{center}
\hskip-1.5cm\begin{tabular}{|c|c|c|}\hline
header1 & header2 & header3 \\ \hline \hline
cell\_11 & cell\_12 & cell\_13 \\ \hline
cell\_21 & cell\_22 & cell\_23 \\ \hline
\end{tabular}
\stepcounter{tabnum}
\caption{ \capfnt caption...}
\label{tab:example}
\end{center}
\end{table}
"}
test! {tab_quotes, Token::Table {
header:
tokens![
text!("header1"), text!("header2"), text!("header3")
],
cells: tokens![
text!("cell_11"),text!("cell_12"),text!("cell_13"),
text!("cell_21"),text!("cell_22"),text!("cell_23")
],
caption: Some(Box::new(text!("We like \\\"math\\\" so much!"))),
ident: Some("example".into()) },
r#"
\begin{table}[h!]
\fnt
\begin{center}
\hskip-1.5cm\begin{tabular}{|c|c|c|}\hline
header1 & header2 & header3 \\ \hline \hline
cell\_11 & cell\_12 & cell\_13 \\ \hline
cell\_21 & cell\_22 & cell\_23 \\ \hline
\end{tabular}
\stepcounter{tabnum}
\caption{ \capfnt We like "math" so much!}
\label{tab:example}
\end{center}
\end{table}
"#}
// Lists
test! {list_bullets, Token::List { list_type: ListType::Bullet, content: tokens![
text!("point1"),
text!("point2"),
text!("point3")
] }, r"
\begin{itemize}
\item point1
\item point2
\item point3
\end{itemize}
"}
test! {list_numeric, Token::List { list_type: ListType::Num, content: tokens![
text!("point1"),
text!("point2"),
text!("point3")
] }, r"
\begin{enumerate}
\item point1
\item point2
\item point3
\end{enumerate}
"}
test! {list_cyrillic, Token::List { list_type: ListType::Cyrillic, content: tokens![
text!("point1"),
text!("point2"),
text!("point3")
] }, r"
\begin{enumerate}
\item[а.] point1
\item[б.] point2
\item[в.] point3
\end{enumerate}
"}
// Text
test! {text, Token::Multiple{tokens:tokens![Token::PageDiv, Token::Header { order: 0, content: "Header".into() }, text!("Here's a little formula: "), Token::InlineMath{content:r"(a+b)^2 = a^2 + 2 \cdot a \cdot b + b^2".into(), space_before: false}, text!(". It's very useful, more useful than me!")]}, r"
\clearpage
\section{Header}
Here's a little formula: $(a+b)^2 = a^2 + 2 \cdot a \cdot b + b^2$. It's very useful, more useful than me!
"}
// Footnotes
test! {footnote_single, Token::Multiple{tokens:tokens![
Token::FootnoteReference{ident:"explanation".into(), space_before: false},
Token::FootnoteContent { content: Box::new(text!("42")), ident: "explanation".into() }
]}, r"
\footnotemark[1]
\footnotetext[1]{42}
"}
test! {footnote_multiple, Token::Multiple{tokens:tokens![
text!("Here's a first note"),
Token::FootnoteReference{ident:"explanation1".into(), space_before: false},
text!(", there are also second"),
Token::FootnoteReference{ident: "explanation2".into(), space_before: false},
text!(", and the third"),
Token::FootnoteReference{ident:"explanation3".into(), space_before: false},
Token::FootnoteContent { content: Box::new(text!("There are things in this world that's you're not meant to see")), ident: "explanation1".into() },
Token::FootnoteContent { content: Box::new(text!("now the voice of a deity permeates")), ident: "explanation2".into() },
Token::FootnoteContent { content: Box::new(text!("see notes 1 and 2")), ident: "explanation3".into() },
text!(". But notes will not be duplicated here!"),
Token::FootnoteReference{ident:"explanation1".into(), space_before: false}
]}, r"
Here's a first note
\footnotemark[1]
\footnotetext[1]{There are things in this world that's you're not meant to see}
, there are also second
\footnotemark[2]
\footnotetext[2]{now the voice of a deity permeates}
, and the third
\footnotemark[3]
\footnotetext[3]{see notes 1 and 2}
. But notes will not be duplicated here!
\footnotemark[1]
"}
test! {ayano_plain1, Token::Ayano{data: AyanoBlock{
ident: StaticDebug(rand::thread_rng().gen()),
display_state: DisplayState::NotDisplayed,
is_static: false,
code: "1".into(),
insert_path: None,
spaces_before: 0,
}}, r"1"}
test! {ayano_plain2, Token::Ayano{data: AyanoBlock{
ident: StaticDebug(rand::thread_rng().gen()),
display_state: DisplayState::NotDisplayed,
is_static: false,
code: "1 + 2".into(),
insert_path: None,
spaces_before: 0,
}}, r"3"}
test! {ayano_plain3, Token::Ayano{data: AyanoBlock{
ident: StaticDebug(rand::thread_rng().gen()),
display_state: DisplayState::NotDisplayed,
is_static: false,
code: r"
from math import sqrt
x = 103
y = sqrt(x)
int(y)"
.into(),
insert_path: None,
spaces_before: 0,
}}, r"10"}
test! {ayano_plain4, Token::Ayano{data: AyanoBlock{
ident: StaticDebug(rand::thread_rng().gen()),
display_state: DisplayState::NotDisplayed,
is_static: false,
code: r"
res = 0
for i in range(101):
res += i
res"
.into(),
insert_path: None,
spaces_before: 0,
}}, r"5050"}
test! {ayano_err1, Token::Ayano{data: AyanoBlock{
ident: StaticDebug(rand::thread_rng().gen()),
display_state: DisplayState::NotDisplayed,
is_static: false,
code: r"
'err', 1.0, 0.1"
.into(),
insert_path: None,
spaces_before: 0,
}}, r"1.00 $\pm$ 0.10"}
test! {ayano_err2, Token::Ayano{data: AyanoBlock{
ident: StaticDebug(rand::thread_rng().gen()),
display_state: DisplayState::NotDisplayed,
is_static: false,
code: r"
'err', 1.0, 0.4"
.into(),
insert_path: None,
spaces_before: 0,
}}, r"1.0 $\pm$ 0.4"}
test! {ayano_err3, Token::Ayano{data: AyanoBlock{
ident: StaticDebug(rand::thread_rng().gen()),
display_state: DisplayState::NotDisplayed,
is_static: false,
code: r"
x = 1.0
y = 0.4
@dev: x, y"
.into(),
insert_path: None,
spaces_before: 0,
}}, r"1.0 $\pm$ 0.4"
}
test! {ayano_fig1, Token::Ayano{data: AyanoBlock{
ident: StaticDebug(rand::thread_rng().gen()),
display_state: DisplayState::NotDisplayed,
is_static: false,
code: r"'fig', 'path/to/image.jpg', None, None, None".into(),
insert_path: None,
spaces_before: 0,
}}, r"
\begin{figure}[h!]
\centering
\includegraphics[width = 0.9 \textwidth]{path/to/image.jpg}
\end{figure}"
}
test! {ayano_fig2_width, Token::Ayano{data: AyanoBlock{
ident: StaticDebug(rand::thread_rng().gen()),
display_state: DisplayState::NotDisplayed,
is_static: false,
code: "'fig', \"path/to/image.jpg\", None, None, 0.625".into(),
insert_path: None,
spaces_before: 0,
}}, r"
\begin{figure}[h!]
\centering
\includegraphics[width = 0.625 \textwidth]{path/to/image.jpg}
\end{figure}"
}
test! {ayano_fig2, Token::Ayano{data: AyanoBlock{ident: StaticDebug(rand::thread_rng().gen()),display_state: DisplayState::NotDisplayed,is_static: false,code: "@fig: src = \"path/to/image.jpg\"".into(),insert_path: None,spaces_before: 0,}},
r"
\begin{figure}[h!]
\centering
\includegraphics[width = 0.9 \textwidth]{path/to/image.jpg}
\end{figure}"}
test! {ayano_fig3, Token::Ayano{data: AyanoBlock{
ident: StaticDebug(rand::thread_rng().gen()), display_state: DisplayState::NotDisplayed,is_static: false,code: r#"@fig: src = "path/to/image.jpg", ident = "meow""#.into(),insert_path: None,
spaces_before: 0,
}},
r#"
\begin{figure}[h!]
\centering
\includegraphics[width = 0.9 \textwidth]{path/to/image.jpg}
\label{fig:meow}
\end{figure}"#
}
// TODO add tests with captions
test! {ayano_gen_tab1, Token::Ayano{data: AyanoBlock {
ident: StaticDebug(rand::thread_rng().gen()),display_state: DisplayState::NotDisplayed, is_static: false, code:
r#"
data = [[1, 2, 3], [4, 5, 6]]
@gen_table: lambda r,c: data[r][c]; rows=2, columns=3
"#.into(), insert_path: None,
spaces_before: 0, }},
r#"
\begin{table}[h!]
\fnt
\begin{center}
\hskip-1.5cm\begin{tabular}{|c|c|c|}\hline
1 & 2 & 3 \\ \hline \hline
4 & 5 & 6 \\ \hline
\end{tabular}
\stepcounter{tabnum}
\end{center}
\end{table}
"#
}
// TODO add tests with csv-tables
// TODO add minted to preamble
// TODO add -shell-escape to script util
test! {cobe_block1, Token::CodeBlock { code: r"
def f():
x = 5
y = x * x
return y".into(), language: Some("python".into()) },
r"
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
def f():
x = 5
y = x * x
return y
\end{minted}
"}
// TODO probably add more tests for code blocks..?
}

View file

@ -1,101 +0,0 @@
mod ayano;
pub mod lab;
/// Compact latex format
pub mod comp;
use std::fmt::Write;
use itertools::Itertools;
use crate::data::{Token, Tokens};
#[derive(Debug, thiserror::Error)]
pub enum GenerationError<'source> {
#[error(transparent)]
Write(std::fmt::Error),
#[error(transparent)]
Ayano(ayano::Error<'source>),
#[error(transparent)]
Path(Box<dyn std::error::Error + 'source>),
#[error(transparent)]
Meta(Box<dyn std::error::Error + 'source>),
#[error(transparent)]
Python(pyo3::PyErr),
}
impl From<std::fmt::Error> for GenerationError<'_> {
fn from(value: std::fmt::Error) -> Self {
Self::Write(value)
}
}
impl<'source> From<ayano::Error<'source>> for GenerationError<'source> {
fn from(value: ayano::Error<'source>) -> Self {
Self::Ayano(value)
}
}
impl From<pyo3::PyErr> for GenerationError<'_> {
fn from(value: pyo3::PyErr) -> Self {
Self::Python(value)
}
}
pub type Res<'source> = Result<(), GenerationError<'source>>;
// TODO make this thing modular (probably use tower to handle writes or th)
// TODO REMOVE LIFETIME FROM HERE!!!
pub trait OutputGenerator<'source, Meta, Context> {
type MetaError: Into<Box<dyn std::error::Error + 'source>>;
fn write_to<'meta, 'context, 'token, W: Write + ?Sized>(
&self,
output: &mut W,
meta: &'meta Meta,
context: &'context mut Context,
token: &'token Token<'source>,
) -> Res<'source>
where
'source: 'meta + 'context + 'token,
'token: 'context;
fn write_tokens_to<'meta, 'context, 'token, W: Write + ?Sized>(
&self,
output: &mut W,
meta: &'meta Meta,
context: &'context mut Context,
tokens: &'token Tokens<'source>,
) -> Res<'source>
where
'source: 'meta + 'context + 'token,
'token: 'context,
{
tokens
.into_iter()
.map(|t| self.write_to(output, meta, context, t))
.try_collect()
}
fn write_preamble<'meta, W: Write + ?Sized>(&self, _: &mut W, _: &'meta Meta) -> Res<'source> {
Ok(())
}
fn write_postamble<'meta, 'context, W: Write + ?Sized>(
&self,
_: &mut W,
_: &'meta Meta,
_: &'context mut Context,
) -> Res<'source> {
Ok(())
}
fn collect_context<'token>(
&self,
tokens: &'token Token<'source>,
) -> Result<Meta, Self::MetaError>
where
'source: 'token;
fn default_context(&self) -> Context;
}

File diff suppressed because it is too large Load diff

View file

@ -1,124 +0,0 @@
#![doc = include_str!("../README.md")]
use data::{TitleInfo, Token};
use gen::lab;
pub use gen::OutputGenerator;
use lexer::KyomatoLexError;
/// This module defines types that are used to represent parsed data
mod data;
mod gen;
pub use gen::comp::Generator;
pub use gen::lab::LabaLatex;
mod lexer;
pub mod path_engine;
/// Reexports
pub fn lex<'source>(
input: &'source str,
) -> Result<(data::TitleInfo<'source>, data::Token<'source>), KyomatoLexError> {
let (input, title_info) =
lexer::title_info::<'source, KyomatoLexError>(input).map_err(|err| match err {
nom::Err::Error(err) | nom::Err::Failure(err) => err,
nom::Err::Incomplete(_) => unreachable!("This is a complete input"),
})?;
let (_, tokens) = lexer::lex::<'source, KyomatoLexError>(input).map_err(|err| match err {
nom::Err::Error(err) | nom::Err::Failure(err) => err,
nom::Err::Incomplete(_) => unreachable!("This is a complete input"),
})?;
Ok((title_info, tokens))
}
pub fn gen<'token, 'source: 'token, Meta, Context>(
token: &'token data::Token<'source>,
title_info: impl Into<Option<TitleInfo<'source>>>,
generator: impl OutputGenerator<'token, Meta, Context>,
mut output: impl std::io::Write,
) -> Result<(), Box<dyn std::error::Error + 'token>>
where
Meta: MetaExt<'source>,
{
let buf = gen_to_string(token, title_info, generator)?;
// TODO make a transformer struct
output.write_all(buf.as_bytes())?;
Ok(())
}
pub fn gen_to_string<'token, 'source: 'token, Meta, Context>(
token: &'token data::Token<'source>,
title_info: impl Into<Option<TitleInfo<'source>>>,
generator: impl OutputGenerator<'token, Meta, Context>,
) -> Result<String, Box<dyn std::error::Error + 'token>>
where
Meta: MetaExt<'source>,
{
let mut meta = generator
.collect_context(token)
.map_err(std::convert::Into::into)?;
if let Some(title_info) = title_info.into() {
meta.set_title_info(title_info); // TODO make title declarations tokens too!
}
let mut buf = String::new();
generator.write_preamble(&mut buf, &meta)?;
let mut context = generator.default_context();
generator.write_to(&mut buf, &meta, &mut context, token)?;
generator.write_postamble(&mut buf, &meta, &mut context)?;
Ok(buf)
}
pub fn parse_title_info<'source>(
input: &mut &'source str,
) -> Result<TitleInfo<'source>, KyomatoLexError> {
let (rest, title_info) =
lexer::title_info::<'source, KyomatoLexError>(input).map_err(|err| match err {
nom::Err::Error(err) | nom::Err::Failure(err) => err,
nom::Err::Incomplete(_) => unreachable!("This input is complete"),
})?;
*input = rest;
Ok(title_info)
}
pub fn parse_tokens<'source>(input: &mut &'source str) -> Result<Token<'source>, KyomatoLexError> {
let (rest, tokens) =
lexer::lex::<'source, KyomatoLexError>(input).map_err(|err| match err {
nom::Err::Error(err) | nom::Err::Failure(err) => err,
nom::Err::Incomplete(_) => unreachable!("This input is complete"),
})?;
*input = rest;
Ok(tokens)
}
pub fn parse_all(mut input: &str) -> Result<(TitleInfo<'_>, Token<'_>), KyomatoLexError> {
let title_info = parse_title_info(&mut input)?;
let tokens = parse_tokens(&mut input)?;
Ok((title_info, tokens))
}
/// This module houses "utility-like" structs and functions.
///
/// Since this is generally a bad practice, I'd like to know suggestions on std library/other crates items I could use instead
mod util;
pub use util::FmtToIo;
// TODO rename & move to approprite place, it's me patching holes in panic (I'm sorry me, from the
// future, ми все проїбали ((((( )
pub trait MetaExt<'source> {
fn set_title_info<'title>(&mut self, title_info: TitleInfo<'title>)
where
'title: 'source;
}
impl<'source, Aynao> MetaExt<'source> for lab::SourceData<'source, Aynao> {
fn set_title_info<'title>(&mut self, title_info: TitleInfo<'title>)
where
'title: 'source,
{
self.title_info = title_info;
}
}
impl<'source> MetaExt<'source> for () {
fn set_title_info<'title>(&mut self, _title_info: TitleInfo)
where
'title: 'source,
{
}
}

View file

@ -1,612 +0,0 @@
use std::{
borrow::Cow,
error::Error,
path::{Path, PathBuf},
};
/// Represents a context of occurred error
#[derive(Debug, Clone, Copy)]
pub enum ErrorContext {
Source,
Ayano,
Specific,
Execution,
None,
}
pub trait EngineError: Error {
fn from_generic(error: Box<dyn Error>, context: ErrorContext) -> Self;
fn append(&mut self, other: Box<dyn Error>, context: ErrorContext);
fn record_path<'l>(&mut self, path: impl Into<Cow<'l, Path>>);
}
/// An error type with no state - It's only mission is to report an error happening
#[derive(Debug, thiserror::Error, PartialEq)]
#[error("Could not resolve a path")]
pub struct MarkerError;
impl EngineError for MarkerError {
fn append(&mut self, _other: Box<dyn Error>, _context: ErrorContext) {}
fn from_generic(_: Box<dyn Error>, _: ErrorContext) -> Self {
MarkerError
}
fn record_path<'l>(&mut self, _: impl Into<Cow<'l, Path>>) {}
}
/// An error holding rich information about reason resolution had failed
#[derive(Debug, thiserror::Error, Default)]
#[error("{:?}", self)]
pub struct InformativeError {
source: Option<Box<dyn Error>>,
ayano: Option<Box<dyn Error>>,
specific: Option<Box<dyn Error>>,
execution: Option<Box<dyn Error>>,
none: Option<Box<dyn Error>>,
path: Option<PathBuf>,
}
impl EngineError for InformativeError {
fn append(&mut self, other: Box<dyn Error>, context: ErrorContext) {
match context {
ErrorContext::Source => self.source.insert(other),
ErrorContext::Ayano => self.ayano.insert(other),
ErrorContext::Specific => self.specific.insert(other),
ErrorContext::Execution => self.execution.insert(other),
ErrorContext::None => self.none.insert(other),
};
}
fn from_generic(error: Box<dyn Error>, context: ErrorContext) -> Self {
let mut this = Self::default();
this.append(error, context);
this
}
fn record_path<'l>(&mut self, path: impl Into<Cow<'l, Path>>) {
if self.path.is_none() {
self.path = Some(path.into().into_owned());
}
}
}
#[cfg(test)]
impl PartialEq for InformativeError {
fn eq(&self, other: &Self) -> bool {
format!("{self:?}") == format!("{other:?}")
}
}
type CheckType = fn(&Path) -> Result<(), Box<dyn Error>>;
/// A trait representing a `thing` that controls file path finding for `OutputGenerator`.
///
/// You can implement it, but you are not supposed to. There are composition methods to build one yourself along with predefined presets.
pub trait PathEngine<E> {
fn image<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
ayano_insert: Option<&Path>,
) -> Result<Cow<'p, Path>, E>;
fn csv<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
ayano_insert: Option<&Path>,
) -> Result<Cow<'p, Path>, E>;
fn ayano_insert<'p>(&self, path: impl Into<Cow<'p, Path>>) -> Result<Cow<'p, Path>, E>;
fn set_check(&mut self, new_check: CheckType);
fn get_check(&self) -> CheckType;
fn get_source_folder(&self) -> Option<&Path>;
}
pub trait EngineExt<E>: PathEngine<E> + Sized {
fn with_ayano(self) -> AyanoRespecting<Self> {
AyanoRespecting(self)
}
fn with_image_folder(self, folder: impl Into<PathBuf>) -> WithImageFolder<Self> {
WithImageFolder(self, folder.into())
}
fn with_execution_folder(self, folder: impl Into<PathBuf>) -> WithExecutionFolder<Self> {
WithExecutionFolder(self, folder.into())
}
fn with_source_folder(self, folder: impl Into<PathBuf>) -> WithSourceFolder<Self> {
WithSourceFolder(self, folder.into())
}
fn with_check(mut self, new_check: CheckType) -> impl PathEngine<E> + Sized {
self.set_check(new_check);
self
}
fn with_error_type<NewE: EngineError>(self) -> impl PathEngine<NewE> + Sized
where
Self: PathEngine<NewE>,
{
self
}
}
impl<E, PE> EngineExt<E> for PE where PE: PathEngine<E> {}
/// A primitive path engine, that returns, well, exact paths it's given. It literally does nothing.
pub struct Engine(CheckType);
impl<E: EngineError> PathEngine<E> for Engine {
fn image<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
_: Option<&Path>,
) -> Result<Cow<'p, Path>, E> {
let path: Cow<'p, Path> = path.into();
if let Err(err) = (self.0)(&path) {
let mut error = E::from_generic(err, ErrorContext::None);
error.record_path(path);
return Err(error);
}
Ok(path)
}
fn csv<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
_: Option<&Path>,
) -> Result<Cow<'p, Path>, E> {
let path: Cow<'p, Path> = path.into();
if let Err(err) = (self.0)(&path) {
let mut error = E::from_generic(err, ErrorContext::None);
error.record_path(path);
return Err(error);
}
Ok(path)
}
fn ayano_insert<'p>(&self, path: impl Into<Cow<'p, Path>>) -> Result<Cow<'p, Path>, E> {
let path: Cow<'p, Path> = path.into();
if let Err(err) = (self.0)(&path) {
let mut error = E::from_generic(err, ErrorContext::None);
error.record_path(path);
return Err(error);
}
Ok(path)
}
fn set_check(&mut self, new_check: CheckType) {
self.0 = new_check;
}
fn get_check(&self) -> CheckType {
self.0
}
fn get_source_folder(&self) -> Option<&Path> {
None
}
}
/// Represents various reasons file path may not be correct
#[derive(Debug, derive_more::From, thiserror::Error)]
pub enum VariantError {
#[error(transparent)]
Io(std::io::Error), // missing permissions, or broken symbolic links
#[error("file does not exist")]
NotExist, // file does not in fact exist in the filesystem
#[error("path is not of a file")]
NotAFile, // path resolves to a directory, not a file
}
/// A path-checking function always positive about path being correct
pub fn no_check(_: &Path) -> Result<(), Box<dyn Error>> {
Ok(())
}
/// A path-checking function actually checking if file exists via normal means
pub fn fs_check(path: &Path) -> Result<(), Box<dyn Error>> {
match path.try_exists() {
Ok(true) => {
if path.is_file() {
Ok(())
} else {
Err(Box::new(VariantError::NotAFile))
}
}
Err(io) => Err(Box::new(VariantError::Io(io))),
Ok(false) => Err(Box::new(VariantError::NotExist)),
}
}
macro_rules! try_variant {
($this_path:expr, $this_variant:ident, $self:ident, $method:ident, $path:ident $(, $arg:tt)*) => {{
let path = $path.into();
// first, try this path
let this_error = if let Some(this_path) = $this_path.as_ref() {
let this_guess = this_path.join(&path);
let Err(err) = $self.get_check()(&this_guess) else {
return Ok(Cow::Owned(this_guess));
};
Some(err)
} else {
None
};
// Then, attempt to delegate deeper
match $self.0.$method(path $(, $arg)*) {
// if inner engine is successful, return it's result
Ok(res) => Ok(res),
// if it's not -- append this error to it's error and propagate
Err(mut err) => {
if let Some(this_error) = this_error {
err.append(this_error, ErrorContext::$this_variant);
}
Err(err)
}
}
}};
}
macro_rules! impl_check_delegate {
() => {
fn set_check(&mut self, new_check: CheckType) {
self.0.set_check(new_check)
}
fn get_check(&self) -> CheckType {
self.0.get_check()
}
};
}
pub struct AyanoRespecting<PE>(PE);
impl<E: EngineError, PE: PathEngine<E>> PathEngine<E> for AyanoRespecting<PE> {
fn image<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
ayano_insert: Option<&Path>,
) -> Result<Cow<'p, Path>, E> {
let path = path.into();
// ayano insertion is kinda different in a way that `ayano_insert` is a file that's being inserted
// first, try this path
let this_error = if let Some(ayano_insert) = ayano_insert {
// ayano_insert is presumably a valid script path
// hence, if there's not parent, file must be located at the current directory
let ayano_insert_dir = ayano_insert.parent().unwrap_or(Path::new("./"));
let this_guess = ayano_insert_dir.join(&path);
let Err(err) = self.get_check()(&this_guess) else {
return Ok(Cow::Owned(this_guess));
};
Some(err)
} else {
None
};
// Then, attempt to delegate deeper
match self.0.image(path, ayano_insert) {
// if inner engine is successful, return it's result
Ok(res) => Ok(res),
// if it's not -- append this error to it's error and propagate
Err(mut err) => {
if let Some(this_error) = this_error {
err.append(this_error, ErrorContext::Ayano);
}
Err(err)
}
}
}
fn csv<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
ayano_insert: Option<&Path>,
) -> Result<Cow<'p, Path>, E> {
let path = path.into();
// ayano insertion is kinda different in a way that `ayano_insert` is a file that's being inserted
// first, try this path
let this_error = if let Some(ayano_insert) = ayano_insert {
// ayano_insert is presumably a valid script path
// hence, if there's not parent, file must be located at the current directory
let ayano_insert_dir = ayano_insert.parent().unwrap_or(Path::new("./"));
let this_guess = ayano_insert_dir.join(&path);
let Err(err) = self.get_check()(&this_guess) else {
return Ok(Cow::Owned(this_guess));
};
Some(err)
} else {
None
};
// Then, attempt to delegate deeper
match self.0.csv(path, ayano_insert) {
// if inner engine is successful, return it's result
Ok(res) => Ok(res),
// if it's not -- append this error to it's error and propagate
Err(mut err) => {
if let Some(this_error) = this_error {
err.append(this_error, ErrorContext::Ayano);
}
Err(err)
}
}
}
fn ayano_insert<'p>(&self, path: impl Into<Cow<'p, Path>>) -> Result<Cow<'p, Path>, E> {
self.0.ayano_insert(path)
}
impl_check_delegate! {}
fn get_source_folder(&self) -> Option<&Path> {
self.0.get_source_folder()
}
}
pub struct WithImageFolder<PE>(PE, PathBuf);
impl<E: EngineError, PE: PathEngine<E>> PathEngine<E> for WithImageFolder<PE> {
fn image<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
ayano_insert: Option<&Path>,
) -> Result<Cow<'p, Path>, E> {
try_variant!(Some(&self.1), Specific, self, image, path, ayano_insert)
}
fn csv<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
ayano_insert: Option<&Path>,
) -> Result<Cow<'p, Path>, E> {
self.0.csv(path, ayano_insert)
}
fn ayano_insert<'p>(&self, path: impl Into<Cow<'p, Path>>) -> Result<Cow<'p, Path>, E> {
self.0.ayano_insert(path)
}
impl_check_delegate! {}
fn get_source_folder(&self) -> Option<&Path> {
self.0.get_source_folder()
}
}
pub struct WithSourceFolder<PE>(PE, PathBuf);
impl<E: EngineError, PE: PathEngine<E>> PathEngine<E> for WithSourceFolder<PE> {
fn image<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
ayano_insert: Option<&Path>,
) -> Result<Cow<'p, Path>, E> {
try_variant!(Some(&self.1), Source, self, image, path, ayano_insert)
}
fn csv<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
ayano_insert: Option<&Path>,
) -> Result<Cow<'p, Path>, E> {
try_variant!(Some(&self.1), Source, self, csv, path, ayano_insert)
}
fn ayano_insert<'p>(&self, path: impl Into<Cow<'p, Path>>) -> Result<Cow<'p, Path>, E> {
try_variant!(Some(&self.1), Source, self, ayano_insert, path)
}
impl_check_delegate! {}
fn get_source_folder(&self) -> Option<&Path> {
Some(&self.1)
}
}
pub struct WithExecutionFolder<PE>(PE, PathBuf);
impl<E: EngineError, PE: PathEngine<E>> PathEngine<E> for WithExecutionFolder<PE> {
fn image<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
ayano_insert: Option<&Path>,
) -> Result<Cow<'p, Path>, E> {
try_variant!(Some(&self.1), Execution, self, image, path, ayano_insert)
}
fn csv<'p>(
&self,
path: impl Into<Cow<'p, Path>>,
ayano_insert: Option<&Path>,
) -> Result<Cow<'p, Path>, E> {
try_variant!(Some(&self.1), Execution, self, csv, path, ayano_insert)
}
fn ayano_insert<'p>(&self, path: impl Into<Cow<'p, Path>>) -> Result<Cow<'p, Path>, E> {
try_variant!(Some(&self.1), Execution, self, ayano_insert, path)
}
impl_check_delegate! {}
fn get_source_folder(&self) -> Option<&Path> {
self.0.get_source_folder()
}
}
/// A basic engine
///
/// Generic method. Rust will most likely complain about type inference if you use it directly;
/// Make sure to add at least one `with_th_error`.
pub fn base_gen<E: EngineError>() -> impl PathEngine<E> + Sized {
Engine(no_check)
}
/// The most primitive engine setup, performs no checks and has no special folders
pub fn primitive() -> impl PathEngine<MarkerError> + Sized {
base_gen()
}
/// A primitive engine, but with informative error and `fs_check`
pub fn primitive_dbg() -> impl PathEngine<InformativeError> + Sized {
base_gen()
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[derive(Debug, thiserror::Error)]
#[error("A path {} does not exist", .0.to_string_lossy())]
struct DontExist(PathBuf);
macro_rules! test_check {
($($existing_path:literal), *) => {{
fn test_check(path: &Path) -> Result<(), Box<dyn Error>> {
const EXISTING_PATHS: &[&str] = &[$($existing_path), *]; // elided array sizes are unstable :(
if EXISTING_PATHS.contains(&&*path.to_string_lossy()) {
Ok(())
} else {
Err(Box::new(DontExist(path.to_owned())))
}
}
test_check
}};
}
macro_rules! test_ok {
{$name:ident, $engine:expr, $method:ident, $test_path:literal, $ayano_path:expr, $expected_res:expr} => {
test_ok!{$name, $engine.$method(Path::new($test_path), $ayano_path.map(|p: &str| Path::new(p).into())), $expected_res}
};
{$name:ident, $engine:expr, $test_path:literal, $expected_res:expr} => {
test_ok!{$name, $engine.ayano_insert(Path::new($test_path)), $expected_res}
};
{$name:ident, $call_expr:expr, $expected_res:expr} => {
#[test]
fn $name() {
// arrange
// act
let engine_result = $call_expr.expect("Should be ok");
// assert
assert_eq!(Path::new($expected_res), engine_result, "Path engine should produce expected result");
}
};
}
macro_rules! test_expr {
{$name:ident, $engine:expr, $method:ident, $test_path:literal, $ayano_path:expr, $expected_res:expr} => {
test_expr!{$name, $engine.$method(Path::new($test_path), $ayano_path.map(|p: &str| Path::new(p).into())), $expected_res}
};
{$name:ident, $engine:expr, $test_path:literal, $expected_res:expr} => {
test_expr!{$name, $engine.ayano_insert(Path::new($test_path)), $expected_res}
};
{$name:ident, $call_expr:expr, $expected_res:expr} => {
#[test]
fn $name() {
// arrange
// act
let engine_result = $call_expr;
// assert
assert_eq!($expected_res, engine_result, "Path engine should produce expected result");
}
};
}
macro_rules! test_pat {
{$name:ident, $engine:expr, $method:ident, $test_path:literal, $ayano_path:expr, $expected_res:pat} => {
test_pat!{$name, $engine.$method(Path::new($test_path), $ayano_path.map(|p: &str| Path::new(p).into())), $expected_res}
};
{$name:ident, $engine:expr, $test_path:literal, $expected_res:pat} => {
test_pat!{$name, $engine.ayano_insert(Path::new($test_path)), $expected_res}
};
{$name:ident, $call_expr:expr, $expected_res:pat} => {
#[test]
fn $name() {
// arrange
// act
let engine_result = $call_expr;
// assert
#[allow(clippy::redundant_pattern_matching)]
if !matches!(engine_result, $expected_res) {
panic!("Path engine should produce matching result: {engine_result:?}");
}
}
};
}
test_ok! {ok_simple_ayano, primitive(), "scripts/calc.py", "scripts/calc.py"}
test_pat! {err_deny_ayano, primitive().with_check(test_check!()), "scripts/calc.py", Err(_)}
macro_rules! paths_set_engine {
($($existing_path:literal), *) => {
primitive_dbg()
.with_check(test_check!($($existing_path), *))
.with_execution_folder(PathBuf::from_str("./execution").unwrap())
.with_source_folder(PathBuf::from_str("./source").unwrap())
.with_image_folder(PathBuf::from_str("./image").unwrap())
.with_ayano()
};
}
test_ok! {ok_find_ayano_correct1, paths_set_engine!("./execution/calc.py", "./image/calc.py"), "calc.py", "./execution/calc.py"}
test_ok! {ok_find_ayano_correct2, paths_set_engine!("./execution/calc.py", "./image/calc.py", "./source/calc.py"), image, "calc.py", None, "./image/calc.py"}
test_ok! {ok_find_ayano_correct2_2, paths_set_engine!("./execution/calc.py", "./source/calc.py"), image, "calc.py", None, "./source/calc.py"}
test_ok! {ok_find_ayano_correct2_3, paths_set_engine!("./execution/calc.py"), image, "calc.py", None, "./execution/calc.py"}
test_ok! {ok_find_ayano_correct2_4, paths_set_engine!("./execution/calc.py", "./image/calc.py", "./source/calc.py", "./ayano/calc.py"), image, "calc.py", Some("./ayano/calc.py"), "./ayano/calc.py"}
test_ok! {ok_find_ayano_correct3, paths_set_engine!("./execution/table.csv", "./image/table.csv", "./source/table.csv"), csv, "table.csv", None, "./source/table.csv"}
test_ok! {ok_find_ayano_correct3_2, paths_set_engine!("./execution/table.csv", "./image/table.csv"), csv, "table.csv", None, "./execution/table.csv"}
test_pat! {err_find_ayano_correct3_3, paths_set_engine!("./image/table.csv"), csv, "table.csv", None, Err(_)}
macro_rules! err {
($path:literal) => {{
Some(Box::new(DontExist(PathBuf::from_str($path).unwrap())) as Box<dyn Error>)
}};
}
test_expr! {err_reaction1, primitive_dbg().with_check(test_check!()), csv, "file.txt", None,
Err(InformativeError {
source: None,
ayano: None,
specific: None,
execution: None,
none: err!("file.txt"),
path: Some(PathBuf::from("file.txt")),
})}
test_expr! {err_reaction2, primitive_dbg().with_ayano().with_check(test_check!()), csv, "file.txt", Some("./ayano/script.py"),
Err(InformativeError {
source: None,
ayano: err!("./ayano/file.txt"),
specific: None,
execution: None,
none: err!("file.txt"),
path: Some(PathBuf::from("file.txt")),
})}
test_expr! {err_reaction3, primitive_dbg().with_ayano().with_execution_folder(Path::new("./execution")).with_check(test_check!()), csv, "file.txt", Some("./ayano/script.py"),
Err(InformativeError {
source: None,
ayano: err!("./ayano/file.txt"),
specific: None,
execution: err!("./execution/file.txt"),
none: err!("file.txt"),
path: Some(PathBuf::from("file.txt")),
})}
test_expr! {err_reaction4, primitive_dbg().with_ayano().with_image_folder(Path::new("./image")).with_execution_folder(Path::new("./execution")).with_check(test_check!()), csv, "file.txt", Some("./ayano/script.py"),
Err(InformativeError {
source: None,
ayano: err!("./ayano/file.txt"),
specific: None,
execution: err!("./execution/file.txt"),
none: err!("file.txt"),
path: Some(PathBuf::from("file.txt")),
})}
test_expr! {err_reaction5, primitive_dbg().with_ayano().with_image_folder(Path::new("./image")).with_execution_folder(Path::new("./execution")).with_check(test_check!()), image, "file.txt", Some("./ayano/script.py"),
Err(InformativeError {
source: None,
ayano: err!("./ayano/file.txt"),
specific: err!("./image/file.txt"),
execution: err!("./execution/file.txt"),
none: err!("file.txt"),
path: Some(PathBuf::from("file.txt")),
})}
test_expr! {err_reaction6, primitive_dbg().with_ayano().with_source_folder(Path::new("./source")).with_image_folder(Path::new("./image")).with_execution_folder(Path::new("./execution")).with_check(test_check!()), image, "file.txt", Some("./ayano/script.py"),
Err(InformativeError {
source: err!("./source/file.txt"),
ayano: err!("./ayano/file.txt"),
specific: err!("./image/file.txt"),
execution: err!("./execution/file.txt"),
none: err!("file.txt"),
path: Some(PathBuf::from("file.txt")),
})}
}

View file

@ -1,480 +0,0 @@
use std::{
collections::VecDeque,
mem::MaybeUninit,
num::ParseIntError,
ops::{Range, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive},
str::FromStr,
};
use nom::{error::ParseError, IResult, Parser};
#[derive(Debug, Clone, PartialEq)]
pub enum GenericRange {
DoubleBounded(Range<usize>),
DoubleBoundedInclusive(RangeInclusive<usize>),
From(RangeFrom<usize>),
To(RangeTo<usize>),
ToInclusive(RangeToInclusive<usize>),
Full,
}
impl GenericRange {
pub fn from(&self) -> Option<usize> {
match self {
GenericRange::DoubleBounded(range) => Some(range.start),
GenericRange::DoubleBoundedInclusive(range) => Some(*range.start()),
GenericRange::From(range) => Some(range.start),
GenericRange::To(_) | GenericRange::ToInclusive(_) | GenericRange::Full => None,
}
}
pub fn to(&self) -> Option<usize> {
match self {
GenericRange::DoubleBounded(range) => Some(range.end - 1),
GenericRange::DoubleBoundedInclusive(range) => Some(*range.end()),
GenericRange::From(_) | GenericRange::Full => None,
GenericRange::ToInclusive(range) => Some(range.end),
GenericRange::To(range) => Some(range.end - 1),
}
}
}
#[derive(Debug, PartialEq, thiserror::Error)]
pub enum GenericRangeParseError {
#[error("Generic range format is wrong")]
Format,
#[error("{}", .0)]
Object(ParseIntError),
}
impl FromStr for GenericRange {
type Err = GenericRangeParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == ".." {
return Ok(GenericRange::Full);
}
match (s.starts_with(".."), s.ends_with(".."), s.contains('=')) {
(false, false, false) => {
let Some((from, to)) = s.split_once("..") else {
return Err(GenericRangeParseError::Format);
};
let from: usize = from.parse().map_err(GenericRangeParseError::Object)?;
let to: usize = to.parse().map_err(GenericRangeParseError::Object)?;
Ok(GenericRange::DoubleBounded(from..to))
}
(false, false, true) => {
let Some((from, to)) = s.split_once("..=") else {
return Err(GenericRangeParseError::Format);
};
let from: usize = from.parse().map_err(GenericRangeParseError::Object)?;
let to: usize = to.parse().map_err(GenericRangeParseError::Object)?;
Ok(GenericRange::DoubleBoundedInclusive(from..=to))
}
(false, true, false) => {
let Some((from, "")) = s.split_once("..") else {
return Err(GenericRangeParseError::Format);
};
let from: usize = from.parse().map_err(GenericRangeParseError::Object)?;
Ok(GenericRange::From(from..))
}
(true, false, false) => {
let Some(("", to)) = s.split_once("..") else {
return Err(GenericRangeParseError::Format);
};
let to: usize = to.parse().map_err(GenericRangeParseError::Object)?;
Ok(GenericRange::To(..to))
}
(true, false, true) => {
let Some(("", to)) = s.split_once("..=") else {
return Err(GenericRangeParseError::Format);
};
let to: usize = to.parse().map_err(GenericRangeParseError::Object)?;
Ok(GenericRange::ToInclusive(..=to))
}
_ => Err(GenericRangeParseError::Format),
}
}
}
#[cfg(test)]
mod generic_rage_tests {
use super::*;
macro_rules! test {
{$name:ident, $input:literal, $output:expr} => {
#[test]
fn $name() {
// arrange
// act
let output: Result<GenericRange, _> = $input.parse();
// assert
assert_eq!(output.expect("Should be able to parse"), $output);
}
};
}
test! {full, "..", GenericRange::Full}
test! {from, "1..", GenericRange::From(1..)}
test! {double_ended, "1..10", GenericRange::DoubleBounded(1..10)}
test! {double_ended_inclusive, "10..=100", GenericRange::DoubleBoundedInclusive(10..=100)}
test! {to, "..100", GenericRange::To(..100)}
test! {to_inclusive, "..=100", GenericRange::ToInclusive(..=100)}
test! {idk1, "5..=100", GenericRange::DoubleBoundedInclusive(5..=100)}
}
#[repr(transparent)]
#[derive(Debug, derive_more::From, derive_more::Deref, derive_more::DerefMut)]
pub struct Equivalent<T>(pub T);
impl<T> PartialEq for Equivalent<T> {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl<T> Eq for Equivalent<T> {}
#[derive(derive_more::From)]
#[repr(transparent)]
#[derive(Debug, derive_more::Deref)]
// XXX REMOVE THAT, IT VERY-VERY BAD SEMANTICALLY!!!!
pub struct Immutable<T>(T);
#[derive(Debug)]
pub struct InlinedStack<'v, T> {
inner: &'v mut Vec<T>,
}
impl<'v, T> InlinedStack<'v, T> {
pub fn new(inner: &'v mut Vec<T>) -> Self {
Self { inner }
}
pub fn accessor<'l>(&'l mut self) -> InlinedStackAccessor<'l, 'v, T> {
InlinedStackAccessor {
recorded_size: self.inner.len(),
reference: self,
}
}
}
pub struct InlinedStackAccessor<'l, 'v: 'l, T> {
reference: &'l mut InlinedStack<'v, T>,
recorded_size: usize,
}
impl<'l, 'v: 'l, T> InlinedStackAccessor<'l, 'v, T> {
pub fn push(&mut self, t: T) {
self.reference.inner.push(t);
}
pub fn accessor<'l1>(&'l1 mut self) -> InlinedStackAccessor<'l1, 'v, T> {
InlinedStackAccessor {
recorded_size: self.reference.inner.len(),
reference: self.reference,
}
}
pub fn revert(&mut self) {
self.reference.inner.truncate(self.recorded_size);
}
}
#[cfg(test)]
mod inline_stack_tests {
use super::InlinedStack;
#[test]
fn create() {
// arrange
let mut v = vec![1, 2, 3];
// act
let mut stack = InlinedStack::new(&mut v);
let _accessor = stack.accessor();
// assert
assert_eq!(v, vec![1, 2, 3]);
}
#[test]
fn push() {
// arrange
let mut v = vec![1, 2, 3];
let mut stack = InlinedStack::new(&mut v);
let mut accessor = stack.accessor();
// act
accessor.push(4);
accessor.push(5);
// assert
assert_eq!(v, vec![1, 2, 3, 4, 5]);
}
#[test]
fn push_inner() {
// arrange
let mut v = vec![1, 2, 3];
let mut stack = InlinedStack::new(&mut v);
let mut accessor = stack.accessor();
let mut accessor_inner = accessor.accessor();
// act
accessor_inner.push(4);
accessor_inner.push(5);
// assert
assert_eq!(v, vec![1, 2, 3, 4, 5]);
}
#[test]
fn revert() {
// arrange
let mut v = vec![1, 2, 3];
let mut stack = InlinedStack::new(&mut v);
let mut accessor = stack.accessor();
accessor.push(4);
accessor.push(5);
// act
accessor.revert();
// assert
assert_eq!(v, vec![1, 2, 3]);
}
#[test]
fn revert_inner() {
// arrange
let mut v = vec![1, 2, 3];
let mut stack = InlinedStack::new(&mut v);
let mut accessor = stack.accessor();
accessor.push(4);
let mut accessor_inner = accessor.accessor();
accessor_inner.push(5);
// act
accessor_inner.revert();
// assert
assert_eq!(v, vec![1, 2, 3, 4]);
}
#[test]
fn revert_outer() {
// arrange
let mut v = vec![1, 2, 3];
let mut stack = InlinedStack::new(&mut v);
let mut accessor = stack.accessor();
accessor.push(4);
let mut accessor_inner = accessor.accessor();
accessor_inner.push(5);
// act
accessor.revert();
// assert
assert_eq!(v, vec![1, 2, 3]);
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub enum ArrayCollectError {
NotEnough { expeected: usize, got: usize },
TooMuch(usize),
}
pub trait IteratorArrayCollectExt<T>: Sized {
fn collect_array<const N: usize>(self) -> Result<[T; N], ArrayCollectError>;
}
// [ ] memory leaks in case of panic
impl<T, I: Sized + Iterator<Item = T>> IteratorArrayCollectExt<T> for I {
fn collect_array<const N: usize>(mut self) -> Result<[T; N], ArrayCollectError> {
{
// Ok, so unsafe is used here, and I am really scared of it. But I couldn't find any alternative, really
// Create an array of uninitialized values.
let mut array: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
for (i, arr_i) in array.iter_mut().enumerate() {
match self.next() {
None => {
return Err(ArrayCollectError::NotEnough {
expeected: N,
got: i,
});
}
Some(next) => {
*arr_i = MaybeUninit::new(next);
}
}
}
if self.next().is_some() {
// there are even more elements to get
return Err(ArrayCollectError::TooMuch(N));
}
Ok(unsafe { std::mem::transmute_copy::<_, [T; N]>(&array) })
}
}
}
pub trait VecTryExtendExt<T, E> {
fn try_extend(&mut self, iter: impl Iterator<Item = Result<T, E>>) -> Result<(), E>;
}
impl<T, E> VecTryExtendExt<T, E> for Vec<T> {
fn try_extend(&mut self, iter: impl Iterator<Item = Result<T, E>>) -> Result<(), E> {
for el in iter {
self.push(el?);
}
Ok(())
}
}
pub trait OptionalPermutation<In, Out, E> {
fn optional_permutation(&mut self, input: In) -> IResult<In, Out, E>;
}
macro_rules! impl_optional_permutation {
{$Count:expr, $type_ident:ident: $out_ident:ident} => {
// base case
#[allow(non_snake_case)]
impl<'source, Err, $out_ident, $type_ident> OptionalPermutation<&'source str, (Option<$out_ident>,), Err> for ($type_ident,)
where
Err: ParseError<&'source str>,
$type_ident: Parser<&'source str, $out_ident, Err>,
{
fn optional_permutation(&mut self, input: &'source str) -> IResult<&'source str, (Option<$out_ident>,), Err>
{
// in a debug build, let's assert that we've passed a correct type count
debug_assert!($Count == 1, "Should parse number equal to number of arguments");
// for a single variant, just try parsing it, and propagate any sort of failure
match self.0.parse(input) {
Ok((rest, result)) => Ok((rest, (Some(result), ))),
Err(nom::Err::Failure(err)) => Err(nom::Err::Failure(err)),
Err(_) => Ok((input, (None, )))
}
}
}
};
{$Count:expr, $type_ident0:ident: $out_ident0:ident, $($type_ident:ident: $out_ident:ident), *} => {
#[allow(non_snake_case)]
impl<'source, Err, $out_ident0, $($out_ident), *, $type_ident0, $($type_ident), *> OptionalPermutation<&'source str, (Option<$out_ident0>, $(Option<$out_ident>, ) *), Err>
for ($type_ident0, $($type_ident, ) *)
where
Err: ParseError<&'source str>,
$type_ident0: Parser<&'source str, $out_ident0, Err>,
$(
$type_ident: Parser<&'source str, $out_ident, Err>
), *
{
fn optional_permutation(&mut self, mut input: &'source str) -> IResult<&'source str, (Option<$out_ident0>, $(Option<$out_ident>), *), Err> {
// This is a max number of iterations we're gonna need
const COUNT: usize = $Count;
// for multiple variants, here comes chaos:
// we can't create any sort of array or vector to hold the results -
// they have different types, that's the point!
// So we are bound to creating a BUNCH of variables
let mut $out_ident0: Option<$out_ident0> = None;
$(
let mut $out_ident: Option<$out_ident> = None;
) *
// Also, since we can't index tuples, we'll need to "deconstruct self"
// I don't feel like creating separate identifiers for that, so I guess compiler IS to complain :idk:
let ($type_ident0, $($type_ident, ) *) = self;
// Now we can use `$type_ident` as corresponding parser, and `$out_ident` as a output variable!
// Amazing! (not really; I imagine that would be a nightmare to read)
for _ in 0..COUNT {
if $out_ident0.is_none() {
match $type_ident0.parse(input) {
Ok((rest, result)) => {
input = rest;
$out_ident0 = Some(result);
continue; // continues parsing attempts from the beginning
},
Err(nom::Err::Failure(err)) => return Err(nom::Err::Failure(err)),
Err(_) => { }
}
}
$(
if $out_ident.is_none() {
match $type_ident.parse(input) {
Ok((rest, result)) => {
input = rest;
$out_ident = Some(result);
continue; // continues parsing attempts from the beginning
},
Err(nom::Err::Failure(err)) => return Err(nom::Err::Failure(err)),
Err(_) => { }
}
}
) *
// All parsing attempts had failed?
// Break out, we're done here.
break;
}
let _ = 2; // <-- his name is Joey
Ok((input, ($out_ident0, $($out_ident, ) *)))
}
}
// forward to N-1
impl_optional_permutation!{$Count - 1, $($type_ident: $out_ident), *}
};
}
impl_optional_permutation! {16, A: AOut, B: BOut, C: COut, D: DOut, E: EOut, F: FOut, G: GOut, H: HOut, I: IOut, J: JOut, K: KOut, L: LOut, M: MOut, N: NOut, O: OOut, P: POut}
pub fn optional_permutation<'parsers, 'source: 'parsers, In, Out, E: ParseError<&'source str>>(
mut parsers: impl OptionalPermutation<In, Out, E> + 'parsers,
) -> impl Parser<In, Out, E> + 'parsers {
move |input| parsers.optional_permutation(input)
}
#[derive(Debug)]
pub struct FmtToIo<W>(W, VecDeque<std::io::Error>);
impl<W> FmtToIo<W> {
pub fn new(io: W) -> Self
where
W: std::io::Write,
{
Self(io, VecDeque::new())
}
pub fn get_error(&mut self) -> Option<std::io::Error> {
self.1.pop_front()
}
pub fn get_all_errors(&mut self) -> impl Iterator<Item = std::io::Error> + '_ {
self.1.drain(..)
}
pub fn into_parts(self) -> (W, impl IntoIterator<Item = std::io::Error>) {
(self.0, self.1)
}
}
impl<W: std::io::Write> std::fmt::Write for FmtToIo<W> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
if let Err(err) = self.0.write_all(s.as_bytes()) {
self.1.push_back(err);
return Err(std::fmt::Error);
}
Ok(())
}
}
#[derive(derive_more::Deref, derive_more::DerefMut, Clone)]
pub struct StaticDebug<T, const R: u64 = 0>(pub T);
impl<T, const R: u64> std::fmt::Debug for StaticDebug<T, R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("StaticDebug").field(&R).finish()
}
}

View file

@ -1,23 +0,0 @@
This is a test of ayano-generated stuff.
This is supposed to be a generated figure:
```python, Ayano
@fig: src="circle.png", ident="circle", caption="Some caption for a line drawing. This is parsed as a *regular document text*, so you may [@eq:line] and $f(x) = A \cdot x^{1} + B \cdot x^{0}$ here, for example"
```
Here's a table with specified width:
```python, Ayano
width = 0.2
@fig: src = "graph.png", width = width
```
This is expected to be a generated table:
```python, Ayano
data = [
["x", "y"],
[1.0, 3.0],
[3.0, 4.0],
[-1.0, -5743234.0],
]
@gen_table: lambda r,c: data[r][c]; rows=4, columns=2, caption="Caption for a table 1, *because* why not", ident="table1"
```

View file

@ -1,25 +0,0 @@
---
code-section-title: Код
---
# Display blocks
This feature allows you to insert certain `Ayano` blocks to the end of the output document, to document your exact actions for displayed results.
Here's an example:
```python, Ayano *
# You should be able to see this comment in the output doc
42
```
Display blocks can have descriptions too! Here's one:
```python, Ayano * "me, fooling around"
# This code block has description
# Might as well add some python syntax to check out minted's syntax highlighting:
for i in range(100):
s = str(i)
s = s[::-1]
@dev: -2.034e4, 145.64
```

View file

@ -1,23 +0,0 @@
# Перша тема
Тут трохи тексту і пояснень про квантові мультивироджені рефурбалідзери змішаного типу. Це нудно, тож тримайте формулу:
$$
Ref(z) = \int \limits_{0}^{1} t^{z-1} \cdot \exp(-t) dt
$$
{ref = refurbalidzer_1 }
## Підпункт
Рефурбалідзери можуть виявитись дуже важливими для збереження екологічної безпеки нашої планети. До прикладу, в них є наступні переваги:
- Нормувальний множник $|\psi|^{2} = 3/4$
- Кількість хромосом - $\exp(\pi)$
- Поживна цінність - 20 дптр/см
![[refurb.jpg]]
{ref = refurb, caption = "Принципове влаштування рефурбалідзера", width = 0.4}
На світлині [@ fig:refurb] можна бачити принципову схему влаштування рефурбалідзера. Неважно помітити великі очі[^1], і два термоядерних реактори, що оточують їх. Точні реакції всередині невідомі, але цілком можливо що там женуть $du + t -> {}^{5}_{6}C$...
[^1]: Для поливу.

View file

@ -1,204 +0,0 @@
This test is dedicated to attempts of parser deception, i.e. usage of special symbols in wrong places to see if parser recognizes them as control symbols. There we go:
# Page dividers
Page dividers are represented by ------
If the line above has dashes ^^^^^^ here, it probably works alright.
Here's a genuine divider
------
Fun fact: there can be a paragraph right after it!
------ Some text, cause why not
But this **DOES** look cursed, don't do it.
# Headers
Headers are most recognizable by '#'-symbol. For it to actually represent a header, it needs to at the beginning of the line and there should be no more than 6 of them. **ANYTHING** up to the end of line will be included into a header, then.
Example:
###### This is a long, yet formally correct header. You can add $equations$ and *formatting* here, but I provide no guarantees as of the way these would display in final document.
Here's an example of incorrect header:
###### This header is considered incorrect for this application, and will be parsed as regular paragraph instead
Another example of incorrect header, featuring 7 hash-symbols:
####### This will be parsed as a regular paragraph
# Equations
There are two types of equations in markdown/latex: inline mathmode and display mathmode
Both of them are defined by $-symbols. However, they must be paired to actually represent these. Here are examples:
Inline: $y = x ^ 2$
Display:
$$
y = x ^ 2
$$
Note that display mathmode should ALWAYS start from a new line for this application.
Basically, there's not much to deceive for: you can do that:
$
$
(the above should not be mathmode), and that's it, basically :sad:
# Table
Tables are defined by |-symbol. But the actual table will happen only if they are the first thing on the line.
Due to current implementation, there's no cure apart from display mathmode and code:
(this should be display mathmode):
$$
|x|
$$
(this should be a code block):
```bash
head /dev/random
| cat
```
# Figure
Figure is defined by a ![[ sequence at a start of the line. Same points should be uphold here:
$$
![[
$$
```
![[
```
# References
These start with []
## Hyperrefs
These are followed by parentheses (). Nothing is allowed between them, thus
Here's a correct href: [my gh page](https://youtu.be/rickroll_link_lol)
Here are incorrect refs:
[my gh page] (https://youtu.be/rickroll_link_lol)
[my gh page]
(https://youtu.be/rickroll_link_lol)
## Footnote refs
These are started by [^.
Here are correct footnote refs: [^1], [^source]
Here are incorrect ones: [^1, ^1],
[^
1
]
So on
## Object refs
These refer to objects like figures, tables and equations.
Started by [@.
Here's a correct ref: [@ fig:experiment_schema]
Here's are bad ones: [@ fig:1, @ source3],
[@
eq2
]
**NOTE**: despite object types being prepended with 'eq', 'fig' or 'tab' in refs, you *can* use regular latex to define your custom refs. In fact, LaTeX doesn't care about all of these at all; I'm doing these transformations just for my own comfort, as I've been doing this manually.
# Formatting
This object's type is deprecated and will be removed soon.
It starts and ends with
- * or _
- ** or __
- ~~
**WARN**: As of now, these *might* cause cause massive problems if encountered at the start of the line.
Basically, parsers for these tokens attempt parse everything up until they find a closing token. But if they are inserted in a middle of the text, then it's ok - they will be limited by current paragraph, and (most likely) will find no match inside it, if their use was unintended.
# Footnote content
These start with '[^ID]:' and proceed to the end of line.
Example of valid content:
[^source2]: Explanation of source2.
Example of invalid content:
[^source2: This is invalid content
[^source2] : This is invalid content
[^source2]: This is invalid content
They also must start at the beginning of the line. If fact, this: [^source2]: Some content
Is expected to be a footnote ref with a colon.
# Code blocks
There are two fundamental types of code blocks: normal code block and **Ayano** code block.
## Normal code block
These start and end with '```' on a newline. There can be **practically anything** inside. Example:
```java
int x = 2;
int y = 4;
int[] arr = new int[2];
arr[0] = x;
arr[1] = 4;
```
There can be anything after code block had closed:
```lua(Idk why vscode detects this block closing as a valid lua syntax)
local x = 'matt'
local y = 'y'
local z = x .. y -- matty!!
``` Here's some text that's ok to be here
There can't be any spaces before delimiting ```s:
```python
# This here is not really a python block
x = 2
print(x)
```
## Ayano code block
*because Ayano loves Kyoko*
This object looks and behaves just like normal code block, but is only limited to *python* language, can have spacial arguments in a language line, may leave something behind in the document. For example:
This Ayano block will leave plain "2" behind it:
```python, Ayano
2
```
Akin to normal blocks, it's ok to leave something trailing:
```ayano
2
``` This text is ok to be here
And these are invalid Ayano blocks:
```ayano
2
```
```ayano
2
```

View file

@ -1,12 +0,0 @@
# List-after-para
Some para
- List item 1
- List item 2
Some para
- List item 1
- List item 2
*This bug resulted in lists to be parsed as series of paragraphs.*

View file

@ -1,36 +0,0 @@
Наступний тест. Тут протестуємо таблиці і блоки коду.
## Таблиці
Звичайні таблиці; Можуть всередині мати **формули** чи *посилання* на інші штуки
| Storage | Space | Number |
| :---------: | :-----------: | :------------------------: |
| 1 | 2 | $3 + e ^{\pi}$ |
| $e$[^euler] | $\cdot \pm 1$ | І тут ще *трохи* тексту \| |
{ref = example_1, caption = "Оце приклад таблиці. Як казав мій вчитель: **ВИКОРИСТОВУЙ БІЛЬШЕ ВИБУХІВКИ** (і \"лапок\")"}
Таблиця цілком собі може не мати ідентифікатора або опису:
| Кількість | Роль |
| :--------: | :-------: |
| 3 діоптрії | 7 галон |
| rust | #PANIC!!! |
{ref = table_2}
[^euler]: Це число Ейлера. Воно дуже корисне, ~~але не може бути записане~~ у вигляді *десяткового* дробу.
## Блоки коду
Наразі мають досить обмежену підтримку, адже у $\LaTeX$ немає нативного поняття блоків коду.
На щастя, у **markdown** таке поняття є[^code]:
```rust
let x = 5;
let y = x.powi(2);
let z = (y as f64).sqrt();
println!("x = {x}, y = {y}, z = {x}");
```
[^code]: Принаймні, *Obsidian* таке робить

View file

@ -1,23 +0,0 @@
---
author-line2: дяч дзучунович
header-line1: Навч. заклад
title-line2: з історії стародавнього Єгипту
header-line2: Факультет
author-line1: виконувало:
author-line3: (perfectly still)
document-type: ПРОТОКОЛ
date: вербень 2077
title-line4: НАЗВА РОБОТИ
title-line1: Виконання марної роботи
prof: me :idk:
title-line3: (пинальна частина)
---
# First para
This text is *just* a dummy, so that document won't feel ~~too~~ **empty**
Have a some oily maccaroni:
$$
\gamma = \lim_{n \to \infty} \sum\limits_{i=1}^{n} \dfrac{1}{n} - \ln(n)
$$

View file

@ -1,145 +0,0 @@
use std::{ffi::OsString, path::PathBuf, str::FromStr};
const CLEAR: &str = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" ;
const INPUT_DIR: &str = "./tests/input_files";
const LEX_DIR: &str = "./tests/lex_files";
fn input_files() -> impl Iterator<Item = (OsString, String)> {
std::fs::read_dir(INPUT_DIR)
.expect("Should be able to access input directory")
.map(|file| {
let file_path = file
.expect("Should be able to access files in input directory")
.path();
let input =
std::fs::read_to_string(&file_path).expect("Should be able to read input files");
(OsString::from(file_path.file_name().unwrap()), input)
})
}
/// Prints input file's lex into the stdout, for inspection
#[ignore = "manual"]
#[test]
fn show_lex() {
for (_, input) in input_files() {
let parsed = match kyomato_lib::lex(input.as_str()) {
Ok(ok) => ok,
Err(err) => {
panic!("Should successfully parse the input: {err:#?}");
}
};
println!("{parsed:#?}");
std::io::stdin()
.read_line(&mut String::new())
.expect("Should be able to read a line");
println!("{}", CLEAR);
}
}
// TODO rewrite to use serde-json after token rework
/// Generates representation of a parsed tokens, for automated testing to compare with
#[cfg(feature = "serde")]
#[ignore = "manual"]
#[test]
fn gen_lex_output() {
use std::{fs::File, io::Write, os::unix::fs::FileExt, path::PathBuf, str::FromStr};
{
use std::io::Read;
print!("Are you sure? This will rewrite previous test outputs: [y/N]: ");
std::io::stdout()
.flush()
.expect("Should be able to flush the stdout");
let mut buf = [0u8; 1];
std::io::stdin()
.read_exact(&mut buf)
.expect("Should be able to read stdin");
if buf[0] != b'y' {
println!("\nAborting...");
return;
}
}
let mut output_path = PathBuf::from_str(LEX_DIR).unwrap();
if !output_path.is_dir() {
std::fs::create_dir(&output_path).expect("Failed to create directory");
}
for (filename, input) in input_files() {
let (title_info, tokens) = match kyomato_lib::lex(input.as_str()) {
Ok(ok) => ok,
Err(err) => {
panic!("Input files should be successfully parsed: {err:#?}");
}
};
// create representation of the parsed tokens
// TODO this part should be replaced, once serde is supported
let repr = format!("{:#?}\n{:#?}", title_info, tokens);
// write the result to output directory
let output_file = {
output_path.push(filename);
output_path.set_extension("txt");
let output_file = if output_path.is_file() {
File::options()
.write(true)
.truncate(true)
.open(&output_path)
} else {
File::create(&output_path)
}
.expect("Failed to create output file");
output_path.pop();
output_file
};
output_file
.write_at(repr.as_bytes(), 0)
.expect("Failed to write into output file");
}
}
#[test]
fn test_lex() {
let mut output_results = Vec::new();
let mut output_path = PathBuf::from_str(LEX_DIR).unwrap();
if !output_path.is_dir() {
std::fs::create_dir(&output_path).expect("Failed to create directory");
}
for (filename, input) in input_files() {
let (title_info, tokens) = match kyomato_lib::lex(input.as_str()) {
Ok(ok) => ok,
Err(err) => {
panic!("Should successfully parse the input: {err:#?}");
}
};
// create representation of the parsed tokens
// TODO this part should be replaced, once serde is supported
let repr = format!("{:#?}\n{:#?}", title_info, tokens);
// write the result to output directory
let expected_output = {
output_path.push(&filename);
output_path.set_extension("txt");
let output =
std::fs::read_to_string(&output_path).expect("Failed to read expected output");
output_path.pop();
output
};
output_results.push((filename, repr, expected_output));
}
let total = output_results.len();
let success = output_results
.iter()
.filter(|(_, real, expected)| real == expected)
.count();
let failed = total - success;
if failed > 0 {
let mut output_msg = String::new();
output_msg.push_str(&format!("passed: ({success}/{total})\nFailures:\n"));
for (filename, real, expected) in output_results {
if real != expected {
output_msg.push_str(&format!(
"\tFilename: {}\n\tActual: {real}\n\tExpected: {expected}\n\n",
filename.to_string_lossy(),
));
}
}
panic!("{output_msg}");
}
}

View file

@ -1,81 +0,0 @@
TitleInfo {
header_line1: None,
header_line2: None,
document_type: None,
title_line1: None,
title_line2: None,
title_line3: None,
title_line4: None,
author_line1: None,
author_line2: None,
author_line3: None,
date: None,
prof: None,
code_section_title: None,
}
Multiple {
tokens: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "This is a test of ayano-generated stuff.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "This is supposed to be a generated figure:",
},
Ayano {
data: AyanoBlock {
ident: StaticDebug(
0,
),
display_state: NotDisplayed,
spaces_before: 0,
is_static: false,
code: "@fig: src=\"circle.png\", ident=\"circle\", caption=\"Some caption for a line drawing. This is parsed as a *regular document text*, so you may [@eq:line] and $f(x) = A \\cdot x^{1} + B \\cdot x^{0}$ here, for example\"",
insert_path: None,
},
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Here's a table with specified width:",
},
Ayano {
data: AyanoBlock {
ident: StaticDebug(
0,
),
display_state: NotDisplayed,
spaces_before: 0,
is_static: false,
code: "width = 0.2\n@fig: src = \"graph.png\", width = width",
insert_path: None,
},
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "This is expected to be a generated table:",
},
Ayano {
data: AyanoBlock {
ident: StaticDebug(
0,
),
display_state: NotDisplayed,
spaces_before: 0,
is_static: false,
code: "data = [\n\t\t[\"x\", \"y\"],\n\t\t[1.0, 3.0],\n\t\t[3.0, 4.0],\n\t\t[-1.0, -5743234.0],\n]\n@gen_table: lambda r,c: data[r][c]; rows=4, columns=2, caption=\"Caption for a table 1, *because* why not\", ident=\"table1\"",
insert_path: None,
},
},
],
),
}

View file

@ -1,90 +0,0 @@
TitleInfo {
header_line1: None,
header_line2: None,
document_type: None,
title_line1: None,
title_line2: None,
title_line3: None,
title_line4: None,
author_line1: None,
author_line2: None,
author_line3: None,
date: None,
prof: None,
code_section_title: Some(
"Код",
),
}
Multiple {
tokens: Tokens(
[
Header {
order: 0,
content: "Display blocks",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "This feature allows you to insert certain",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
MonoSpace,
),
content: "Ayano",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "blocks to the end of the output document, to document your exact actions for displayed results.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Here's an example:",
},
Ayano {
data: AyanoBlock {
ident: StaticDebug(
0,
),
display_state: DisplayedNoCaption,
spaces_before: 1,
is_static: false,
code: "# You should be able to see this comment in the output doc\n42",
insert_path: None,
},
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Display blocks can have descriptions too! Here's one:",
},
Ayano {
data: AyanoBlock {
ident: StaticDebug(
0,
),
display_state: Caption(
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "me, fooling around",
},
),
spaces_before: 1,
is_static: false,
code: "# This code block has description\n# Might as well add some python syntax to check out minted's syntax highlighting:\nfor i in range(100):\n s = str(i)\n s = s[::-1]\n@dev: -2.034e4, 145.64",
insert_path: None,
},
},
],
),
}

View file

@ -1,154 +0,0 @@
TitleInfo {
header_line1: None,
header_line2: None,
document_type: None,
title_line1: None,
title_line2: None,
title_line3: None,
title_line4: None,
author_line1: None,
author_line2: None,
author_line3: None,
date: None,
prof: None,
code_section_title: None,
}
Multiple {
tokens: Tokens(
[
Header {
order: 0,
content: "Перша тема",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Тут трохи тексту і пояснень про квантові мультивироджені рефурбалідзери змішаного типу. Це нудно, тож тримайте формулу:",
},
DisplayMath {
content: "Ref(z) = \\int \\limits_{0}^{1} t^{z-1} \\cdot \\exp(-t) dt",
ident: Some(
"refurbalidzer_1",
),
},
Header {
order: 1,
content: "Підпункт",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Рефурбалідзери можуть виявитись дуже важливими для збереження екологічної безпеки нашої планети. До прикладу, в них є наступні переваги:",
},
List {
list_type: Bullet,
content: Tokens(
[
Multiple {
tokens: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "Нормувальний множник",
},
InlineMath {
space_before: true,
content: "|\\psi|^{2} = 3/4",
},
],
),
},
Multiple {
tokens: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "Кількість хромосом -",
},
InlineMath {
space_before: true,
content: "\\exp(\\pi)",
},
],
),
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "Поживна цінність - 20 дптр/см",
},
],
),
},
Figure {
src_name: "refurb.jpg",
caption: Some(
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "Принципове влаштування рефурбалідзера",
},
),
ident: Some(
"refurb",
),
width: Some(
0.4,
),
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "На світлині",
},
Reference {
space_before: true,
ident: "fig:refurb",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "можна бачити принципову схему влаштування рефурбалідзера. Неважно помітити великі очі",
},
FootnoteReference {
space_before: false,
ident: "1",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: ", і два термоядерних реактори, що оточують їх. Точні реакції всередині невідомі, але цілком можливо що там женуть",
},
InlineMath {
space_before: true,
content: "du + t -> {}^{5}_{6}C",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "...",
},
FootnoteContent {
content: Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "Для поливу.",
},
ident: "1",
},
],
),
}

View file

@ -1,868 +0,0 @@
TitleInfo {
header_line1: None,
header_line2: None,
document_type: None,
title_line1: None,
title_line2: None,
title_line3: None,
title_line4: None,
author_line1: None,
author_line2: None,
author_line3: None,
date: None,
prof: None,
code_section_title: None,
}
Multiple {
tokens: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "This test is dedicated to attempts of parser deception, i.e. usage of special symbols in wrong places to see if parser recognizes them as control symbols. There we go:",
},
Header {
order: 0,
content: "Page dividers",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Page dividers are represented by ------",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "If the line above has dashes ^^^^^^ here, it probably works alright.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Here's a genuine divider",
},
PageDiv,
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Fun fact: there can be a paragraph right after it!",
},
PageDiv,
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "Some text, cause why not",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "But this",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Bold,
),
content: "DOES",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "look cursed, don't do it.",
},
Header {
order: 0,
content: "Headers",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Headers are most recognizable by '#'-symbol. For it to actually represent a header, it needs to at the beginning of the line and there should be no more than 6 of them.",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Bold,
),
content: "ANYTHING",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "up to the end of line will be included into a header, then.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Example:",
},
Header {
order: 5,
content: "This is a long, yet formally correct header. You can add $equations$ and *formatting* here, but I provide no guarantees as of the way these would display in final document.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Here's an example of incorrect header:",
},
Paragraph {
is_newline: true,
space_before: true,
formatting: None,
content: "###### This header is considered incorrect for this application, and will be parsed as regular paragraph instead",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Another example of incorrect header, featuring 7 hash-symbols:",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "####### This will be parsed as a regular paragraph",
},
Header {
order: 0,
content: "Equations",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "There are two types of equations in markdown/latex: inline mathmode and display mathmode",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Both of them are defined by $-symbols. However, they must be paired to actually represent these. Here are examples:",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Inline:",
},
InlineMath {
space_before: true,
content: "y = x ^ 2",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Display:",
},
DisplayMath {
content: "y = x ^ 2",
ident: None,
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Note that display mathmode should ALWAYS start from a new line for this application.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Basically, there's not much to deceive for: you can do that:",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "$",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "$",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "(the above should not be mathmode), and that's it, basically :sad:",
},
Header {
order: 0,
content: "Table",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Tables are defined by |-symbol. But the actual table will happen only if they are the first thing on the line.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Due to current implementation, there's no cure apart from display mathmode and code:",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "(this should be display mathmode):",
},
DisplayMath {
content: "|x|",
ident: None,
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "(this should be a code block):",
},
CodeBlock {
code: "head /dev/random\n| cat",
language: Some(
"bash",
),
},
Header {
order: 0,
content: "Figure",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Figure is defined by a ![[ sequence at a start of the line. Same points should be uphold here:",
},
DisplayMath {
content: "![[",
ident: None,
},
CodeBlock {
code: "![[",
language: None,
},
Header {
order: 0,
content: "References",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "These start with []",
},
Header {
order: 1,
content: "Hyperrefs",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "These are followed by parentheses (). Nothing is allowed between them, thus",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Here's a correct href:",
},
Href {
space_before: true,
url: Url {
scheme: "https",
cannot_be_a_base: false,
username: "",
password: None,
host: Some(
Domain(
"youtu.be",
),
),
port: None,
path: "/rickroll_link_lol",
query: None,
fragment: None,
},
display: "my gh page",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Here are incorrect refs:",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "[my gh page] (https://youtu.be/rickroll",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: Some(
Italic,
),
content: "link",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "lol)",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "[my gh page]",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "(https://youtu.be/rickroll",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: Some(
Italic,
),
content: "link",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "lol)",
},
Header {
order: 1,
content: "Footnote refs",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "These are started by [^.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Here are correct footnote refs:",
},
FootnoteReference {
space_before: true,
ident: "1",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: ",",
},
FootnoteReference {
space_before: true,
ident: "source",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Here are incorrect ones: [^1, ^1],",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "[^",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "1",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "]",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "So on",
},
Header {
order: 1,
content: "Object refs",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "These refer to objects like figures, tables and equations.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Started by [@.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Here's a correct ref:",
},
Reference {
space_before: true,
ident: "fig:experiment_schema",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Here's are bad ones: [@ fig:1, @ source3],",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "[@",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "eq2",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "]",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: Some(
Bold,
),
content: "NOTE",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: ": despite object types being prepended with 'eq', 'fig' or 'tab' in refs, you",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Italic,
),
content: "can",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "use regular latex to define your custom refs. In fact, LaTeX doesn't care about all of these at all; I'm doing these transformations just for my own comfort, as I've been doing this manually.",
},
Header {
order: 0,
content: "Formatting",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "This object's type is deprecated and will be removed soon.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "It starts and ends with",
},
List {
list_type: Bullet,
content: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "* or _",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "** or __",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "~~",
},
],
),
},
Paragraph {
is_newline: true,
space_before: false,
formatting: Some(
Bold,
),
content: "WARN",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: ": As of now, these",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Italic,
),
content: "might",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "cause cause massive problems if encountered at the start of the line.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Basically, parsers for these tokens attempt parse everything up until they find a closing token. But if they are inserted in a middle of the text, then it's ok - they will be limited by current paragraph, and (most likely) will find no match inside it, if their use was unintended.",
},
Header {
order: 0,
content: "Footnote content",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "These start with '",
},
FootnoteReference {
space_before: false,
ident: "ID",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: ":' and proceed to the end of line.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Example of valid content:",
},
FootnoteContent {
content: Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "Explanation of source2.",
},
ident: "source2",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Example of invalid content:",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "[^source2: This is invalid content",
},
FootnoteReference {
space_before: false,
ident: "source2",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: ": This is invalid content",
},
FootnoteReference {
space_before: true,
ident: "source2",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: ": This is invalid content",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "They also must start at the beginning of the line. If fact, this:",
},
FootnoteReference {
space_before: true,
ident: "source2",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: ": Some content",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Is expected to be a footnote ref with a colon.",
},
Header {
order: 0,
content: "Code blocks",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "There are two fundamental types of code blocks: normal code block and",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Bold,
),
content: "Ayano",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "code block.",
},
Header {
order: 1,
content: "Normal code block",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "These start and end with '```' on a newline. There can be",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Bold,
),
content: "practically anything",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "inside. Example:",
},
CodeBlock {
code: "int x = 2;\nint y = 4;\nint[] arr = new int[2];\narr[0] = x;\narr[1] = 4;",
language: Some(
"java",
),
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "There can be anything after code block had closed:",
},
CodeBlock {
code: "local x = 'matt'\nlocal y = 'y'\nlocal z = x .. y -- matty!!",
language: Some(
"lua(Idk why vscode detects this block closing as a valid lua syntax)",
),
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "Here's some text that's ok to be here",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "There can't be any spaces before delimiting ```s:",
},
Paragraph {
is_newline: true,
space_before: true,
formatting: None,
content: "```python",
},
Paragraph {
is_newline: true,
space_before: true,
formatting: None,
content: "# This here is not really a python block",
},
Paragraph {
is_newline: true,
space_before: true,
formatting: None,
content: "x = 2",
},
Paragraph {
is_newline: true,
space_before: true,
formatting: None,
content: "print(x)",
},
Paragraph {
is_newline: true,
space_before: true,
formatting: None,
content: "```",
},
Header {
order: 1,
content: "Ayano code block",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: Some(
Italic,
),
content: "because Ayano loves Kyoko",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "This object looks and behaves just like normal code block, but is only limited to",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Italic,
),
content: "python",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "language, can have spacial arguments in a language line, may leave something behind in the document. For example:",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "This Ayano block will leave plain \"2\" behind it:",
},
Ayano {
data: AyanoBlock {
ident: StaticDebug(
0,
),
display_state: NotDisplayed,
spaces_before: 0,
is_static: false,
code: "2",
insert_path: None,
},
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Akin to normal blocks, it's ok to leave something trailing:",
},
CodeBlock {
code: "2",
language: Some(
"ayano",
),
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "This text is ok to be here",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "And these are invalid Ayano blocks:",
},
CodeBlock {
code: "2",
language: Some(
"ayano",
),
},
Paragraph {
is_newline: true,
space_before: true,
formatting: None,
content: "```ayano",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "2",
},
Paragraph {
is_newline: true,
space_before: true,
formatting: None,
content: "```",
},
],
),
}

View file

@ -1,85 +0,0 @@
TitleInfo {
header_line1: None,
header_line2: None,
document_type: None,
title_line1: None,
title_line2: None,
title_line3: None,
title_line4: None,
author_line1: None,
author_line2: None,
author_line3: None,
date: None,
prof: None,
code_section_title: None,
}
Multiple {
tokens: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "# List-after-para",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Some para",
},
List {
list_type: Bullet,
content: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "List item 1",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "List item 2",
},
],
),
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Some para",
},
List {
list_type: Bullet,
content: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "List item 1",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "List item 2",
},
],
),
},
Paragraph {
is_newline: true,
space_before: false,
formatting: Some(
Italic,
),
content: "This bug resulted in lists to be parsed as series of paragraphs.",
},
],
),
}

View file

@ -1,368 +0,0 @@
TitleInfo {
header_line1: None,
header_line2: None,
document_type: None,
title_line1: None,
title_line2: None,
title_line3: None,
title_line4: None,
author_line1: None,
author_line2: None,
author_line3: None,
date: None,
prof: None,
code_section_title: None,
}
Multiple {
tokens: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "Наступний тест. Тут протестуємо таблиці і блоки коду.",
},
Header {
order: 1,
content: "Таблиці",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Звичайні таблиці; Можуть всередині мати",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Bold,
),
content: "формули",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "чи",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Italic,
),
content: "посилання",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "на інші штуки",
},
Table {
header: Tokens(
[
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "Storage",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "Space",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "Number",
},
],
),
cells: Tokens(
[
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "1",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "2",
},
InlineMath {
space_before: true,
content: "3 + e ^{\\pi}",
},
Multiple {
tokens: Tokens(
[
InlineMath {
space_before: true,
content: "e",
},
FootnoteReference {
space_before: false,
ident: "euler",
},
],
),
},
InlineMath {
space_before: true,
content: "\\cdot \\pm 1",
},
Multiple {
tokens: Tokens(
[
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "І тут ще",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Italic,
),
content: "трохи",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "тексту \\|",
},
],
),
},
],
),
caption: Some(
Multiple {
tokens: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "Оце приклад таблиці. Як казав мій вчитель:",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Bold,
),
content: "ВИКОРИСТОВУЙ БІЛЬШЕ ВИБУХІВКИ",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "(і \\\"лапок\\\")",
},
],
),
},
),
ident: Some(
"example_1",
),
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Таблиця цілком собі може не мати ідентифікатора або опису:",
},
Table {
header: Tokens(
[
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "Кількість",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "Роль",
},
],
),
cells: Tokens(
[
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "3 діоптрії",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "7 галон",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "rust",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "#PANIC!!!",
},
],
),
caption: None,
ident: Some(
"table_2",
),
},
FootnoteContent {
content: Multiple {
tokens: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "Це число Ейлера. Воно дуже корисне,",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
StrikeThrough,
),
content: "але не може бути записане",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "у вигляді",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Italic,
),
content: "десяткового",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "дробу.",
},
],
),
},
ident: "euler",
},
Header {
order: 1,
content: "Блоки коду",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Наразі мають досить обмежену підтримку, адже у",
},
InlineMath {
space_before: true,
content: "\\LaTeX",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "немає нативного поняття блоків коду.",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "На щастя, у",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Bold,
),
content: "markdown",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "таке поняття є",
},
FootnoteReference {
space_before: false,
ident: "code",
},
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: ":",
},
CodeBlock {
code: "let x = 5;\nlet y = x.powi(2);\nlet z = (y as f64).sqrt();\nprintln!(\"x = {x}, y = {y}, z = {x}\");",
language: Some(
"rust",
),
},
FootnoteContent {
content: Multiple {
tokens: Tokens(
[
Paragraph {
is_newline: false,
space_before: false,
formatting: None,
content: "Принаймні,",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Italic,
),
content: "Obsidian",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "таке робить",
},
],
),
},
ident: "code",
},
],
),
}

View file

@ -1,95 +0,0 @@
TitleInfo {
header_line1: Some(
"Навч. заклад",
),
header_line2: Some(
"Факультет",
),
document_type: Some(
"ПРОТОКОЛ",
),
title_line1: Some(
"Виконання марної роботи",
),
title_line2: Some(
"з історії стародавнього Єгипту",
),
title_line3: Some(
"(пинальна частина)",
),
title_line4: Some(
"НАЗВА РОБОТИ",
),
author_line1: Some(
"виконувало:",
),
author_line2: Some(
"дяч дзучунович",
),
author_line3: Some(
"(perfectly still)",
),
date: Some(
"вербень 2077",
),
prof: Some(
"me :idk:",
),
code_section_title: None,
}
Multiple {
tokens: Tokens(
[
Header {
order: 0,
content: "First para",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "This text is",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Italic,
),
content: "just",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: None,
content: "a dummy, so that document won't feel",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
StrikeThrough,
),
content: "too",
},
Paragraph {
is_newline: false,
space_before: true,
formatting: Some(
Bold,
),
content: "empty",
},
Paragraph {
is_newline: true,
space_before: false,
formatting: None,
content: "Have a some oily maccaroni:",
},
DisplayMath {
content: "\\gamma = \\lim_{n \\to \\infty} \\sum\\limits_{i=1}^{n} \\dfrac{1}{n} - \\ln(n)",
ident: None,
},
],
),
}

View file

@ -1,153 +0,0 @@
use std::{ffi::OsString, path::PathBuf, str::FromStr};
const CLEAR: &str = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" ;
const INPUT_DIR: &str = "./tests/input_files";
const TRANSFORM_DIR: &str = "./tests/transformed_files";
fn input_files() -> impl Iterator<Item = (OsString, String)> {
std::fs::read_dir(INPUT_DIR)
.expect("Should be able to access input directory")
.map(|file| {
let file_path = file
.expect("Should be able to access files in input directory")
.path();
let input =
std::fs::read_to_string(&file_path).expect("Should be able to read input files");
(OsString::from(file_path.file_name().unwrap()), input)
})
}
/// Prints input file's lex into the stdout, for inspection
#[ignore = "manual"]
#[test]
fn show_transform() {
for (_, input) in input_files() {
let (title_info, tokens) = match kyomato_lib::lex(input.as_str()) {
Ok(ok) => ok,
Err(err) => {
panic!("Should successfully parse the input: {err:#?}");
}
};
let repr = kyomato_lib::gen_to_string(
&tokens,
title_info,
kyomato_lib::LabaLatex::new(kyomato_lib::path_engine::primitive()),
)
.expect("Should be able to transform tokens");
println!("{repr}");
std::io::stdin()
.read_line(&mut String::new())
.expect("Should be able to read a line");
println!("{}", CLEAR);
}
}
#[ignore = "manual"]
#[test]
fn gen_transform_output() {
use std::{fs::File, io::Write, path::PathBuf, str::FromStr};
{
use std::io::Read;
print!("Are you sure? This will rewrite previous test outputs: [y/N]: ");
std::io::stdout()
.flush()
.expect("Should be able to flush the stdout");
let mut buf = [0u8; 1];
std::io::stdin()
.read_exact(&mut buf)
.expect("Should be able to read stdin");
if buf[0] != b'y' {
println!("\nAborting...");
return;
}
}
let mut output_path = PathBuf::from_str(TRANSFORM_DIR).unwrap();
if !output_path.is_dir() {
std::fs::create_dir(&output_path).expect("Failed to create directory");
}
for (filename, input) in input_files() {
let (title_info, tokens) = match kyomato_lib::lex(input.as_str()) {
Ok(ok) => ok,
Err(err) => {
panic!("Input files should be successfully parsed: {err:#?}");
}
};
// open output file
let mut output_file = {
output_path.push(filename);
output_path.set_extension("txt");
let output_file = if output_path.is_file() {
File::options()
.write(true)
.truncate(true)
.open(&output_path)
} else {
File::create(&output_path)
}
.expect("Failed to create output file");
output_path.pop();
output_file
};
// write the result to output file
kyomato_lib::gen(
&tokens,
title_info,
kyomato_lib::LabaLatex::new(kyomato_lib::path_engine::primitive()),
&mut output_file,
)
.expect("Failed to generate output");
}
}
#[test]
fn test_transform() {
let mut output_results = Vec::new();
let mut output_path = PathBuf::from_str(TRANSFORM_DIR).unwrap();
if !output_path.is_dir() {
std::fs::create_dir(&output_path).expect("Failed to create directory");
}
for (filename, input) in input_files() {
let (title_info, tokens) = match kyomato_lib::lex(input.as_str()) {
Ok(ok) => ok,
Err(err) => {
panic!("Should successfully parse the input: {err:#?}");
}
};
let repr = kyomato_lib::gen_to_string(
&tokens,
title_info,
kyomato_lib::LabaLatex::new(kyomato_lib::path_engine::primitive()),
)
.expect("Failed to generate output");
// write the result to output directory
let expected_output = {
output_path.push(&filename);
output_path.set_extension("txt");
let output =
std::fs::read_to_string(&output_path).expect("Failed to read expected output");
output_path.pop();
output
};
output_results.push((filename, repr, expected_output));
}
let total = output_results.len();
let success = output_results
.iter()
.filter(|(_, real, expected)| real == expected)
.count();
let failed = total - success;
if failed > 0 {
let mut output_msg = String::new();
output_msg.push_str(&format!("passed: ({success}/{total})\nFailures:\n"));
for (filename, real, expected) in output_results {
if real != expected {
output_msg.push_str(&format!(
"\tFilename: {}\n\tActual: {real}\n\tExpected: {expected}\n\n",
filename.to_string_lossy(),
));
}
}
panic!("{output_msg}");
}
}

View file

@ -1,172 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
% bra and ket from QM
\usepackage{braket}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE
\LARGE
\vspace{100pt}
\LARGE
\Large
\Large
\Large
\huge
\vspace{50pt}
\large
\large
\large
\large
\vspace*{\fill}
\hspace{\fill}\large \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt
This is a test of ayano-generated stuff.
\tbln This is supposed to be a generated figure:
\begin{figure}[h!]
\centering
\includegraphics[width=0.9\textwidth]{circle.png}
\caption{\capfnt Some caption for a line drawing. This is parsed as a \textit{regular document text}, so you may \ref{eq:line} and $f(x) = A \cdot x^{1} + B \cdot x^{0}$ here, for example}
\label{fig:circle}
\end{figure}
\tb Here's a table with specified width:
\begin{figure}[h!]
\centering
\includegraphics[width=0.2\textwidth]{graph.png}
\end{figure}
\tb This is expected to be a generated table:
\begin{table}[h!]
\fnt
\begin{center}
\hskip-1.5cm\begin{tabular}{|c|c|}\hline
x & y\\ \hline
\hline
1.0 & 3.0\\ \hline
3.0 & 4.0\\ \hline
-1.0 & -5743234.0\\ \hline
\end{tabular}
\stepcounter{tabnum}
\caption{\capfnt Caption for a table 1, \textit{because} why not}
\label{tab:table1}
\end{center}
\end{table}
\end{document}

View file

@ -1,169 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
% bra and ket from QM
\usepackage{braket}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE
\LARGE
\vspace{100pt}
\LARGE
\Large
\Large
\Large
\huge
\vspace{50pt}
\large
\large
\large
\large
\vspace*{\fill}
\hspace{\fill}\large \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt
\section{Display blocks}
\tb This feature allows you to insert certain \texttt{Ayano} blocks to the end of the output document, to document your exact actions for displayed results.
\tbln Here's an example: 42
\tbln Display blocks can have descriptions too! Here's one: -20340$\pm$150
\clearpage
\subsection{Код}
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
# You should be able to see this comment in the output doc
42
\end{minted}
\tb ayano-unnamed \\
\begin{minted}[linenos, mathescape, autogobble, breaklines]{python}
# This code block has description
# Might as well add some python syntax to check out minted's syntax highlighting:
for i in range(100):
s = str(i)
s = s[::-1]
@dev: -2.034e4, 145.64
\end{minted}
me, fooling around \\
\end{document}

View file

@ -1,165 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
% bra and ket from QM
\usepackage{braket}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE
\LARGE
\vspace{100pt}
\LARGE
\Large
\Large
\Large
\huge
\vspace{50pt}
\large
\large
\large
\large
\vspace*{\fill}
\hspace{\fill}\large \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt
\section{Перша тема}
\tb Тут трохи тексту і пояснень про квантові мультивироджені рефурбалідзери змішаного типу. Це нудно, тож тримайте формулу:
\begin{equation}
Ref(z) = \int \limits_{0}^{1} t^{z-1} \cdot \exp(-t) dt
\label{eq:refurbalidzer_1}
\end{equation}
\subsection{Підпункт}
\tb Рефурбалідзери можуть виявитись дуже важливими для збереження екологічної безпеки нашої планети. До прикладу, в них є наступні переваги:
\begin{itemize}
\item Нормувальний множник $|\psi|^{2} = 3/4$
\item Кількість хромосом - $\exp(\pi)$
\item Поживна цінність - 20 дптр/см
\end{itemize}
\begin{figure}[h!]
\centering
\includegraphics[width=0.4\textwidth]{refurb.jpg}
\caption{\capfnt Принципове влаштування рефурбалідзера}
\label{fig:refurb}
\end{figure}
\tb На світлині \ref{fig:refurb} можна бачити принципову схему влаштування рефурбалідзера. Неважно помітити великі очі\footnotemark[1]\footnotetext[1]{Для поливу.}, і два термоядерних реактори, що оточують їх. Точні реакції всередині невідомі, але цілком можливо що там женуть $du + t -> {}^{5}_{6}C$...
\end{document}

View file

@ -1,303 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
% bra and ket from QM
\usepackage{braket}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE
\LARGE
\vspace{100pt}
\LARGE
\Large
\Large
\Large
\huge
\vspace{50pt}
\large
\large
\large
\large
\vspace*{\fill}
\hspace{\fill}\large \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt
This test is dedicated to attempts of parser deception, i.e. usage of special symbols in wrong places to see if parser recognizes them as control symbols. There we go:
\section{Page dividers}
\tb Page dividers are represented by ------
\tbln If the line above has dashes ^^^^^^ here, it probably works alright.
\tbln Here's a genuine divider
\clearpage
\tb Fun fact: there can be a paragraph right after it!
\clearpage
Some text, cause why not
\tbln But this \textbf{DOES} look cursed, don't do it.
\section{Headers}
\tb Headers are most recognizable by '\#'-symbol. For it to actually represent a header, it needs to at the beginning of the line and there should be no more than 6 of them. \textbf{ANYTHING} up to the end of line will be included into a header, then.
\tbln Example:
\textbf{This is a long, yet formally correct header. You can add $equations$ and *formatting* here, but I provide no guarantees as of the way these would display in final document.}
\tb Here's an example of incorrect header:
\tbln \#\#\#\#\#\# This header is considered incorrect for this application, and will be parsed as regular paragraph instead
\tbln Another example of incorrect header, featuring 7 hash-symbols:
\tbln \#\#\#\#\#\#\# This will be parsed as a regular paragraph
\section{Equations}
\tb There are two types of equations in markdown/latex: inline mathmode and display mathmode
\tbln Both of them are defined by $-symbols. However, they must be paired to actually represent these. Here are examples:
\tbln Inline: $y = x ^ 2$
\tbln Display:
\begin{equation}
y = x ^ 2
\end{equation}
\tb Note that display mathmode should ALWAYS start from a new line for this application.
\tbln Basically, there's not much to deceive for: you can do that:
\tbln $
\tbln $
\tbln (the above should not be mathmode), and that's it, basically :sad:
\section{Table}
\tb Tables are defined by |-symbol. But the actual table will happen only if they are the first thing on the line.
\tbln Due to current implementation, there's no cure apart from display mathmode and code:
\tbln (this should be display mathmode):
\begin{equation}
|x|
\end{equation}
\tb (this should be a code block):
\begin{minted}[linenos, mathescape, autogobble, breaklines]{bash}
head /dev/random
| cat
\end{minted}
\section{Figure}
\tb Figure is defined by a ![[ sequence at a start of the line. Same points should be uphold here:
\begin{equation}
![[
\end{equation}
\begin{minted}[linenos, mathescape, autogobble, breaklines]{text}
![[
\end{minted}
\section{References}
\tb These start with []
\subsection{Hyperrefs}
\tb These are followed by parentheses (). Nothing is allowed between them, thus
\tbln Here's a correct href: \href{https://youtu.be/rickroll_link_lol}{my gh page}
\tbln Here are incorrect refs:
\tbln [my gh page] (https://youtu.be/rickroll\textit{link}lol)
\tbln [my gh page]
\tbln (https://youtu.be/rickroll\textit{link}lol)
\subsection{Footnote refs}
\tb These are started by [^.
\tbln Here are correct footnote refs: \footnotemark[1], \footnotemark[2]
\tbln Here are incorrect ones: [^1, ^1],
\tbln [^
\tbln 1
\tbln ]
\tbln So on
\subsection{Object refs}
\tb These refer to objects like figures, tables and equations.
\tbln Started by [@.
\tbln Here's a correct ref: \ref{fig:experiment_schema}
\tbln Here's are bad ones: [@ fig:1, @ source3],
\tbln [@
\tbln eq2
\tbln ]
\tbln \textbf{NOTE}: despite object types being prepended with 'eq', 'fig' or 'tab' in refs, you \textit{can} use regular latex to define your custom refs. In fact, LaTeX doesn't care about all of these at all; I'm doing these transformations just for my own comfort, as I've been doing this manually.
\section{Formatting}
\tb This object's type is deprecated and will be removed soon.
\tbln It starts and ends with
\begin{itemize}
\item * or \_
\item ** or \_\_
\item ~~
\end{itemize}
\tb \textbf{WARN}: As of now, these \textit{might} cause cause massive problems if encountered at the start of the line.
\tbln Basically, parsers for these tokens attempt parse everything up until they find a closing token. But if they are inserted in a middle of the text, then it's ok - they will be limited by current paragraph, and (most likely) will find no match inside it, if their use was unintended.
\section{Footnote content}
\tb These start with '\footnotemark[3]:' and proceed to the end of line.
\tbln Example of valid content:
\tbln Example of invalid content:
\tbln [^source2: This is invalid content\footnotemark[4]\footnotetext[4]{Explanation of source2.} : This is invalid content \footnotemark[4]: This is invalid content
\tbln They also must start at the beginning of the line. If fact, this: \footnotemark[4]: Some content
\tbln Is expected to be a footnote ref with a colon.
\section{Code blocks}
\tb There are two fundamental types of code blocks: normal code block and \textbf{Ayano} code block.
\subsection{Normal code block}
\tb These start and end with '```' on a newline. There can be \textbf{practically anything} inside. Example:
\begin{minted}[linenos, mathescape, autogobble, breaklines]{java}
int x = 2;
int y = 4;
int[] arr = new int[2];
arr[0] = x;
arr[1] = 4;
\end{minted}
\tb There can be anything after code block had closed:
\begin{minted}[linenos, mathescape, autogobble, breaklines]{lua(Idk why vscode detects this block closing as a valid lua syntax)}
local x = 'matt'
local y = 'y'
local z = x .. y -- matty!!
\end{minted}
Here's some text that's ok to be here
\tbln There can't be any spaces before delimiting ```s:
\tbln ```python
\tbln \# This here is not really a python block
\tbln x = 2
\tbln print(x)
\tbln ```
\subsection{Ayano code block}
\tb \textit{because Ayano loves Kyoko}
\tbln This object looks and behaves just like normal code block, but is only limited to \textit{python} language, can have spacial arguments in a language line, may leave something behind in the document. For example:
\tbln This Ayano block will leave plain "2" behind it:2
\tbln Akin to normal blocks, it's ok to leave something trailing:
\begin{minted}[linenos, mathescape, autogobble, breaklines]{ayano}
2
\end{minted}
This text is ok to be here
\tbln And these are invalid Ayano blocks:
\begin{minted}[linenos, mathescape, autogobble, breaklines]{ayano}
2
\end{minted}
\tb ```ayano
\tbln 2
\tbln ```
\end{document}

View file

@ -1,152 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
% bra and ket from QM
\usepackage{braket}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE
\LARGE
\vspace{100pt}
\LARGE
\Large
\Large
\Large
\huge
\vspace{50pt}
\large
\large
\large
\large
\vspace*{\fill}
\hspace{\fill}\large \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt
\# List-after-para
\tbln Some para
\begin{itemize}
\item List item 1
\item List item 2
\end{itemize}
\tb Some para
\begin{itemize}
\item List item 1
\item List item 2
\end{itemize}
\tb \textit{This bug resulted in lists to be parsed as series of paragraphs.}
\end{document}

View file

@ -1,185 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
% bra and ket from QM
\usepackage{braket}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE
\LARGE
\vspace{100pt}
\LARGE
\Large
\Large
\Large
\huge
\vspace{50pt}
\large
\large
\large
\large
\vspace*{\fill}
\hspace{\fill}\large \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt
Наступний тест. Тут протестуємо таблиці і блоки коду.
\subsection{Таблиці}
\tb Звичайні таблиці; Можуть всередині мати \textbf{формули} чи \textit{посилання} на інші штуки
\begin{table}[h!]
\fnt
\begin{center}
\hskip-1.5cm\begin{tabular}{|c|c|c|}\hline
Storage & Space & Number\\ \hline
\hline
1 & 2 & $3 + e ^{\pi}$\\ \hline
$e$\footnotemark[1]\footnotetext[1]{Це число Ейлера. Воно дуже корисне, \st{але не може бути записане} у вигляді \textit{десяткового} дробу.} & $\cdot \pm 1$ & І тут ще \textit{трохи} тексту \|\\ \hline
\end{tabular}
\stepcounter{tabnum}
\caption{\capfnt Оце приклад таблиці. Як казав мій вчитель: \textbf{ВИКОРИСТОВУЙ БІЛЬШЕ ВИБУХІВКИ} (і "лапок")}
\label{tab:example_1}
\end{center}
\end{table}
\tb Таблиця цілком собі може не мати ідентифікатора або опису:
\begin{table}[h!]
\fnt
\begin{center}
\hskip-1.5cm\begin{tabular}{|c|c|}\hline
Кількість & Роль\\ \hline
\hline
3 діоптрії & 7 галон\\ \hline
rust & \#PANIC!!!\\ \hline
\end{tabular}
\stepcounter{tabnum}\label{tab:table_2}
\end{center}
\end{table}
\subsection{Блоки коду}
\tb Наразі мають досить обмежену підтримку, адже у $\LaTeX$ немає нативного поняття блоків коду.
\tbln На щастя, у \textbf{markdown} таке поняття є\footnotemark[2]\footnotetext[2]{Принаймні, \textit{Obsidian} таке робить}:
\begin{minted}[linenos, mathescape, autogobble, breaklines]{rust}
let x = 5;
let y = x.powi(2);
let z = (y as f64).sqrt();
println!("x = {x}, y = {y}, z = {x}");
\end{minted}
\end{document}

View file

@ -1,149 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx, hyperref, xcolor, gensymb, amssymb, mathtools, wrapfig, mathtools, microtype, lastpage, caption, titlesec, paracol, longtable, booktabs, cancel}
\usepackage[T2A, T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian, ukrainian]{babel}
\hypersetup{
colorlinks,
linkcolor={blue!20!black},
citecolor={blue!50!black},
urlcolor={blue!80!black}
}
\usepackage[left = 25mm, right = 10mm, top=20mm, bottom=20mm, bindingoffset=0cm]{geometry}
% КОМАНДИ
\newcommand{\fnt}{
\fontshape{n}
\fontsize{14pt} {19pt}
\linespread{0.8}
\selectfont
} % нормативний шрифт
\newcommand{\capfnt}{
\fnt
} % норматичний шрифт для підписів (тимчасово співпадає зі звичайним шрифтом)
\newcommand{\tb}{
\hspace*{10mm}
} % відступ у 5 символів "x" відповідно до ДСТУ
\newcommand{\tbln}{
\newline
\tb
} % те ж саме, але з переносом рядка
\newcommand{\tbsp}{
\vspace*{1ex}
\tbln
} % те ж саме, але з додатковим відступом між рядками
% бібліографія
\usepackage[square,sort,comma,numbers]{natbib}
\renewcommand{\bibsection}{}
\usepackage{totcount}
\newtotcounter{citnum}
% лічильник цитувань
\def\oldbibitem{} \let\oldbibitem=\bibitem
\def\bibitem{\stepcounter{citnum}\oldbibitem}
% лічильник зображень
\newtotcounter{graphnum}
\def\oldincludegraphics{} \let\oldincludegraphics=\includegraphics
\def\includegraphics{\stepcounter{graphnum}\oldincludegraphics}
% лічильник таблиць (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{tabnum}
% лічильник додатків (НЕАВТОМАТИЧНИЙ!!!)
\newtotcounter{dodnum}
% нумерація сторінок
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\setlength{\headheight}{15.3pt}
\fancyhead[R]{\fnt \thepage}
% виноски
\renewcommand{\thefootnote}{\large\arabic{footnote})~}
\renewcommand{\footnoterule}{\rule{20mm}{0.4pt} \vspace*{0.5ex}}
% \newcommand{\vyn}[2]{
% \footnote[#1]{\large #2}
% } % створити виноску
\let\oldFootnote\footnote
\renewcommand{\footnote}[1]{
\oldFootnote{\large #1}
} % перегрузка виноски
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
% підписи малюнків/таблиць
\captionsetup[figure]{name={\fnt Рисунок~},labelsep=period}
\captionsetup[table]{name={\fnt Таблиця~},labelsep=period}
% markdown strikeouts
\usepackage{soul}
% codeblocks
\usepackage{minted}
% enumeration
\usepackage[shortlabels]{enumitem}
% scaleable font (whatever that means)
\usepackage[normalem]{ulem}
\titleformat*{\section}{\fnt\LARGE\bfseries}
\titleformat*{\subsection}{\fnt\Large\bfseries}
\titleformat*{\subsubsection}{\fnt\Large}
% bra and ket from QM
\usepackage{braket}
\begin{document}
\thispagestyle{empty} % [A]:for cropping
% ------------
% source: https://tex.stackexchange.com/questions/173651/need-help-to-create-such-a-beautiful-title-page
\begingroup
\centering
\obeylines
\topskip 0pt % source: https://tex.stackexchange.com/questions/2326/vertically-center-text-on-a-page
\vspace*{\fill}
\textbf{
\LARGE Навч. заклад
\LARGE Факультет
\vspace{100pt}
\LARGE ПРОТОКОЛ
\Large Виконання марної роботи
\Large з історії стародавнього Єгипту
\Large (пинальна частина)
\huge НАЗВА РОБОТИ
\vspace{50pt}
\large виконувало:
\large дяч дзучунович
\large (perfectly still)
\large вербень 2077
\vspace*{\fill}
\hspace{\fill}\large me :idk: \rule{20mm}{0.4pt}
\vspace{10pt}
}
\endgroup
% ------------
\newpage
\tableofcontents
\newpage
\fnt
\section{First para}
\tb This text is \textit{just} a dummy, so that document won't feel \st{too} \textbf{empty}
\tbln Have a some oily maccaroni:
\begin{equation}
\gamma = \lim_{n \to \infty} \sum\limits_{i=1}^{n} \dfrac{1}{n} - \ln(n)
\end{equation}
\end{document}