diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..fd961f8 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +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 diff --git a/.gitignore b/.gitignore index ea8c4bf..e7af667 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,10 @@ +# Build /target + +# VSCode +.vscode/settings.json +examples/advanced_ayano/sine_graph.png +examples/advanced_ayano/source/other_sine_graph.png + +/**/.kyomato_tmp +/**/*.pdf \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 53e0cae..d47bf89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,7 +1,1035 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 + +[[package]] +name = "ahash" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cached" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355face540df58778b96814c48abb3c2ed67c4878a8087ab1819c1fedeec505f" +dependencies = [ + "ahash", + "cached_proc_macro", + "cached_proc_macro_types", + "hashbrown", + "instant", + "once_cell", + "thiserror", +] + +[[package]] +name = "cached_proc_macro" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d52f526f7cbc875b296856ca8c964a9f6290556922c303a8a3883e3c676e6a1" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.0", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "examples-util" +version = "0.1.0" +dependencies = [ + "kyomato-lib", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding 2.3.1", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi", + "winapi-build", +] [[package]] name = "kyomato" version = "0.1.0" +dependencies = [ + "clap", + "derive_more", + "kyomato-lib", +] + +[[package]] +name = "kyomato-lib" +version = "0.1.0" +dependencies = [ + "cached", + "derive_more", + "examples-util", + "itertools", + "nom", + "once_cell", + "pathdiff", + "pyo3", + "rand", + "regex", + "serde", + "smart-default", + "text-diff", + "thiserror", + "url 2.5.0", + "url_serde", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f738b4e40d50b5711957f142878cfa0f28e054aa0ebdfc3fd137a843f74ed3" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc910d4851847827daf9d6cdd4a823fbdaab5b8818325c5e97a86da79e8881f" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "smart-default" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" + +[[package]] +name = "term" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2077e54d38055cf1ca0fd7933a2e00cd3ec8f6fed352b2a377f06dcdaaf3281" +dependencies = [ + "kernel32-sys", + "winapi", +] + +[[package]] +name = "text-diff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "309238dd66f8bf11a20d015b727b926f294a13fcb8d56770bb984e7a22c43897" +dependencies = [ + "getopts", + "term", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna 0.1.5", + "matches", + "percent-encoding 1.0.1", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna 0.5.0", + "percent-encoding 2.3.1", +] + +[[package]] +name = "url_serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74e7d099f1ee52f823d4bdd60c93c3602043c728f5db3b97bdb548467f7bddea" +dependencies = [ + "serde", + "url 1.7.2", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] diff --git a/Cargo.toml b/Cargo.toml index 6394329..8728fd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,41 @@ -[workspace] -members = [ "core"] -resolver = "1" +workspace = { members = ["examples-util", "kyomato-cli", "kyomato-core"] } +[package] +name = "kyomato-lib" +version = "0.1.0" +edition = "2021" -[workspace.lints.rust] -rust_2018_idioms = { level = "deny", priority = -1 } -missing_debug_implementations = "deny" -missing_docs = "deny" -unsafe_code = "deny" +[features] +default = ["serde"] # TODO REMOVE AFTER TOKEN REWORK +serde = ["dep:serde", "dep:url_serde"] -[workspace.lints.rustdoc] -broken_intra_doc_links = "forbid" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[workspace.lints.clippy] -module_name_repetitions = "allow" +[dependencies] +cached = "0.48.1" +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* diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1108ff --- /dev/null +++ b/README.md @@ -0,0 +1,251 @@ +*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 \ No newline at end of file diff --git a/core/README.md b/core/README.md deleted file mode 100644 index 271bbe9..0000000 --- a/core/README.md +++ /dev/null @@ -1,9 +0,0 @@ -So, here comes yet-another token redesign. Thing is, I've found a great Djot parser at , 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:. diff --git a/core/src/lib.rs b/core/src/lib.rs deleted file mode 100644 index 7913c79..0000000 --- a/core/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -#![doc = include_str!("../README.md")] diff --git a/examples-util/Cargo.toml b/examples-util/Cargo.toml new file mode 100644 index 0000000..cdf674a --- /dev/null +++ b/examples-util/Cargo.toml @@ -0,0 +1,9 @@ +[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 = ".."} \ No newline at end of file diff --git a/examples-util/src/lib.rs b/examples-util/src/lib.rs new file mode 100644 index 0000000..08b0bdb --- /dev/null +++ b/examples-util/src/lib.rs @@ -0,0 +1,19 @@ +use std::path::Path; + +pub fn setup_io( + input_path: impl AsRef, + output_path: impl AsRef, +) -> 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)) +} diff --git a/examples/advanced_ayano/2 b/examples/advanced_ayano/2 new file mode 100644 index 0000000..d8263ee --- /dev/null +++ b/examples/advanced_ayano/2 @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/examples/advanced_ayano/data/20240327230336_8798.csv b/examples/advanced_ayano/data/20240327230336_8798.csv new file mode 100644 index 0000000..1a33bab --- /dev/null +++ b/examples/advanced_ayano/data/20240327230336_8798.csv @@ -0,0 +1,16 @@ +"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" diff --git a/examples/advanced_ayano/data/3 b/examples/advanced_ayano/data/3 new file mode 100644 index 0000000..e440e5c --- /dev/null +++ b/examples/advanced_ayano/data/3 @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/examples/advanced_ayano/data/calculations_table.csv b/examples/advanced_ayano/data/calculations_table.csv new file mode 100644 index 0000000..f0baa03 --- /dev/null +++ b/examples/advanced_ayano/data/calculations_table.csv @@ -0,0 +1,5 @@ +'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 diff --git a/examples/advanced_ayano/data/doing_stuff.py b/examples/advanced_ayano/data/doing_stuff.py new file mode 100644 index 0000000..f9acb44 --- /dev/null +++ b/examples/advanced_ayano/data/doing_stuff.py @@ -0,0 +1,42 @@ +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) \ No newline at end of file diff --git a/examples/advanced_ayano/data/read3.py b/examples/advanced_ayano/data/read3.py new file mode 100644 index 0000000..99766fa --- /dev/null +++ b/examples/advanced_ayano/data/read3.py @@ -0,0 +1,3 @@ +three = None +with open("3", "r") as f: + three = int(f.readline()) \ No newline at end of file diff --git a/examples/advanced_ayano/images/nomi.jpg b/examples/advanced_ayano/images/nomi.jpg new file mode 100644 index 0000000..cf3552c Binary files /dev/null and b/examples/advanced_ayano/images/nomi.jpg differ diff --git a/examples/advanced_ayano/main.rs b/examples/advanced_ayano/main.rs new file mode 100644 index 0000000..dcd9b5b --- /dev/null +++ b/examples/advanced_ayano/main.rs @@ -0,0 +1,40 @@ +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::(); + // 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(); +} diff --git a/examples/advanced_ayano/output.tex b/examples/advanced_ayano/output.tex new file mode 100644 index 0000000..8964a8c --- /dev/null +++ b/examples/advanced_ayano/output.tex @@ -0,0 +1,544 @@ +\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} \ No newline at end of file diff --git a/examples/advanced_ayano/scripts/calculate_e.py b/examples/advanced_ayano/scripts/calculate_e.py new file mode 100644 index 0000000..8bf4308 --- /dev/null +++ b/examples/advanced_ayano/scripts/calculate_e.py @@ -0,0 +1,5 @@ +e = 1.0 +f = 1.0 +for i in range(1, 100): + f /= i + e += f diff --git a/examples/advanced_ayano/scripts/generate_sine.py b/examples/advanced_ayano/scripts/generate_sine.py new file mode 100644 index 0000000..5e5fc6c --- /dev/null +++ b/examples/advanced_ayano/scripts/generate_sine.py @@ -0,0 +1,12 @@ +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) \ No newline at end of file diff --git a/examples/advanced_ayano/scripts/toshino_kyoko.py b/examples/advanced_ayano/scripts/toshino_kyoko.py new file mode 100644 index 0000000..20acd4c --- /dev/null +++ b/examples/advanced_ayano/scripts/toshino_kyoko.py @@ -0,0 +1,7 @@ +# 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 \ No newline at end of file diff --git a/examples/advanced_ayano/source/input.md b/examples/advanced_ayano/source/input.md new file mode 100644 index 0000000..f3f1f65 --- /dev/null +++ b/examples/advanced_ayano/source/input.md @@ -0,0 +1,131 @@ +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} diff --git a/examples/advanced_ayano/source/sine_graph.png b/examples/advanced_ayano/source/sine_graph.png new file mode 100644 index 0000000..bed9e05 Binary files /dev/null and b/examples/advanced_ayano/source/sine_graph.png differ diff --git a/examples/advanced_ayano/test.py b/examples/advanced_ayano/test.py new file mode 100644 index 0000000..d23d548 --- /dev/null +++ b/examples/advanced_ayano/test.py @@ -0,0 +1,41 @@ + +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 \ No newline at end of file diff --git a/examples/basic_ayano/input.md b/examples/basic_ayano/input.md new file mode 100644 index 0000000..8f73a72 --- /dev/null +++ b/examples/basic_ayano/input.md @@ -0,0 +1,56 @@ +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. \ No newline at end of file diff --git a/examples/basic_ayano/main.rs b/examples/basic_ayano/main.rs new file mode 100644 index 0000000..46b2899 --- /dev/null +++ b/examples/basic_ayano/main.rs @@ -0,0 +1,24 @@ +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(); +} diff --git a/examples/basic_ayano/output.tex b/examples/basic_ayano/output.tex new file mode 100644 index 0000000..79c06d9 --- /dev/null +++ b/examples/basic_ayano/output.tex @@ -0,0 +1,218 @@ +\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} \ No newline at end of file diff --git a/examples/basic_syntax/images/loss_meme.png b/examples/basic_syntax/images/loss_meme.png new file mode 100644 index 0000000..1a6aa46 Binary files /dev/null and b/examples/basic_syntax/images/loss_meme.png differ diff --git a/examples/basic_syntax/input.md b/examples/basic_syntax/input.md new file mode 100644 index 0000000..b22debc --- /dev/null +++ b/examples/basic_syntax/input.md @@ -0,0 +1,108 @@ +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(pub T); + +impl PartialEq for Equivalent { + fn eq(&self, _: &Self) -> bool { + true + } +} + +impl Eq for Equivalent {} + +// 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. \ No newline at end of file diff --git a/examples/basic_syntax/main.rs b/examples/basic_syntax/main.rs new file mode 100644 index 0000000..3b683b1 --- /dev/null +++ b/examples/basic_syntax/main.rs @@ -0,0 +1,33 @@ +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::() + // 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(); +} diff --git a/examples/basic_syntax/output.tex b/examples/basic_syntax/output.tex new file mode 100644 index 0000000..148bb63 --- /dev/null +++ b/examples/basic_syntax/output.tex @@ -0,0 +1,259 @@ +\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(pub T); + +impl PartialEq for Equivalent { + fn eq(&self, _: &Self) -> bool { + true + } +} + +impl Eq for Equivalent {} + +// 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} \ No newline at end of file diff --git a/examples/title_info/input.md b/examples/title_info/input.md new file mode 100644 index 0000000..57513d1 --- /dev/null +++ b/examples/title_info/input.md @@ -0,0 +1,23 @@ +--- +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. \ No newline at end of file diff --git a/examples/title_info/main.rs b/examples/title_info/main.rs new file mode 100644 index 0000000..ea878d7 --- /dev/null +++ b/examples/title_info/main.rs @@ -0,0 +1,24 @@ +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(); +} diff --git a/examples/title_info/output.tex b/examples/title_info/output.tex new file mode 100644 index 0000000..fe7d798 --- /dev/null +++ b/examples/title_info/output.tex @@ -0,0 +1,143 @@ +\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} \ No newline at end of file diff --git a/kyomato-cli/Cargo.toml b/kyomato-cli/Cargo.toml new file mode 100644 index 0000000..28b83b5 --- /dev/null +++ b/kyomato-cli/Cargo.toml @@ -0,0 +1,11 @@ +[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 = ".." } \ No newline at end of file diff --git a/kyomato-cli/src/main.rs b/kyomato-cli/src/main.rs new file mode 100644 index 0000000..03d97ad --- /dev/null +++ b/kyomato-cli/src/main.rs @@ -0,0 +1,277 @@ +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, + /// Sets a dedicated image directory. If omitted, "KYOMATO_IMAGES" envvar will be checked. + #[arg(short, long = "im")] + image_directory: Option, + /// 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, +} + +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::(), + 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::(), + 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::(), + 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::(), + 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::(), + 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::(), + 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::(), + 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::(), + config.generator, + ), + (true, None, None, None) => main_inner( + input, + base_gen() + .with_check(fs_check) + .with_ayano() + .with_error_type::(), + 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::(), + 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::(), + 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::(), + 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::(), + 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::(), + 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::(), + 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::(), + config.generator, + ), + } +} + +fn main_inner>( + 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"); +} diff --git a/kyomato-core/Cargo.toml b/kyomato-core/Cargo.toml new file mode 100644 index 0000000..a70fed3 --- /dev/null +++ b/kyomato-core/Cargo.toml @@ -0,0 +1,18 @@ +[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" ] diff --git a/kyomato-core/src/lib.rs b/kyomato-core/src/lib.rs new file mode 100644 index 0000000..732bd45 --- /dev/null +++ b/kyomato-core/src/lib.rs @@ -0,0 +1,51 @@ +#![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"; diff --git a/kyomato-core/src/selci/ayano.rs b/kyomato-core/src/selci/ayano.rs new file mode 100644 index 0000000..9ae9455 --- /dev/null +++ b/kyomato-core/src/selci/ayano.rs @@ -0,0 +1,15 @@ +#[derive(Debug)] +pub struct Selci { + // TODO +} + +#[derive(Debug)] +pub(crate) struct InnerSelci { + // TODO +} + +impl InnerSelci { + pub fn seed(&self, source: &str, offset: isize) -> Result { + todo!() + } +} diff --git a/kyomato-core/src/selci/code.rs b/kyomato-core/src/selci/code.rs new file mode 100644 index 0000000..3c460dc --- /dev/null +++ b/kyomato-core/src/selci/code.rs @@ -0,0 +1,31 @@ +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, + code: Span, +} + +impl InnerSelci { + pub fn seed<'tree>( + &self, + position: usize, + tree: &'tree SeededSelciTree<'tree>, + ) -> Result, TreeSeedingError> { + todo!() + } +} diff --git a/kyomato-core/src/selci/footnote.rs b/kyomato-core/src/selci/footnote.rs new file mode 100644 index 0000000..93966da --- /dev/null +++ b/kyomato-core/src/selci/footnote.rs @@ -0,0 +1,39 @@ +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>, +} + +impl<'tree> Selci<'tree> { + pub fn content(&self) -> impl Iterator> { + 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, TreeSeedingError> { + Ok(Selci { + ident: tree.seed_ident_span(&self.ident)?, + inner_content: tree.children_iter(position, self.content_length).into(), + }) + } +} diff --git a/kyomato-core/src/selci/formatting.rs b/kyomato-core/src/selci/formatting.rs new file mode 100644 index 0000000..1844994 --- /dev/null +++ b/kyomato-core/src/selci/formatting.rs @@ -0,0 +1,51 @@ +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 `` tag + Bold, + /// Italic format, represented by `*` or `` 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>, +} + +impl<'tree> Selci<'tree> { + /// Possibly multiple [super::Selci] to apply formatting to + pub fn content(&self) -> impl Iterator> { + 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, TreeSeedingError> { + Ok(Selci { + kind: self.kind, + inner_content: tree.children_iter(position, self.content_length).into(), + }) + } +} diff --git a/kyomato-core/src/selci/image.rs b/kyomato-core/src/selci/image.rs new file mode 100644 index 0000000..860758b --- /dev/null +++ b/kyomato-core/src/selci/image.rs @@ -0,0 +1,55 @@ +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>, + /// With of the figure, as a fraction of full document width + pub width: Option, + inner_caption: Immutable>, +} + +impl<'tree> Selci<'tree> { + pub fn caption(&self) -> impl Iterator> { + self.inner_caption.clone().unwrap() + } +} + +#[derive(Debug)] +pub(crate) struct InnerSelci { + src: Span, + ident: Option, + width: Option, + caption_length: ChildrenCount, +} + +impl InnerSelci { + pub fn seed<'tree>( + &self, + position: usize, + tree: &'tree SeededSelciTree<'tree>, + ) -> Result, 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(), + }) + } +} diff --git a/kyomato-core/src/selci/link.rs b/kyomato-core/src/selci/link.rs new file mode 100644 index 0000000..578c277 --- /dev/null +++ b/kyomato-core/src/selci/link.rs @@ -0,0 +1,45 @@ +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>, +} + +impl<'tree> Selci<'tree> { + pub fn content(&self) -> impl Iterator> { + 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, TreeSeedingError> { + Ok(Selci { + link: tree.seed_span(&self.link)?, + is_wikilink: self.is_wikilink, + inner_content: tree.children_iter(position, self.content_length).into(), + }) + } +} diff --git a/kyomato-core/src/selci/list.rs b/kyomato-core/src/selci/list.rs new file mode 100644 index 0000000..0241a54 --- /dev/null +++ b/kyomato-core/src/selci/list.rs @@ -0,0 +1,76 @@ +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 `