diff --git a/.envrc b/.envrc index 7076030..8c3f443 100644 --- a/.envrc +++ b/.envrc @@ -1,4 +1,5 @@ -# Use with direnv. I use it in zsh and it seems to work pretty well for me. +# Use with direnv. # https://nixos.wiki/wiki/Development_environment_with_nix-shell # https://direnv.net/ use flake +source_up diff --git a/Cargo.lock b/Cargo.lock index 340170a..0f6c357 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,7 +52,7 @@ dependencies = [ "actix-rt", "actix-service", "actix-utils", - "ahash 0.8.3", + "ahash 0.8.11", "base64 0.21.2", "bitflags 1.3.2", "brotli", @@ -207,7 +207,7 @@ dependencies = [ "serde_urlencoded", "smallvec", "socket2", - "time 0.3.22", + "time 0.3.36", "url", ] @@ -277,14 +277,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -347,6 +348,27 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayref" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "askama_escape" version = "0.10.3" @@ -361,7 +383,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.72", ] [[package]] @@ -381,6 +403,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "barrel" version = "0.7.0" @@ -411,6 +439,49 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "biblatex" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27fe7285040d0227cd8b5395e1c4783f44f0b673eca5a657f4432ae401f2b7b8" +dependencies = [ + "numerals", + "paste", + "strum", + "unicode-normalization", + "unscanny", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -419,9 +490,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbe3c979c178231552ecba20214a8272df4e09f232a87aef4320cf06539aded" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -459,6 +533,24 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.4.0" @@ -489,6 +581,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chinese-number" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fccaef6346f6d6a741908d3b79fe97c2debe2fbb5eb3a7d00ff5981b52bb6c" +dependencies = [ + "chinese-variant", + "enum-ordinalize", + "num-bigint", + "num-traits", +] + +[[package]] +name = "chinese-variant" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7588475145507237ded760e52bf2f1085495245502033756d28ea72ade0e498b" + [[package]] name = "chrono" version = "0.4.26" @@ -504,6 +614,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -514,6 +651,16 @@ dependencies = [ "inout", ] +[[package]] +name = "citationberg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d259fe9fd78ffa05a119581d20fddb50bfba428311057b12741ffb9015123d0b" +dependencies = [ + "quick-xml 0.31.0", + "serde", +] + [[package]] name = "clap" version = "2.34.0" @@ -538,6 +685,41 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "comemo" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6916408a724339aa77b18214233355f3eb04c42eb895e5f8909215bd8a7a91" +dependencies = [ + "comemo-macros", + "once_cell", + "parking_lot", + "siphasher 1.0.1", +] + +[[package]] +name = "comemo-macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8936e42f9b4f5bdfaf23700609ac1f11cb03ad4c1ec128a4ee4fd0903e228db" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "commoncrypto" version = "0.2.0" @@ -582,7 +764,7 @@ dependencies = [ "rand 0.8.5", "sha2", "subtle", - "time 0.3.22", + "time 0.3.36", "version_check", ] @@ -602,6 +784,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "core_maths" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3" +dependencies = [ + "libm", +] + [[package]] name = "cpufeatures" version = "0.2.8" @@ -620,6 +811,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -643,6 +865,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "ctr" version = "0.9.2" @@ -652,6 +895,21 @@ dependencies = [ "cipher", ] +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -682,6 +940,32 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "ecow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54bfbb1708988623190a6c4dbedaeaf0f53c20c6395abd6a01feb327b3146f4b" +dependencies = [ + "serde", +] + [[package]] name = "either" version = "1.8.1" @@ -704,6 +988,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -713,6 +1003,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "env_logger" version = "0.5.13" @@ -726,6 +1036,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.1" @@ -759,6 +1075,22 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "fastrand" version = "1.9.0" @@ -768,6 +1100,15 @@ dependencies = [ "instant", ] +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + [[package]] name = "flate2" version = "1.0.26" @@ -778,12 +1119,30 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "log", + "slotmap", + "tinyvec", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -890,6 +1249,16 @@ dependencies = [ "polyval", ] +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "h2" version = "0.3.19" @@ -902,13 +1271,23 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -917,11 +1296,11 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.11", "allocator-api2", ] @@ -931,29 +1310,62 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" dependencies = [ - "hashbrown 0.14.0", + "hashbrown 0.14.5", ] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "hayagriva" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "1d0d20c98b77b86ce737876b2a1653e2e6abbeee84afbb39d72111091191c97a" dependencies = [ - "libc", + "biblatex", + "ciborium", + "citationberg", + "indexmap 2.3.0", + "numerals", + "paste", + "serde", + "serde_yaml", + "thiserror", + "unic-langid", + "unicode-segmentation", + "unscanny", + "url", ] [[package]] -name = "hermit-abi" -version = "0.2.6" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] -name = "hermit-abi" +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" @@ -1031,6 +1443,12 @@ dependencies = [ "quick-error", ] +[[package]] +name = "hypher" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b24ad5637230df201ab1034d593f1d09bf7f2a9274f2e8897638078579f4265" + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -1054,6 +1472,154 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "serde", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "serde", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "postcard", + "serde", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_adapters" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6324dfd08348a8e0374a447ebd334044d766b1839bb8d5ccf2482a99a77c0bc" +dependencies = [ + "icu_locid", + "icu_locid_transform", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_provider_blob" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c24b98d1365f55d78186c205817631a4acf08d7a45bdf5dc9dcf9c5d54dccf51" +dependencies = [ + "icu_provider", + "postcard", + "serde", + "writeable", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "icu_segmenter" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de" +dependencies = [ + "core_maths", + "displaydoc", + "icu_collections", + "icu_locid", + "icu_provider", + "icu_segmenter_data", + "serde", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df" + [[package]] name = "idna" version = "0.3.0" @@ -1074,6 +1640,33 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-traits", + "png", +] + +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "indexmap" version = "1.9.3" @@ -1084,6 +1677,23 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "indexmap" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", + "serde", +] + +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + [[package]] name = "inout" version = "0.1.3" @@ -1128,6 +1738,12 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.64" @@ -1143,6 +1759,24 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +[[package]] +name = "kamadak-exif" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077" +dependencies = [ + "mutate_once", +] + +[[package]] +name = "kurbo" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +dependencies = [ + "arrayvec", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1184,6 +1818,12 @@ version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libsqlite3-sys" version = "0.26.0" @@ -1194,12 +1834,37 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "lipsum" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "636860251af8963cc40f6b4baadee105f02e21b28131d76eba8e40ce84ab8064" +dependencies = [ + "rand 0.8.5", + "rand_chacha", +] + +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +dependencies = [ + "serde", +] + [[package]] name = "local-channel" version = "0.1.3" @@ -1275,6 +1940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -1289,6 +1955,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mutate_once" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" + [[package]] name = "native-tls" version = "0.2.11" @@ -1317,11 +1989,36 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1336,6 +2033,12 @@ dependencies = [ "libc", ] +[[package]] +name = "numerals" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25be21376a772d15f97ae789845340a9651d3c4246ff5ebb6a2b35f9c37bd31" + [[package]] name = "once_cell" version = "1.18.0" @@ -1371,7 +2074,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.72", ] [[package]] @@ -1420,6 +2123,30 @@ dependencies = [ "uuid", ] +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1445,9 +2172,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" @@ -1455,6 +2182,54 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1473,6 +2248,32 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "plist" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" +dependencies = [ + "base64 0.22.1", + "indexmap 2.3.0", + "quick-xml 0.32.0", + "serde", + "time 0.3.36", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polyval" version = "0.6.1" @@ -1485,6 +2286,29 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" + +[[package]] +name = "postcard" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +dependencies = [ + "cobs", + "embedded-io", + "serde", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1499,13 +2323,28 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "qcms" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edecfcd5d755a5e5d98e24cf43113e7cdaec5a070edd0f6b250c03a573da30fa" + [[package]] name = "quick-error" version = "1.2.3" @@ -1513,12 +2352,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] -name = "quote" -version = "1.0.28" +name = "quick-xml" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ - "proc-macro2", + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", ] [[package]] @@ -1585,6 +2443,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -1602,7 +2480,7 @@ checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.2", ] [[package]] @@ -1611,13 +2489,25 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + [[package]] name = "rusqlite" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" dependencies = [ - "bitflags 2.3.2", + "bitflags 2.6.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1657,12 +2547,43 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "rustybuzz" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ae5692c5beaad6a9e22830deeed7874eae8a4e3ba4076fb48e12c56856222c" +dependencies = [ + "bitflags 2.6.0", + "bytemuck", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.21" @@ -1724,19 +2645,22 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.164" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.72", ] [[package]] @@ -1750,6 +2674,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1762,6 +2695,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.3.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.6.1" @@ -1808,12 +2754,39 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "simple-error" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.8" @@ -1823,11 +2796,20 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -1839,6 +2821,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + [[package]] name = "standback" version = "0.2.17" @@ -1897,18 +2904,59 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.72", +] + [[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "svgtypes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" +dependencies = [ + "kurbo", + "siphasher 0.3.11", +] + [[package]] name = "syn" version = "1.0.109" @@ -1922,15 +2970,48 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "syntect" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +dependencies = [ + "bincode", + "bitflags 1.3.2", + "fancy-regex", + "flate2", + "fnv", + "once_cell", + "plist", + "regex-syntax 0.8.4", + "serde", + "serde_derive", + "serde_json", + "thiserror", + "walkdir", + "yaml-rust", +] + [[package]] name = "tempfile" version = "3.6.0" @@ -1963,6 +3044,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "time" version = "0.1.45" @@ -1991,21 +3092,24 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ + "deranged", "itoa", + "num-conv", + "powerfmt", "serde", "time-core", - "time-macros 0.2.9", + "time-macros 0.2.18", ] [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" @@ -2019,10 +3123,11 @@ dependencies = [ [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -2065,6 +3170,7 @@ dependencies = [ "time 0.2.27", "timer", "toml 0.4.10", + "typst", "uuid", ] @@ -2077,6 +3183,28 @@ dependencies = [ "chrono", ] +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "serde", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2141,6 +3269,40 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap 2.3.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.37" @@ -2162,12 +3324,163 @@ dependencies = [ "once_cell", ] +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "two-face" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37bed2135b2459c7eefba72c906d374697eb15949c205f2f124e3636a46b5eeb" +dependencies = [ + "once_cell", + "serde", + "syntect", +] + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "typst" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12492297d20937494f0143ae50ef339e5fd3d927b4096af1c52fe73fb9c5fa9a" +dependencies = [ + "az", + "bitflags 2.6.0", + "chinese-number", + "ciborium", + "comemo", + "csv", + "ecow", + "fontdb", + "hayagriva", + "hypher", + "icu_properties", + "icu_provider", + "icu_provider_adapters", + "icu_provider_blob", + "icu_segmenter", + "if_chain", + "image", + "indexmap 2.3.0", + "kamadak-exif", + "kurbo", + "lipsum", + "log", + "once_cell", + "palette", + "phf", + "png", + "portable-atomic", + "qcms", + "rayon", + "regex", + "roxmltree", + "rustybuzz", + "serde", + "serde_json", + "serde_yaml", + "siphasher 1.0.1", + "smallvec", + "stacker", + "syntect", + "time 0.3.36", + "toml 0.8.19", + "ttf-parser", + "two-face", + "typed-arena", + "typst-assets", + "typst-macros", + "typst-syntax", + "typst-timing", + "unicode-bidi", + "unicode-math-class", + "unicode-script", + "unicode-segmentation", + "usvg", + "wasmi", +] + +[[package]] +name = "typst-assets" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b3061f8d268e8eec7481c9ab24540455cb4912983c49aae38fa6e8bf8ef4d9c" + +[[package]] +name = "typst-macros" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a0fdfd46b4920b0f8e4215e5b8438c737e8bc3498a681ea59b0130228363fc" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "typst-syntax" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3db69f2f41613b1ff6edbec44fd7dc524137f099ee36c46f560cedeaadb40c4" +dependencies = [ + "comemo", + "ecow", + "once_cell", + "serde", + "unicode-ident", + "unicode-math-class", + "unicode-script", + "unicode-segmentation", + "unscanny", +] + +[[package]] +name = "typst-timing" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b58e17192bcacb2a39aace6d3eece70f008b2949ce384ac501a58357fafee67" +dependencies = [ + "parking_lot", + "serde", + "serde_json", + "typst-syntax", +] + +[[package]] +name = "unic-langid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dd9d1e72a73b25e07123a80776aae3e7b0ec461ef94f9151eed6ec88005a44" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" +dependencies = [ + "serde", + "tinystr", +] + [[package]] name = "unicase" version = "2.6.0" @@ -2183,12 +3496,30 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + [[package]] name = "unicode-ident" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +[[package]] +name = "unicode-math-class" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d246cf599d5fae3c8d56e04b20eb519adb89a8af8d0b0fbcded369aa3647d65" + [[package]] name = "unicode-normalization" version = "0.1.22" @@ -2198,6 +3529,30 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-script" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + [[package]] name = "unicode-width" version = "0.1.10" @@ -2214,6 +3569,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "unscanny" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" + [[package]] name = "url" version = "2.4.0" @@ -2223,8 +3590,75 @@ dependencies = [ "form_urlencoded", "idna 0.4.0", "percent-encoding", + "serde", +] + +[[package]] +name = "usvg" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "377f62b4a3c173de8654c1aa80ab1dac1154e6f13a779a9943e53780120d1625" +dependencies = [ + "base64 0.21.2", + "log", + "pico-args", + "usvg-parser", + "usvg-text-layout", + "usvg-tree", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a05e6f2023d6b4e946f734240a3927aefdcf930d7d42587a2c8a8869814b0" +dependencies = [ + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "roxmltree", + "simplecss", + "siphasher 0.3.11", + "svgtypes", + "usvg-tree", ] +[[package]] +name = "usvg-text-layout" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c41888b9d5cf431fe852eaf9d047bbde83251b98f1749c2f08b1071e6db46e2" +dependencies = [ + "fontdb", + "kurbo", + "log", + "rustybuzz", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "usvg-tree", +] + +[[package]] +name = "usvg-tree" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18863e0404ed153d6e56362c5b1146db9f4f262a3244e3cf2dbe7d8a85909f05" +dependencies = [ + "strict-num", + "svgtypes", + "tiny-skia-path", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uuid" version = "0.8.2" @@ -2253,6 +3687,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -2286,7 +3730,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -2308,7 +3752,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2319,6 +3763,52 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "wasmi" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8281d1d660cdf54c76a3efa9ddd0c270cada1383a995db3ccb43d166456c7" +dependencies = [ + "smallvec", + "spin", + "wasmi_arena", + "wasmi_core", + "wasmparser-nostd", +] + +[[package]] +name = "wasmi_arena" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" + +[[package]] +name = "wasmi_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + +[[package]] +name = "wasmparser-nostd" +version = "0.100.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" +dependencies = [ + "indexmap-nostd", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "winapi" version = "0.3.9" @@ -2482,6 +3972,136 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f" +dependencies = [ + "displaydoc", + "litemap", + "serde", + "zerovec", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "zstd" version = "0.12.3+zstd.1.5.2" diff --git a/elm/src/Data.elm b/elm/src/Data.elm index 1deacad..884cc4d 100644 --- a/elm/src/Data.elm +++ b/elm/src/Data.elm @@ -1,11 +1,15 @@ module Data exposing ( Allocation , AllocationId(..) + , ExtraField + , InvoiceItem , ListProject , LoginData , PayEntry , PayEntryId(..) , PayType(..) + , PrintInvoice + , PrintInvoiceInternal , Project , ProjectEdit , ProjectId(..) @@ -16,6 +20,7 @@ module Data exposing , SavePayEntry , SaveProject , SaveProjectEdit + , SaveProjectInvoice , SaveProjectMember , SaveProjectTime , SaveTimeEntry @@ -38,11 +43,13 @@ module Data exposing , decodeSavedProjectEdit , decodeTimeEntry , decodeUser + , encodePrintInvoice , encodeRole , encodeSaveAllocation , encodeSavePayEntry , encodeSaveProject , encodeSaveProjectEdit + , encodeSaveProjectInvoice , encodeSaveProjectMember , encodeSaveProjectTime , encodeSaveTimeEntry @@ -54,19 +61,25 @@ module Data exposing , getTimeEntryIdVal , ldToOdLd , makeAllocationId + , makeInvoiceId , makePayEntryId , makeProjectId , makeTimeEntryId , odLdToLd + , piDate , projectMemberToUser , roleToString , showRole , stringToRole + , toPi + -- , toPrintInvoice + , toSaveProjectInvoice ) import Json.Decode as JD import Json.Encode as JE import Orgauth.Data as OD exposing (UserId, getUserIdVal, makeUserId) +import Time import UUID exposing (UUID) import Url.Builder as UB import Util exposing (andMap) @@ -142,6 +155,13 @@ type alias Project = { id : ProjectId , name : String , description : String + , dueDays : Maybe Int + , extraFields : List ExtraField + , invoiceIdTemplate : String + , invoiceSeq : Int + , payer : String + , payee : String + , genericTask : String , public : Bool , rate : Maybe Float , currency : Maybe String @@ -150,10 +170,24 @@ type alias Project = } +type alias SaveProjectInvoice = + { id : ProjectId + , extraFields : List ExtraField + , invoiceSeq : Int + } + + type alias SaveProject = { id : Maybe ProjectId , name : String , description : String + , dueDays : Maybe Int + , extraFields : List ExtraField + , invoiceIdTemplate : String + , invoiceSeq : Int + , payer : String + , payee : String + , genericTask : String , public : Bool , rate : Maybe Float , currency : Maybe String @@ -326,6 +360,110 @@ type alias ProjectTime = } +type alias InvoiceItem = + { description : String + , duration : Float + , rate : Float + } + + +type alias ExtraField = + { n : String + , v : String + } + + +type alias PrintInvoiceInternal = + { projectid : ProjectId + , seq : Int + , duedays : Maybe Int + , idtemplate : String + , payer : String + , payee : String + , items : List InvoiceItem + , extraFields : List ExtraField + } + + +makeInvoiceId : String -> String -> Int -> String +makeInvoiceId template date seq = + template + |> String.replace "" (String.fromInt seq) + |> String.replace "" date + + +piDate : Time.Posix -> Time.Zone -> String +piDate time zone = + (String.fromInt <| Time.toYear zone time) + ++ "-" + ++ (String.fromInt <| Util.monthInt <| Time.toMonth zone time) + ++ "-" + ++ (String.fromInt <| Time.toDay zone time) + + +toPi : + PrintInvoiceInternal + -> String + -> String + -> PrintInvoice +toPi pii date duedate = + { id = makeInvoiceId pii.idtemplate date pii.seq + , payer = pii.payer + , payee = pii.payee + , items = pii.items + , date = date + , dueDate = + if duedate == "" then + Nothing + + else + Just duedate + , extraFields = pii.extraFields + } + + +type alias PrintInvoice = + { id : String + , payer : String + , payee : String + , items : List InvoiceItem + , date : String + , dueDate : Maybe String + , extraFields : List ExtraField + } + + +toSaveProjectInvoice : PrintInvoiceInternal -> SaveProjectInvoice +toSaveProjectInvoice pi = + { id = pi.projectid + , extraFields = pi.extraFields + , invoiceSeq = pi.seq + } + + +encodeInvoiceItem : InvoiceItem -> JE.Value +encodeInvoiceItem ii = + JE.object + [ ( "description", JE.string ii.description ) + , ( "duration", JE.float ii.duration ) + , ( "rate", JE.float ii.rate ) + ] + + +encodePrintInvoice : PrintInvoice -> JE.Value +encodePrintInvoice pi = + JE.object <| + List.filterMap identity + [ Just ( "id", JE.string pi.id ) + , Just ( "payer", JE.string pi.payer ) + , Just ( "payee", JE.string pi.payee ) + , Just ( "date", JE.string pi.date ) + , pi.dueDate |> Maybe.map (\dd -> ( "due_date", JE.string dd )) + , Just ( "items", JE.list encodeInvoiceItem pi.items ) + , Just ( "extra_fields", JE.list encodeExtraField pi.extraFields ) + ] + + ------------------------------------------- -- Id types. They're all ints underneath. @@ -437,25 +575,48 @@ decodeListProject = |> andMap (JD.field "role" decodeRole) +encodeSaveProjectInvoice : SaveProjectInvoice -> JE.Value +encodeSaveProjectInvoice sp = + JE.object <| + [ ( "id", JE.int (getProjectIdVal sp.id) ) + , ( "extra_fields", JE.list encodeExtraField sp.extraFields ) + , ( "invoice_seq", JE.int sp.invoiceSeq ) + ] + + encodeSaveProject : SaveProject -> JE.Value encodeSaveProject sp = JE.object <| - [ ( "name", JE.string sp.name ) - , ( "description", JE.string sp.description ) - , ( "public", JE.bool sp.public ) + List.filterMap identity + [ Just ( "name", JE.string sp.name ) + , Just ( "description", JE.string sp.description ) + , sp.dueDays |> Maybe.map (\dd -> ( "due_days", JE.int dd )) + , Just ( "extra_fields", JE.list encodeExtraField sp.extraFields ) + , Just ( "invoice_id_template", JE.string sp.invoiceIdTemplate ) + , Just ( "invoice_seq", JE.int sp.invoiceSeq ) + , Just ( "payer", JE.string sp.payer ) + , Just ( "payee", JE.string sp.payee ) + , Just ( "generic_task", JE.string sp.genericTask ) + , Just ( "public", JE.bool sp.public ) + , sp.rate |> Maybe.map (\rate -> ( "rate", JE.float rate )) + , sp.currency |> Maybe.map (\currency -> ( "currency", JE.string currency )) + , sp.id |> Maybe.map (\id -> ( "id", JE.int (getProjectIdVal id) )) + ] + + +encodeExtraField : ExtraField -> JE.Value +encodeExtraField ef = + JE.object + [ ( "n", JE.string ef.n ) + , ( "v", JE.string ef.v ) ] - ++ (sp.rate - |> Maybe.map (\rate -> [ ( "rate", JE.float rate ) ]) - |> Maybe.withDefault [] - ) - ++ (sp.currency - |> Maybe.map (\currency -> [ ( "currency", JE.string currency ) ]) - |> Maybe.withDefault [] - ) - ++ (sp.id - |> Maybe.map (\id -> [ ( "id", JE.int (getProjectIdVal id) ) ]) - |> Maybe.withDefault [] - ) + + +decodeExtraField : JD.Decoder ExtraField +decodeExtraField = + JD.succeed ExtraField + |> andMap (JD.field "n" JD.string) + |> andMap (JD.field "v" JD.string) decodeProject : JD.Decoder Project @@ -464,6 +625,13 @@ decodeProject = |> andMap (JD.field "id" JD.int |> JD.map makeProjectId) |> andMap (JD.field "name" JD.string) |> andMap (JD.field "description" JD.string) + |> andMap (JD.field "due_days" (JD.maybe JD.int)) + |> andMap (JD.field "extra_fields" <| JD.list decodeExtraField) + |> andMap (JD.field "invoice_id_template" JD.string) + |> andMap (JD.field "invoice_seq" JD.int) + |> andMap (JD.field "payer" JD.string) + |> andMap (JD.field "payee" JD.string) + |> andMap (JD.field "generic_task" JD.string) |> andMap (JD.field "public" JD.bool) |> andMap (JD.field "rate" <| JD.maybe JD.float) |> andMap (JD.field "currency" <| JD.maybe JD.string) diff --git a/elm/src/Main.elm b/elm/src/Main.elm index 5edeb80..08baaae 100644 --- a/elm/src/Main.elm +++ b/elm/src/Main.elm @@ -37,6 +37,7 @@ import Orgauth.ShowUrl as ShowUrl import Orgauth.UserEdit as UserEdit import Orgauth.UserInterface as UI import Orgauth.UserListing as UserListing +import PrintInvoice as PI import ProjectEdit import ProjectListing import ProjectTime @@ -81,11 +82,13 @@ type Msg | ProjectTimeData String (Result Http.Error TI.ServerResponse) | ProjectViewData String (Result Http.Error PI.ServerResponse) | TProjectViewData String (Result Http.Error TI.ServerResponse) + | PrintInvoiceReplyData (Result Http.Error (Cmd Msg)) | LoadUrl String | InternalUrl Url | SelectedText JD.Value | UrlChanged Url | WindowSize Util.Size + | PrintInvoiceDialogMsg (GD.Msg PI.Msg) | DisplayMessageMsg (GD.Msg DisplayMessage.Msg) | SelectUserDialogMsg (GD.Msg (SS.Msg Data.User)) | SelectRoleDialogMsg (GD.Msg (SS.Msg ( UserId, Data.Role ))) @@ -102,6 +105,7 @@ type Msg | ProjectTimeMsg ProjectTime.Msg | FileLoaded (String -> Msg) F.File | TimeCmd (Time.Posix -> Cmd Msg) Time.Posix + | PrintInvoiceInit Data.PrintInvoiceInternal String String | Noop @@ -117,6 +121,7 @@ type State | ShowMessage ShowMessage.Model Data.LoginData (Maybe State) | PubShowMessage ShowMessage.Model (Maybe State) | LoginShowMessage ShowMessage.Model Data.LoginData Url + | PrintInvoiceDialog PI.GDModel State | SelectUserDialog (SS.GDModel Data.User) State | SelectRoleDialog (SS.GDModel ( UserId, Data.Role )) State | ChangePasswordDialog CP.GDModel State @@ -466,6 +471,9 @@ showMessage msg = ReceiveLocalVal _ -> "ReceiveLocalVal" + PrintInvoiceDialogMsg _ -> + "PrintInvoiceDialogMsg" + SelectUserDialogMsg _ -> "SelectUserDialogMsg" @@ -499,6 +507,12 @@ showMessage msg = ClockTick _ -> "ClockTick" + PrintInvoiceReplyData _ -> + "PrintInvoiceReplyData" + + PrintInvoiceInit _ _ _ -> + "PrintInvoiceInit " + showState : State -> String showState state = @@ -542,6 +556,9 @@ showState state = Wait _ _ -> "Wait" + PrintInvoiceDialog _ _ -> + "PrintInvoiceDialog" + SelectUserDialog _ _ -> "SelectDialog" @@ -617,13 +634,17 @@ viewState size state model = UserSettings em _ _ -> E.map UserSettingsMsg <| UserSettings.view em - DisplayMessage em _ -> + DisplayMessage _ _ -> -- render is at the layout level, not here. E.none Wait innerState _ -> E.map (\_ -> Noop) (viewState size innerState model) + PrintInvoiceDialog _ _ -> + -- render is at the layout level, not here. + E.none + SelectUserDialog _ _ -> -- render is at the layout level, not here. E.none @@ -695,6 +716,9 @@ stateLogin state = Wait wstate _ -> stateLogin wstate + PrintInvoiceDialog _ instate -> + stateLogin instate + SelectUserDialog _ instate -> stateLogin instate @@ -830,6 +854,12 @@ view model = (Just { width = min 600 model.size.width, height = min 200 model.size.height }) cdm + PrintInvoiceDialog cdm _ -> + Html.map PrintInvoiceDialogMsg <| + GD.layout + (Just { width = min 600 model.size.width, height = min 600 model.size.height }) + cdm + _ -> E.layout [ EF.size model.fontsize, E.width E.fill ] <| viewState model.size model.state model ] @@ -1089,6 +1119,14 @@ actualupdate msg model = ( WindowSize s, _ ) -> ( { model | size = s }, Cmd.none ) + ( PrintInvoiceReplyData meh, _ ) -> + case meh of + Ok cmd -> + ( model, cmd ) + + Err e -> + ( displayMessageDialog model <| Util.httpErrorString e, Cmd.none ) + ( ChangePasswordDialogMsg sdmsg, ChangePasswordDialog sdmod instate ) -> case GD.update sdmsg sdmod of GD.Dialog nmod -> @@ -1130,6 +1168,47 @@ actualupdate msg model = ResetPassword.None -> ( { model | state = ResetPassword nst }, Cmd.none ) + ( PrintInvoiceInit pi date duedate, state ) -> + ( { model + | state = + PrintInvoiceDialog + (PI.init pi + date + duedate + Common.buttonStyle + (E.map (\_ -> ()) (viewState model.size model.state model)) + ) + state + } + , Cmd.none + ) + + -- (view { model | state = state } |> E.map (always ()))) }, Cmd.none ) + -- (UserSettings.view numod |> E.map (always ()))) + ( PrintInvoiceDialogMsg sdmsg, PrintInvoiceDialog sdmod instate ) -> + case GD.update sdmsg sdmod of + GD.Dialog nmod -> + ( { model | state = PrintInvoiceDialog nmod instate }, Cmd.none ) + + GD.Ok ( pi, spi ) -> + ( { model | state = instate } + , Cmd.batch + [ Http.post + { url = model.location ++ "/invoice" + , body = Http.jsonBody (Data.encodePrintInvoice pi) + , expect = + Http.expectBytesResponse PrintInvoiceReplyData <| + resolve <| + \bytes -> + Ok <| FD.bytes (pi.id ++ ".pdf") "application/pdf" bytes + } + , sendTIMsg model.location (TI.SaveProjectInvoice spi) + ] + ) + + GD.Cancel -> + ( { model | state = instate }, Cmd.none ) + ( UserSettingsMsg umsg, UserSettings umod login prevstate ) -> let ( numod, c ) = @@ -1722,6 +1801,17 @@ actualupdate msg model = _ -> ( model, Cmd.none ) + TI.SavedProjectInvoice p -> + case state of + ProjectEdit s l -> + ( { model | state = ProjectEdit (ProjectEdit.onSavedProjectInvoice p s) l }, Cmd.none ) + + ProjectTime s l -> + ( { model | state = ProjectTime (ProjectTime.onSavedProjectInvoice p s) l }, Cmd.none ) + + _ -> + ( model, Cmd.none ) + TI.SavedProjectEdit x -> case state of ProjectEdit s l -> @@ -1860,6 +1950,9 @@ actualupdate msg model = ( DisplayMessageMsg GD.Noop, _ ) -> ( model, Cmd.none ) + ( PrintInvoiceDialogMsg GD.Noop, _ ) -> + ( model, Cmd.none ) + ( ProjectListingMsg ms, ProjectListing st login ) -> let ( nm, cmd ) = @@ -1941,7 +2034,7 @@ actualupdate msg model = ( ProjectViewMsg ms, ProjectView st mblogin ) -> handleProjectView model (ProjectView.update ms st model.timezone) mblogin - ( x, y ) -> + ( x, _ ) -> ( unexpectedMsg model x , Cmd.none ) @@ -2071,6 +2164,48 @@ handleProjectTime model ( nm, cmd ) login = ProjectTime.ToClipboard text -> ( { model | state = ProjectTime nm login }, toClipBoard text ) + ProjectTime.PrintInvoice pi -> + ( { model | state = ProjectTime nm login } + , Time.now + |> Task.perform + (\now -> + PrintInvoiceInit pi + (Data.piDate now model.timezone) + (pi.duedays + |> Maybe.map + (\dd -> + Data.piDate + (now + |> Time.posixToMillis + |> (+) (dd * 24 * 60 * 60 * 1000) + |> Time.millisToPosix + ) + model.timezone + ) + |> Maybe.withDefault "" + ) + ) + ) + + +resolve : (body -> Result String a) -> Http.Response body -> Result Http.Error a +resolve toResult response = + case response of + Http.BadUrl_ url -> + Err (Http.BadUrl url) + + Http.Timeout_ -> + Err Http.Timeout + + Http.NetworkError_ -> + Err Http.NetworkError + + Http.BadStatus_ metadata _ -> + Err (Http.BadStatus metadata.statusCode) + + Http.GoodStatus_ _ body -> + Result.mapError Http.BadBody (toResult body) + handleProjectView : Model -> ( ProjectView.Model, ProjectView.Command ) -> Maybe Data.LoginData -> ( Model, Cmd Msg ) handleProjectView model ( nm, cmd ) mblogin = @@ -2359,6 +2494,7 @@ port receiveKeyMsg : (JD.Value -> msg) -> Sub msg port toClipBoard : String -> Cmd msg +keyreceive : Sub Msg keyreceive = receiveKeyMsg <| WindowKeys.receive WkMsg @@ -2366,5 +2502,6 @@ keyreceive = port sendKeyCommand : JE.Value -> Cmd msg +skcommand : WindowKeys.WindowKeyCmd -> Cmd Msg skcommand = WindowKeys.send sendKeyCommand diff --git a/elm/src/PrintInvoice.elm b/elm/src/PrintInvoice.elm new file mode 100644 index 0000000..221d8dc --- /dev/null +++ b/elm/src/PrintInvoice.elm @@ -0,0 +1,237 @@ +module PrintInvoice exposing (GDModel, Model, Msg(..), init, update, view) + +import Data +import Dict exposing (Dict) +import Element as E exposing (Element) +import Element.Background as EBk +import Element.Border as EBd +import Element.Font as EF +import Element.Input as EI +import GenDialog as GD +import Orgauth.Data as Data +import TcCommon as TC +import Util + + +type alias Model = + { date : String + , duedate : String + , sequence : Int + , extraFields : List Data.ExtraField + , printInvoiceInternal : Data.PrintInvoiceInternal + } + + +type Msg + = DateChanged String + | DueDateChanged String + | SequenceChanged String + | NameChanged Int String + | ValueChanged Int String + | AddItem + | RemoveItem Int + | OkClick + | CancelClick + | Noop + + +type alias GDModel = + GD.Model Model Msg ( Data.PrintInvoice, Data.SaveProjectInvoice ) + + +init : Data.PrintInvoiceInternal -> String -> String -> List (E.Attribute Msg) -> Element () -> GDModel +init pi date duedate buttonStyle underLay = + { view = view buttonStyle + , update = update + , model = + { date = date + , duedate = duedate + , sequence = pi.seq + 1 + , extraFields = pi.extraFields + , printInvoiceInternal = pi + } + , underLay = underLay + } + + +view : List (E.Attribute Msg) -> Maybe Util.Size -> Model -> Element Msg +view buttonStyle mbsize model = + E.column + [ E.width (mbsize |> Maybe.map .width |> Maybe.withDefault 500 |> E.px) + , E.height E.shrink + , E.spacing 10 + ] + [ E.el [ EF.size 20, EF.bold, E.centerX ] <| E.text "Print Invoice" + , E.row [] + [ E.text "invoice id: " + , E.el [ EF.bold ] <| E.text (Data.makeInvoiceId model.printInvoiceInternal.idtemplate model.date model.sequence) + ] + , EI.text + [] + { onChange = + DateChanged + , text = model.date + , placeholder = Nothing + , label = + EI.labelLeft + [] + (E.text "date") + } + , EI.text + [] + { onChange = + DueDateChanged + , text = model.duedate + , placeholder = Nothing + , label = + EI.labelLeft + [] + (E.text "due date") + } + , EI.text + [] + { onChange = + SequenceChanged + , text = String.fromInt model.sequence + , placeholder = Nothing + , label = + EI.labelLeft + [] + (E.text "invoice sequence number") + } + , E.column [ E.spacing TC.defaultSpacing, E.padding TC.defaultSpacing, EBd.width 1, E.width E.fill ] + [ E.el [ EF.bold ] <| E.text "extra values" + , E.table [] + { data = List.indexedMap (\i ef -> ( i, ef )) model.extraFields + , columns = + [ { header = E.text "Name" + , width = E.fill + , view = + \( i, ef ) -> + EI.text + [] + { onChange = NameChanged i + , text = ef.n + , placeholder = Nothing + , label = EI.labelHidden "name" + } + } + , { header = E.text "Value" + , width = E.fill + , view = + \( i, ef ) -> + EI.text + [] + { onChange = ValueChanged i + , text = ef.v + , placeholder = Nothing + , label = EI.labelHidden "value" + } + } + , { header = E.text "Delete" + , width = E.shrink + , view = + \( i, _ ) -> + EI.button + buttonStyle + { onPress = Just (RemoveItem i), label = E.text "x" } + } + ] + } + , E.row [] + [ EI.button + buttonStyle + { onPress = Just AddItem, label = E.text "Add Item" } + ] + ] + , E.row [ E.width E.fill, E.spacing 10 ] + [ EI.button buttonStyle + { onPress = Just OkClick, label = E.text "Ok" } + , EI.button + buttonStyle + { onPress = Just CancelClick, label = E.text "Cancel" } + ] + ] + + +update : Msg -> Model -> GD.Transition Model ( Data.PrintInvoice, Data.SaveProjectInvoice ) +update msg model = + case msg of + DateChanged s -> + GD.Dialog { model | date = s } + + DueDateChanged s -> + GD.Dialog { model | duedate = s } + + SequenceChanged s -> + case String.toInt s of + Just i -> + GD.Dialog { model | sequence = i } + + Nothing -> + GD.Dialog model + + NameChanged idx s -> + GD.Dialog + { model + | extraFields = + List.indexedMap + (\i ef -> + if i == idx then + { ef | n = s } + + else + ef + ) + model.extraFields + } + + ValueChanged idx s -> + GD.Dialog + { model + | extraFields = + List.indexedMap + (\i ef -> + if i == idx then + { ef | v = s } + + else + ef + ) + model.extraFields + } + + AddItem -> + GD.Dialog { model | extraFields = List.append model.extraFields [ { n = "", v = "" } ] } + + RemoveItem idx -> + GD.Dialog + { model + | extraFields = + model.extraFields + |> List.indexedMap (\i v -> ( i, v )) + |> List.filterMap + (\( i, v ) -> + if i == idx then + Nothing + + else + Just v + ) + } + + CancelClick -> + GD.Cancel + + OkClick -> + let + mpii = + model.printInvoiceInternal + + mpiis = + { mpii | seq = model.sequence, extraFields = model.extraFields } + in + GD.Ok ( Data.toPi mpiis model.date model.duedate, Data.toSaveProjectInvoice mpiis ) + + Noop -> + GD.Dialog model diff --git a/elm/src/ProjectEdit.elm b/elm/src/ProjectEdit.elm index ee3b603..b89ce75 100644 --- a/elm/src/ProjectEdit.elm +++ b/elm/src/ProjectEdit.elm @@ -2,17 +2,13 @@ module ProjectEdit exposing (..) import Common import Data -import Dialog as D -import Dict exposing (Dict) import Element as E exposing (Element) import Element.Background as EBk import Element.Border as EBd import Element.Font as EF import Element.Input as EI -import Element.Region -import Orgauth.Data as OD exposing (UserId, getUserIdVal, makeUserId) +import Orgauth.Data exposing (UserId, getUserIdVal, makeUserId) import Route -import SelectString import TDict exposing (TDict) import TangoColors as TC import TcCommon as TC @@ -24,7 +20,13 @@ import WindowKeys as WK type Msg = NameChanged String | DescriptionChanged String + | DueDaysChanged String + | InvoiceIdTemplateChanged String + | InvoiceSeqChanged String + | PayerChanged String + | PayeeChanged String | RateChanged String + | GenericTaskChanged String | CurrencyChanged String | SavePress | RevertPress @@ -41,6 +43,13 @@ type alias Model = { id : Maybe Data.ProjectId , name : String , description : String + , dueDays : Maybe Int + , extraFields : List Data.ExtraField + , invoiceIdTemplate : String + , invoiceSeq : Int + , payer : String + , payee : String + , genericTask : String , public : Bool , ratestring : String , currency : String @@ -100,6 +109,13 @@ toSaveProject model = { id = model.id , name = model.name , description = model.description + , dueDays = model.dueDays + , extraFields = model.extraFields + , invoiceIdTemplate = model.invoiceIdTemplate + , invoiceSeq = model.invoiceSeq + , payer = model.payer + , payee = model.payee + , genericTask = model.genericTask , public = model.public , rate = rate , currency = currency @@ -142,6 +158,15 @@ emptyUmDict = TDict.empty getUserIdVal makeUserId +onSavedProjectInvoice : Data.Project -> Model -> Model +onSavedProjectInvoice project model = + let + members = + TDict.values model.members + in + initEdit project members + + onSavedProjectEdit : Data.SavedProjectEdit -> Model -> Model onSavedProjectEdit spe model = let @@ -189,6 +214,12 @@ isDirty model = ((model.id == Just ip.id) && (model.name == ip.name) && (model.description == ip.description) + && (model.dueDays == ip.dueDays) + && (model.extraFields == ip.extraFields) + && (model.invoiceIdTemplate == ip.invoiceIdTemplate) + && (model.invoiceSeq == ip.invoiceSeq) + && (model.payer == ip.payer) + && (model.payee == ip.payee) && (model.public == ip.public) && (model.ratestring == (ip.rate |> Maybe.map String.fromFloat |> Maybe.withDefault "") @@ -213,6 +244,13 @@ initNew ld = { id = Nothing , name = "" , description = "" + , dueDays = Nothing + , extraFields = [] + , invoiceIdTemplate = "example" + , invoiceSeq = 0 + , payer = "" + , payee = "" + , genericTask = "" , public = False , ratestring = "" , currency = "" @@ -235,6 +273,13 @@ initEdit proj members = { id = Just proj.id , name = proj.name , description = proj.description + , dueDays = proj.dueDays + , extraFields = proj.extraFields + , invoiceIdTemplate = proj.invoiceIdTemplate + , invoiceSeq = proj.invoiceSeq + , payer = proj.payer + , payee = proj.payee + , genericTask = proj.genericTask , public = proj.public , ratestring = proj.rate |> Maybe.map String.fromFloat |> Maybe.withDefault "" , currency = proj.currency |> Maybe.withDefault "" @@ -297,7 +342,9 @@ view ld size model = , EBk.color TC.white , E.spacing TC.defaultSpacing ] - [ EI.text + [ E.row [ EF.bold, E.centerX, EF.size 20 ] + [ E.text "All Projects" ] + , EI.text (if isdirty then [ E.focused [ EBd.glow TC.darkYellow 3 ] ] @@ -330,6 +377,29 @@ view ld size model = (E.text "description") , spellcheck = True } + , E.row [ E.spacing 8 ] + [ EI.checkbox [] + { onChange = TogglePublic + , icon = EI.defaultCheckbox + , checked = model.public + , label = EI.labelLeft [] (E.text "public") + } + , case ( model.public, model.id ) of + ( True, Just id ) -> + let + u = + Route.routeUrl <| Route.ProjectViewR (Data.getProjectIdVal id) "team" + in + E.link Common.linkStyle + { url = u + , label = E.text u + } + + _ -> + E.none + ] + , E.row [ EF.bold, E.centerX, EF.size 20 ] + [ E.text "For Distributions" ] , EI.text (if isdirty then [ E.focused [ EBd.glow TC.darkYellow 3 ] ] @@ -362,27 +432,110 @@ view ld size model = [] (E.text "currency") } - , E.row [ E.spacing 8 ] - [ EI.checkbox [] - { onChange = TogglePublic - , icon = EI.defaultCheckbox - , checked = model.public - , label = EI.labelLeft [] (E.text "public") - } - , case ( model.public, model.id ) of - ( True, Just id ) -> - let - u = - Route.routeUrl <| Route.ProjectViewR (Data.getProjectIdVal id) "team" - in - E.link Common.linkStyle - { url = u - , label = E.text u - } + , E.row [ EF.bold, E.centerX, EF.size 20 ] + [ E.text "For Invoices" ] + , EI.text + (if isdirty then + [ E.focused [ EBd.glow TC.darkYellow 3 ] ] - _ -> - E.none + else + [] + ) + { onChange = + InvoiceIdTemplateChanged + , text = model.invoiceIdTemplate + , placeholder = Nothing + , label = + EI.labelLeft + [] + (E.text "invoice id template") + } + , E.row [ E.alignRight ] + [ E.text "Use or , ex: " + , E.el [ EF.italic ] <| E.text "example" ] + , EI.text + (if isdirty then + [ E.focused [ EBd.glow TC.darkYellow 3 ] ] + + else + [] + ) + { onChange = + InvoiceSeqChanged + , text = String.fromInt model.invoiceSeq + , placeholder = Nothing + , label = + EI.labelLeft + [] + (E.text "invoice sequence number") + } + , EI.text + (if isdirty then + [ E.focused [ EBd.glow TC.darkYellow 3 ] ] + + else + [] + ) + { onChange = + GenericTaskChanged + , text = model.genericTask + , placeholder = Nothing + , label = + EI.labelLeft + [] + (E.text "generic task description") + } + , EI.multiline + (if isdirty then + [ E.focused [ EBd.glow TC.darkYellow 3 ] ] + + else + [] + ) + { onChange = + PayerChanged + , text = model.payer + , placeholder = Nothing + , label = + EI.labelLeft + [] + (E.text "payer") + , spellcheck = True + } + , EI.multiline + (if isdirty then + [ E.focused [ EBd.glow TC.darkYellow 3 ] ] + + else + [] + ) + { onChange = + PayeeChanged + , text = model.payee + , placeholder = Nothing + , label = + EI.labelLeft + [] + (E.text "payee") + , spellcheck = True + } + , EI.text + (if isdirty then + [ E.focused [ EBd.glow TC.darkYellow 3 ] ] + + else + [] + ) + { onChange = + DueDaysChanged + , text = model.dueDays |> Maybe.map (\d -> String.fromInt d) |> Maybe.withDefault "" + , placeholder = Nothing + , label = + EI.labelLeft + [] + (E.text "due date (days)") + } ] , E.column [ E.padding 8 @@ -425,6 +578,29 @@ update msg model ld = DescriptionChanged t -> ( { model | description = t }, None ) + DueDaysChanged days -> + ( { model | dueDays = String.toInt days }, None ) + + InvoiceIdTemplateChanged t -> + ( { model | invoiceIdTemplate = t }, None ) + + InvoiceSeqChanged t -> + case String.toInt t of + Just i -> + ( { model | invoiceSeq = i }, None ) + + Nothing -> + ( model, None ) + + PayerChanged t -> + ( { model | payer = t }, None ) + + PayeeChanged t -> + ( { model | payee = t }, None ) + + GenericTaskChanged t -> + ( { model | genericTask = t }, None ) + RateChanged t -> ( { model | ratestring = t }, None ) diff --git a/elm/src/ProjectTime.elm b/elm/src/ProjectTime.elm index 6ddf758..ccb7d36 100644 --- a/elm/src/ProjectTime.elm +++ b/elm/src/ProjectTime.elm @@ -3,7 +3,7 @@ module ProjectTime exposing (..) import Calendar import Common import Csv -import Data +import Data exposing (InvoiceItem, SaveProjectInvoice) import Dict exposing (Dict) import Element as E exposing (Element) import Element.Background as EBk @@ -11,8 +11,7 @@ import Element.Border as EBd import Element.Events as EE import Element.Font as EF import Element.Input as EI -import Html.Attributes -import Orgauth.Data as OD exposing (UserId, getUserIdVal, makeUserId) +import Orgauth.Data exposing (UserId, getUserIdVal) import Paginator as P import Round as R import Set @@ -82,6 +81,7 @@ type Msg | ClearDistribution | CalcDistribution | ToClipboardMsg String + | PrintInvoiceMsg (List InvoiceItem) | OnPaymentChanged UserId String | AddPaymentPress UserId Int Data.PayType | AddPayment UserId Int Data.PayType Int @@ -192,6 +192,7 @@ type Command | ShowError String | SelectMember (List Data.User) | ToClipboard String + | PrintInvoice Data.PrintInvoiceInternal | None @@ -207,6 +208,11 @@ onClockTick time model = } +onSavedProjectInvoice : Data.Project -> Model -> Model +onSavedProjectInvoice project model = + { model | project = project } + + onMemberSelected : UserId -> Model -> Model onMemberSelected user model = case model.focus of @@ -726,7 +732,7 @@ dateTimeWidth = clonkview : Data.LoginData -> Util.Size -> Time.Zone -> Bool -> Model -> List (Element Msg) -clonkview ld size zone isdirty model = +clonkview ld _ zone isdirty model = let ttotes = getTotes model.timeentries @@ -1224,12 +1230,12 @@ clonkview ld size zone isdirty model = [ { header = E.none , width = E.shrink , view = - \( title, entry ) -> E.el headerStyle <| E.text title + \( title, _ ) -> E.el headerStyle <| E.text title } , { header = E.none , width = E.shrink , view = - \( title, entry ) -> E.text entry + \( _, entry ) -> E.text entry } ] } @@ -1271,7 +1277,7 @@ clonkview ld size zone isdirty model = teamview : Data.LoginData -> Util.Size -> Time.Zone -> Bool -> Model -> List (Element Msg) -teamview ld size zone isdirty model = +teamview _ _ zone isdirty model = let ttotes = getTotes model.teamentries @@ -1445,12 +1451,12 @@ teamview ld size zone isdirty model = [ { header = E.none , width = E.shrink , view = - \( title, entry ) -> E.el headerStyle <| E.text title + \( title, _ ) -> E.el headerStyle <| E.text title } , { header = E.none , width = E.shrink , view = - \( title, entry ) -> E.text entry + \( _, entry ) -> E.text entry } ] } @@ -1464,7 +1470,7 @@ type Entry distributionview : Data.LoginData -> Util.Size -> Time.Zone -> Model -> List (Element Msg) -distributionview ld size zone model = +distributionview _ _ zone model = let timetotes = getTes model.timeentries |> Dict.values |> TR.timeTotes @@ -1513,10 +1519,10 @@ distributionview ld size zone model = Dict.foldl (\_ pe c -> c || pe.checked) False model.payentries data = - Dict.union (Dict.map (\i v -> TimeDay v) tmpd) <| + Dict.union (Dict.map (\_ v -> TimeDay v) tmpd) <| Dict.union - (Dict.map (\i v -> PayEntry v) model.payentries) - (Dict.map (\i v -> Allocation v) model.allocations) + (Dict.map (\_ v -> PayEntry v) model.payentries) + (Dict.map (\_ v -> Allocation v) model.allocations) in [ if anychecked then E.row [ E.spacing TC.defaultSpacing ] @@ -1569,7 +1575,7 @@ distributionview ld size zone model = , label = EI.labelHidden "check item" } - Allocation e -> + Allocation _ -> E.none TimeDay _ -> @@ -1580,7 +1586,7 @@ distributionview ld size zone model = , view = \( date, entry ) -> case entry of - TimeDay td -> + TimeDay _ -> date |> Time.millisToPosix |> Calendar.fromPosix @@ -1666,7 +1672,7 @@ distributionview ld size zone model = row ) - Allocation a -> + Allocation _ -> date |> Time.millisToPosix |> Calendar.fromPosix @@ -1779,7 +1785,7 @@ distributionview ld size zone model = |> E.text << millisAsHours - PayEntry epe -> + PayEntry _ -> E.none } , { header = E.el [] <| E.el headerStyle <| E.text "allocations" @@ -2004,10 +2010,40 @@ distributionview ld size zone model = ) |> String.concat ) + + totehours = + dl + |> List.foldl (\( _, hours ) t -> t + (Maybe.withDefault 0 <| String.toFloat hours)) 0 in - EI.button Common.buttonStyle - { onPress = Just <| ToClipboardMsg textdist, label = E.text "⧉" } - |> E.el [ E.centerY ] + E.row [ E.spacing TC.defaultSpacing ] + [ EI.button Common.buttonStyle + { onPress = Just <| ToClipboardMsg textdist, label = E.text "⧉" } + |> E.el [ E.centerY ] + , EI.button + (case model.project.rate of + Just _ -> + Common.buttonStyle + + Nothing -> + Common.disabledButtonStyle + ) + { onPress = + case model.project.rate of + Just rate -> + Just <| + PrintInvoiceMsg + [ { description = model.project.genericTask + , duration = totehours + , rate = rate + } + ] + + Nothing -> + Nothing + , label = E.text "pdf invoice" + } + |> E.el [ E.centerY ] + ] , width = E.shrink , view = \( user, hours ) -> @@ -3822,7 +3858,7 @@ update msg model ld zone = PeToStart -> ( { model | pepaginator = P.onToStart model.pepaginator }, None ) - PeToEnd c -> + PeToEnd _ -> ( { model | pepaginator = P.onToEnd (Dict.size model.payentries) model.pepaginator }, None ) AForward -> @@ -3852,6 +3888,20 @@ update msg model ld zone = ToClipboardMsg text -> ( model, ToClipboard text ) + PrintInvoiceMsg items -> + ( model + , PrintInvoice + { projectid = model.project.id + , seq = model.project.invoiceSeq + , duedays = model.project.dueDays + , idtemplate = model.project.invoiceIdTemplate + , payer = model.project.payer + , payee = model.project.payee + , items = items + , extraFields = model.project.extraFields + } + ) + DonePress -> ( model, Done ) diff --git a/elm/src/TimeclonkInterface.elm b/elm/src/TimeclonkInterface.elm index cbeecc9..3156fa7 100644 --- a/elm/src/TimeclonkInterface.elm +++ b/elm/src/TimeclonkInterface.elm @@ -10,6 +10,7 @@ type SendMsg = GetProjectList UserId | GetProjectEdit Int | SaveProjectEdit Data.SaveProjectEdit + | SaveProjectInvoice Data.SaveProjectInvoice | GetProjectTime Int | SaveProjectTime Data.SaveProjectTime | GetUserTime @@ -21,6 +22,7 @@ type ServerResponse | ProjectList (List Data.ListProject) | ProjectEdit Data.ProjectEdit | SavedProjectEdit Data.SavedProjectEdit + | SavedProjectInvoice Data.Project | AllUsers (List Data.User) | ProjectTime Data.ProjectTime | UserTime (List Data.TimeEntry) @@ -49,6 +51,9 @@ showServerResponse sr = SavedProjectEdit _ -> "SavedProjectEdit" + SavedProjectInvoice _ -> + "SavedProjectInvoice" + AllUsers _ -> "AllMembers" @@ -85,6 +90,12 @@ encodeSendMsg sm = [ ( "what", JE.string "GetUserTime" ) ] + SaveProjectInvoice p -> + JE.object + [ ( "what", JE.string "SaveProjectInvoice" ) + , ( "data", Data.encodeSaveProjectInvoice p ) + ] + SaveProjectEdit p -> JE.object [ ( "what", JE.string "SaveProjectEdit" ) @@ -123,6 +134,9 @@ serverResponseDecoder = "projectlist" -> JD.map ProjectList (JD.at [ "content" ] (JD.list Data.decodeListProject)) + "savedprojectinvoice" -> + JD.map SavedProjectInvoice (JD.at [ "content" ] Data.decodeProject) + "savedprojectedit" -> JD.map SavedProjectEdit (JD.at [ "content" ] Data.decodeSavedProjectEdit) diff --git a/flake.lock b/flake.lock index bb2d27b..22597b0 100644 --- a/flake.lock +++ b/flake.lock @@ -1,12 +1,15 @@ { "nodes": { "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -20,11 +23,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1671096816, - "narHash": "sha256-ezQCsNgmpUHdZANDCILm3RvtO1xH8uujk/+EqNvzIOg=", + "lastModified": 1721727458, + "narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=", "owner": "nmattia", "repo": "naersk", - "rev": "d998160d6a076cfe8f9741e56aeec7e267e3e114", + "rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11", "type": "github" }, "original": { @@ -35,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1671249438, - "narHash": "sha256-5e+CcnbZA3/i2BRXbnzRS52Ly67MUNdZR+Zpbb2C65k=", + "lastModified": 1722926584, + "narHash": "sha256-sLA+lfCxTkXf4o3JVxcfe+w9kAzauGfEnJ9roNbNng8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "067bfc6c90a301572cec7da48f09c447a9a8eae0", + "rev": "d33cf80b603b3d0cddb48f9816cae707a59e2334", "type": "github" }, "original": { @@ -49,11 +52,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1671249438, - "narHash": "sha256-5e+CcnbZA3/i2BRXbnzRS52Ly67MUNdZR+Zpbb2C65k=", + "lastModified": 1722926584, + "narHash": "sha256-sLA+lfCxTkXf4o3JVxcfe+w9kAzauGfEnJ9roNbNng8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "067bfc6c90a301572cec7da48f09c447a9a8eae0", + "rev": "d33cf80b603b3d0cddb48f9816cae707a59e2334", "type": "github" }, "original": { @@ -67,6 +70,21 @@ "naersk": "naersk", "nixpkgs": "nixpkgs_2" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 783381a..e7f2c47 100644 --- a/flake.nix +++ b/flake.nix @@ -41,12 +41,15 @@ rust-stuff = naersk-lib.buildPackage { pname = pname; root = ./.; - buildInputs = with pkgs; [ + nativeBuildInputs = with pkgs; [ cargo rustc + ]; + buildInputs = with pkgs; [ sqlite - pkgconfig - openssl.dev + pkg-config + openssl.dev + typst ]; }; in @@ -79,13 +82,14 @@ # `nix develop` devShell = pkgs.mkShell { nativeBuildInputs = with pkgs; [ + typst cargo cargo-watch rustc rustfmt rust-analyzer sqlite - pkgconfig + pkg-config openssl.dev elm2nix elmPackages.elm @@ -100,6 +104,9 @@ elmPackages.elm-verify-examples elmPackages.elmi-to-json elmPackages.elm-optimize-level-2 + typst + # typst-fmt + # typst-lsp ]; }; } diff --git a/orgauth b/orgauth index 794cb64..927e8c7 160000 --- a/orgauth +++ b/orgauth @@ -1 +1 @@ -Subproject commit 794cb6420bd97b9fc422d70bd8e74264bd1c2e39 +Subproject commit 927e8c775785e34307d9b6cfdf00416d6e6a3f49 diff --git a/server/Cargo.toml b/server/Cargo.toml index 57a0279..106e35e 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -30,3 +30,4 @@ clap = "2.33.2" timer = "0.2.0" chrono = "0.4.26" either = "1.6.1" +typst = "0.11.1" diff --git a/server/benvoice.typ b/server/benvoice.typ new file mode 100644 index 0000000..bdaaf3b --- /dev/null +++ b/server/benvoice.typ @@ -0,0 +1,480 @@ +#let nbh = "‑" + +// Truncate a number to 2 decimal places +// and add trailing zeros if necessary +// E.g. 1.234 -> 1.23, 1.2 -> 1.20 +#let add-zeros = (num) => { + // Can't use trunc and fract due to rounding errors + let frags = str(num).split(".") + let (intp, decp) = if frags.len() == 2 { frags } else { (num, "00") } + str(intp) + "." + (str(decp) + "00").slice(0, 2) + } + +// From https://stackoverflow.com/a/57080936/1850340 +#let verify-iban = (country, iban) => { + let iban-regexes = ( + DE: regex( + "^DE[a-zA-Z0-9]{2}\s?([0-9]{4}\s?){4}([0-9]{2})$" + ), + GB: regex( + "^GB[a-zA-Z0-9]{2}\s?([a-zA-Z]{4}\s?){1}([0-9]{4}\s?){3}([0-9]{2})$" + ), + ) + + if country == none or not country in iban-regexes { + true + } + else { + iban.find(iban-regexes.at(country)) != none + } +} + +#let parse-date = (date-str) => { + let parts = date-str.split("-") + if parts.len() != 3 { + panic( + "Invalid date string: " + date-str + "\n" + + "Expected format: YYYY-MM-DD" + ) + } + datetime( + year: int(parts.at(0)), + month: int(parts.at(1)), + day: int(parts.at(2)), + ) +} + +#let TODO = box( + inset: (x: 0.5em), + outset: (y: 0.2em), + radius: 0.2em, + fill: rgb(255,180,170), +)[ + #text( + size: 0.8em, + weight: 600, + fill: rgb(100,68,64) + )[TODO] +] + +#let horizontalrule = [ + #v(8mm) + #line( + start: (20%,0%), + end: (80%,0%), + stroke: 0.8pt + gray, + ) + #v(8mm) +] + +#let signature-line = line(length: 5cm, stroke: 0.4pt) + +#let endnote(num, contents) = [ + #stack(dir: ltr, spacing: 3pt, super[#num], contents) +] + +#let languages = ( + en: ( + id: "en", + country: "GB", + recipient: "Recipient", + biller: "Biller", + invoice: "Invoice", + cancellation-invoice: "Cancellation Invoice", + cancellation-notice: (id, issuing-date) => [ + As agreed, you will receive a credit note + for the invoice *#id* dated *#issuing-date*. + ], + invoice-id: "Invoice ID", + issuing-date: "Date", + items: "Items", + closing: "Thank you for the good cooperation!", + number: "№", + date: "Date", + due-date: "Due Date", + item: "Item", + hours: "Hours", + rate: "Rate", + total-time: "Total working time", + subtotal: "Subtotal", + discount-of: "Discount of", + tax: "tax of", + reverse-charge: "Reverse Charge", + total: "Total", + due-text: val => + [Please transfer the money onto following bank account due to *#val*:], + owner: "Owner", + iban: "IBAN", + ), + de: ( + id: "de", + country: "DE", + recipient: "Empfänger", + biller: "Aussteller", + invoice: "Rechnung", + cancellation-invoice: "Stornorechnung", + cancellation-notice: (id, issuing-date) => [ + Vereinbarungsgemäß erhalten Sie hiermit eine Gutschrift + zur Rechnung *#id* vom *#issuing-date*. + ], + invoice-id: "Rechnungsnummer", + issuing-date: "Ausstellungsdatum", + items: "Leistungen", + closing: "Vielen Dank für die gute Zusammenarbeit!", + number: "Nr", + date: "Datum", + due-date: "Due-Date", + item: "Beschreibung", + hours: "Menge", + rate: "Preis", + total-time: "Gesamtarbeitszeit", + subtotal: "Zwischensumme", + discount-of: "Rabatt von", + tax: "Umsatzsteuer von", + reverse-charge: "Steuerschuldnerschaft des\nLeistungsempfängers", + total: "Gesamt", + due-text: val => + [Bitte überweise den Betrag bis *#val* auf folgendes Konto:], + owner: "Inhaber", + iban: "IBAN", + ), + ) + +#let invoice( + language: "en", + country: none, + title: none, + banner-image: none, + invoice-id: none, + cancellation-id: none, + issuing-date: none, + due-date: none, + biller: (:), + recipient: (:), + keywords: (), + hourly-rate: none, + styling: (:), // font, font-size, margin (sets defaults below) + items: (), + extraFields: (), + discount: none, + tax: 0.19, + data: none, + doc, +) = { + // Set styling defaults + styling.font = styling.at("font", default: "Liberation Sans") + styling.font-size = styling.at("font-size", default: 11pt) + styling.margin = styling.at("margin", default: ( + top: 20mm, + right: 25mm, + bottom: 20mm, + left: 25mm, + )) + + language = if data != none { + data.at("language", default: language) + } else { language } + + // Translations + let t = if type(language) == str { languages.at(language) } + else if type(language) == dictionary { language } + else { panic("Language must be either a string or a dictionary.") } + + if data != none { + language = data.at("language", default: language) + country = data.at("country", default: t.country) + title = data.at("title", default: title) + banner-image = data.at("banner-image", default: banner-image) + invoice-id = data.at("invoice-id", default: invoice-id) + cancellation-id = data.at("cancellation-id", default: cancellation-id) + issuing-date = data.at("issuing-date", default: issuing-date) + due-date = data.at("due-date", default: due-date) + biller = data.at("biller", default: biller) + recipient = data.at("recipient", default: recipient) + keywords = data.at("keywords", default: keywords) + hourly-rate = data.at("hourly-rate", default: hourly-rate) + styling = data.at("styling", default: styling) + items = data.at("items", default: items) + extraFields = data.at("extraFields", default: extraFields) + discount = data.at("discount", default: discount) + tax = data.at("tax", default: tax) + } + + // Verify inputs + // assert( + // verify-iban(country, biller.iban), + // message: "Invalid IBAN " + biller.iban + " for country " + country + // ) + + let signature = "" + let issuing-date = if issuing-date != none { issuing-date } + else { datetime.today().display("[year]-[month]-[day]") } + + set document( + title: title, + keywords: keywords, + date: parse-date(issuing-date), + ) + set page( + margin: styling.margin, + numbering: none, + ) + set par(justify: true) + set text( + lang: t.id, + font: if styling.font != none { styling.font } else { () }, + size: styling.font-size, + ) + set table(stroke: none) + + // Offset page top margin for banner image + [#pad(top: -20mm, banner-image)] + + align(center)[#block(inset: 2em)[ + #text(weight: "bold", size: 2em)[ + #(if title != none { title } else { + if cancellation-id != none { t.cancellation-invoice } + else { t.invoice } + }) + ] + ]] + + let invoice-id-norm = if invoice-id != none { + if cancellation-id != none { cancellation-id } + else { invoice-id } + } + else { + TODO + // TODO: Reactitaxe after Typst supports hour, minute, and second + // datetime + // .today() + // .display("[year]-[month]-[day]t[hour][minute][second]") + } + + let ddt = if due-date != none { + ( [#t.due-date:], [*#due-date*]) + } else { () } + + align(center, + table( + columns: 2, + align: (right, left), + inset: 4pt, + [#t.invoice-id:], [*#invoice-id-norm*], + [#t.issuing-date:], [*#issuing-date*], + ..ddt, + ) + ) + + v(2em) + + box(height: 10em)[ + #columns(2, gutter: 4em)[ + === #t.recipient + #v(0.5em) + #recipient + // .name \ + // #{if "title" in recipient { [#recipient.title \ ] }} + // #recipient.address.city #recipient.address.postal-code \ + // #recipient.address.street \ + // #{if recipient.tax-id.starts-with("DE"){"USt-IdNr.:"}} + // #recipient.tax-id + + + === #t.biller + #v(0.5em) + #biller + // .name \ + // #{if "title" in biller { [#biller.title \ ] }} + // #biller.address.city #biller.address.postal-code \ + // #biller.address.street \ + // #{if biller.tax-id.starts-with("DE"){"USt-IdNr.:"}} + // #biller.tax-id + ] + ] + + if cancellation-id != none { + (t.cancellation-notice)(invoice-id, issuing-date) + } + + // [== #t.items] + + v(1em) + + let getRowTotal = row => { + row.rate * row.at("hours", default: 1) + } + + let cancel-neg = if cancellation-id != none { -1 } else { 1 } + + table( + columns: (auto, 1fr, auto, auto, auto, auto, auto), + align: (col, row) => right, + inset: 6pt, + table.header( + // TODO: Add after https://github.com/typst/typst/issues/3734 + // align: (right,left,left,center,center,center,center,), + table.hline(stroke: 0.5pt), + [*#t.item*], + [*#t.hours*], + [*#t.rate*\ #text(size: 0.8em)[( \$ )]], + [*#t.total*\ #text(size: 0.8em)[( \$ )]], + table.hline(stroke: 0.5pt), + ), + ..items + .enumerate() + .map(((index, row)) => { + let dur-min = row.at("dur-min", default: 0) + let dur-hour = dur-min / 60 + ( + // row.at("number", default: index + 1), + // row.date, + row.item, + // str(if dur-min == 0 { "" } else { dur-min }), + str(row.at("hours", default: if dur-min == 0 { "1" } else { "" })), + str(add-zeros(cancel-neg * + row.at("rate", default: calc.round(hourly-rate * dur-hour, digits: 2)) + )), + str(add-zeros(cancel-neg * getRowTotal(row))), + ) + }) + .flatten() + .map(str), + table.hline(stroke: 0.5pt), + ) + + let sub-total = items + .map(getRowTotal) + .sum() + + let total-duration = items + .map(row => int(row.at("dur-min", default: 0))) + .sum() + + let discount-value = if discount == none { 0 } + else { + if (discount.type == "fixed") { discount.value } + else if discount.type == "proportionate" { + sub-total * discount.value + } + else { panic(["#discount.type" is no valid discount type]) } + } + let discount-label = if discount == none { 0 } + else { + if (discount.type == "fixed") { str(discount.value) + " \$" } + else if discount.type == "proportionate" { + str(discount.value * 100) + " %" + } + else { panic(["#discount.type" is no valid discount type]) } + } + let has-reverse-charge = false + // { + // biller.tax-id.slice(0, 2) != recipient.tax-id.slice(0, 2) + // } + let taxamt = if has-reverse-charge { 0 } else { sub-total * tax } + let total = sub-total - discount-value + taxamt + + let table-entries = ( + if total-duration != 0 { + ([#t.total-time:], [*#total-duration min*]) + }, + if (discount-value != 0) or (tax != 0) { + ([#t.subtotal:], + [#{add-zeros(cancel-neg * sub-total)} \$]) + }, + if discount-value != 0 { + ( + [#t.discount-of #discount-label + #{if discount.reason != "" { "(" + discount.reason + ")" }}], + [-#add-zeros(cancel-neg * discount-value) \$] + ) + }, + if not has-reverse-charge and (tax != 0) { + ([#t.tax #{tax * 100} %:], + [#{add-zeros(cancel-neg * taxamt)} \$] + ) + }, + if (has-reverse-charge) { + ([#t.tax:], text(0.9em)[#t.reverse-charge]) + }, + ( + [*#t.total*:], + [*#add-zeros(cancel-neg * total) \$*] + ), + ) + .filter(entry => entry != none) + + let grayish = luma(245) + + align(right, + table( + columns: 2, + fill: (col, row) => // if last row + if row == table-entries.len() - 1 { grayish } + else { none }, + stroke: (col, row) => // if last row + if row == table-entries.len() - 1 { (y: 0.5pt, x: 0pt) } + else { none }, + ..table-entries + .flatten(), + ) + ) + + v(1em) + + if cancellation-id == none { + let due-date = if due-date != none { due-date } + else { + (parse-date(issuing-date) + duration(days: 14)) + .display("[year]-[month]-[day]") + } + + // (t.due-text)(due-date) + align(left, + table( + columns: 2, + align: (left, left), + inset: 4pt, + ..extraFields + .map((row) => { + (row.at(0), row.at(1)) + } + ).flatten() + ) + ) + + + v(1em) + align(center)[ + #table( + fill: grayish, + // stroke: 1pt + blue, + // columns: 2, // TODO: Doesn't work for unknown reason + columns: (8em, auto), + inset: (col, row) => + if col == 0 { + if row == 0 { (top: 1.2em, right: 0.6em, bottom: 0.6em) } + else { (top: 0.6em, right: 0.6em, bottom: 1.2em) } + } + else { + if row == 0 { (top: 1.2em, right: 2em, bottom: 0.6em, left: 0.6em) } + else { (top: 0.6em, right: 2em, bottom: 1.2em, left: 0.6em) } + }, + align: (col, row) => (right,left,).at(col), + table.hline(stroke: 0.5pt), + // [#t.owner:], [*#biller.name*], + // [#t.iban:], [*#biller.iban*], + table.hline(stroke: 0.5pt), + ) + ] + v(1em) + + // t.closing + } + else { + v(1em) + align(center, strong(t.closing)) + } + + doc +} diff --git a/server/config.toml b/server/config.toml index 4c8bf6f..d8da4b0 100644 --- a/server/config.toml +++ b/server/config.toml @@ -14,5 +14,5 @@ email_token_expiration_ms = 86400000 reset_token_expiration_ms = 86400000 invite_token_expiration_ms = 86400000 static_path = "./static/" -open_registration = false +open_registration = true non_admin_invite = true diff --git a/server/src/config.rs b/server/src/config.rs index 4addf78..234530d 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -7,5 +7,6 @@ pub struct Config { pub ip: String, pub port: u16, pub static_path: Option, + pub invoice_template: Option, pub orgauth_config: orgauth_data::Config, } diff --git a/server/src/data.rs b/server/src/data.rs index 3f4b9d2..e1b757c 100644 --- a/server/src/data.rs +++ b/server/src/data.rs @@ -1,4 +1,5 @@ use serde_derive::{Deserialize, Serialize}; +// use std::collections::HashMap; use std::fmt; use std::str::FromStr; @@ -22,11 +23,31 @@ pub struct ListProject { pub role: Role, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ExtraField { + pub n: String, + pub v: String, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct SaveProjectInvoice { + pub id: i64, + pub invoice_seq: i64, + pub extra_fields: Vec, +} + #[derive(Deserialize, Debug, Clone)] pub struct SaveProject { pub id: Option, pub name: String, pub description: Option, + pub due_days: Option, + pub extra_fields: Vec, + pub invoice_id_template: String, + pub invoice_seq: i64, + pub payer: String, + pub payee: String, + pub generic_task: String, pub public: bool, pub rate: Option, pub currency: Option, @@ -94,6 +115,13 @@ pub struct Project { pub id: i64, pub name: String, pub description: String, + pub due_days: Option, + pub extra_fields: Vec, + pub invoice_id_template: String, + pub invoice_seq: i64, + pub payer: String, + pub payee: String, + pub generic_task: String, pub public: bool, pub rate: Option, pub currency: Option, @@ -216,3 +244,21 @@ pub struct SaveAllocation { pub allocationdate: i64, pub description: String, } + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PrintInvoice { + pub id: String, + pub payer: String, + pub payee: String, + pub items: Vec, + pub date: String, + pub due_date: Option, + pub extra_fields: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct InvoiceItem { + pub description: String, + pub duration: f64, + pub rate: f64, +} diff --git a/server/src/interfaces.rs b/server/src/interfaces.rs index fdb4b85..bf58974 100644 --- a/server/src/interfaces.rs +++ b/server/src/interfaces.rs @@ -1,5 +1,5 @@ use crate::config::Config; -use crate::data::{Role, SaveProjectEdit, SaveProjectTime}; +use crate::data::{Role, SaveProjectEdit, SaveProjectInvoice, SaveProjectTime}; use crate::messages::{PublicMessage, ServerResponse, UserMessage}; use crate::sqldata; use actix_session::Session; @@ -92,6 +92,30 @@ pub fn timeclonk_interface_loggedin( }) } } + "SaveProjectInvoice" => { + let msgdata = Option::ok_or(msg.data.as_ref(), "malformed json data")?; + let sp: SaveProjectInvoice = serde_json::from_value(msgdata.clone())?; + + let conn = sqldata::connection_open(config.orgauth_config.db.as_path())?; + let allowed = match sqldata::member_role(&conn, uid, sp.id)? { + Some(Role::Admin) => true, + Some(Role::Member) => true, + _ => false, + }; + + if allowed { + let saved = sqldata::save_project_invoice(&conn, uid, sp)?; + Ok(ServerResponse { + what: "savedprojectinvoice".to_string(), + content: serde_json::to_value(saved)?, + }) + } else { + Ok(ServerResponse { + what: "savedprojectinvoice_denied".to_string(), + content: serde_json::Value::Null, + }) + } + } "GetProjectTime" => { let msgdata = Option::ok_or(msg.data.as_ref(), "malformed json data")?; let pid: i64 = serde_json::from_value(msgdata.clone())?; diff --git a/server/src/main.rs b/server/src/main.rs index a00f22e..8bda4f0 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -4,24 +4,35 @@ mod interfaces; mod messages; mod migrations; mod sqldata; +use actix_files::NamedFile; use actix_session::{ config::PersistentSession, storage::CookieSessionStore, Session, SessionMiddleware, }; use actix_web::{ - cookie::{self, Key}, - middleware, web, App, HttpRequest, HttpResponse, HttpServer, Result, + cookie::{ + self, + time::{OffsetDateTime, UtcOffset}, + Key, + }, + error::{ErrorInternalServerError, ErrorUnauthorized}, + http::StatusCode, + middleware, web, App, HttpRequest, HttpResponse, HttpResponseBuilder, HttpServer, Responder, + ResponseError, }; use clap::Arg; use config::Config; +use data::{InvoiceItem, PrintInvoice}; use log::{error, info}; use messages::{PublicMessage, ServerResponse, UserMessage}; use orgauth::data::WhatMessage; use orgauth::endpoints::Callbacks; +use serde::ser::SerializeTuple; use serde_json; -use std::env; use std::error::Error; use std::path::PathBuf; +use std::process::Command; use std::str::FromStr; +use std::{env, fmt::Display}; use timer; use uuid::Uuid; @@ -254,6 +265,7 @@ fn defcon() -> Config { ip: "127.0.0.1".to_string(), port: 8000, static_path: None, + invoice_template: None, orgauth_config: oc, } } @@ -274,6 +286,165 @@ fn load_config() -> Config { } } +async fn invoice( + session: Session, + config: web::Data, + item: web::Json, + _req: HttpRequest, +) -> actix_web::Result { + info!("invoice start {:?}", item.0); + // we logged in? to prevent randos from making invoices + let token = match session.get::("token")? { + None => { + return Err(ErrorUnauthorized(orgauth::error::Error::String( + "not logged in".to_string(), + ))) + } + Some(t) => t, + }; + + let conn = match sqldata::connection_open(config.orgauth_config.db.as_path()) { + Err(e) => return Err(ErrorInternalServerError(e)), + Ok(c) => c, + }; + let _user = match orgauth::dbfun::read_user_by_token_api( + &conn, + token, + config.orgauth_config.login_token_expiration_ms, + config.orgauth_config.regen_login_tokens, + ) { + Err(e) => { + return Err(ErrorUnauthorized(e)); + } + Ok(u) => u, + }; + + let path = run_invoice(item.0).map_err(|e| ErrorInternalServerError(e.to_string()))?; // .map_err(|e| actix_web::Error::fmt(, ) + + info!("invoice here {}", path.display()); + Ok(NamedFile::open(path)?) +} + +pub fn invoice_str(item: &InvoiceItem) -> String { + format!( + " + ( + item: \"{}\", + dur-min: 0, + hours: {}, + rate: {}, + ), + ", + item.description, item.duration, item.rate + ) +} + +pub fn run_invoice(print_invoice: PrintInvoice) -> Result { + let items = print_invoice + .items + .iter() + .map(|item| invoice_str(item)) + .collect::>() + .concat(); + + let eelines = print_invoice.payee.split('\n').count(); + let erlines = print_invoice.payer.split('\n').count(); + + let (payee, payer) = match eelines.cmp(&erlines) { + std::cmp::Ordering::Less => ( + format!( + "{}{}", + print_invoice.payee, + "\n".to_string().repeat(erlines - eelines) + ), + print_invoice.payer, + ), + std::cmp::Ordering::Equal => (print_invoice.payee, print_invoice.payer), + std::cmp::Ordering::Greater => ( + print_invoice.payee, + format!( + "{}{}", + print_invoice.payer, + "\n".to_string().repeat(eelines - erlines) + ), + ), + }; + + let typ = format!( + " +#import \"./benvoice.typ\": * + + +#let biller = \"{}\" +#let recipient = \"{}\" + + +#let table-data = ( {} ) + +#show: invoice.with( + language: \"en\", + banner-image: none, + invoice-id: \"{}\", + // Set this to create a cancellation invoice + // cancellation-id: \"2024-03-24t210835\", + issuing-date: \"{}\", + due-date: {}, + extraFields: {}, + biller: biller, + hourly-rate: 100, + recipient: recipient, + tax: 0, + items: table-data, + styling: ( font: none ), // Explicitly use Typst's default font +)", + payee, + payer, + items, + print_invoice.id, + print_invoice.date, + print_invoice + .due_date + .map(|dd| format!("\"{}\"", dd)) + .unwrap_or("none".to_string()), + format!( + "( {} )", + print_invoice + .extra_fields + .iter() + .map(|ef| -> String { format!("(\"{}\", \"{}\"), ", ef.n, ef.v) }) + .collect::>() + .join("") + ), + ); + + orgauth::util::write_string("wat.typ", typ.as_str())?; + + // return Ok("./invoice-maker/meh/en.pdf".into()); + let mut child = Command::new("typst") + .arg("compile") + .arg("./wat.typ") + .spawn() + .expect("typst failed to execute"); + + match child.wait() { + Ok(exit_code) => { + if exit_code.success() { + // add file to result. + Ok("./wat.pdf".into()) + } else { + Err(orgauth::error::Error::String(format!( + "typst err {:?}", + exit_code + ))) + } + } + Err(e) => Err(orgauth::error::Error::String(format!( + "invoice err {:?}", + e + ))), + } +} + fn main() { match err_main() { Err(e) => error!("error: {:?}", e), @@ -372,6 +543,7 @@ async fn err_main() -> Result<(), Box> { .service(web::resource("/admin").route(web::post().to(admin))) .service(web::resource(r"/register/{uid}/{key}").route(web::get().to(register))) .service(web::resource(r"/newemail/{uid}/{token}").route(web::get().to(new_email))) + .service(web::resource(r"/invoice").route(web::post().to(invoice))) .service(actix_files::Files::new("/static/", staticpath)) .service(web::resource("/{tail:.*}").route(web::get().to(mainpage))) }) diff --git a/server/src/migrations.rs b/server/src/migrations.rs index 855b017..61c9bdc 100644 --- a/server/src/migrations.rs +++ b/server/src/migrations.rs @@ -968,3 +968,74 @@ pub fn udpate11(dbfile: &Path) -> Result<(), orgauth::error::Error> { Ok(()) } + +pub fn udpate12(dbfile: &Path) -> Result<(), orgauth::error::Error> { + // db connection without foreign key checking. + let conn = Connection::open(dbfile)?; + let mut m1 = Migration::new(); + + // add 'rate' to timeentry. + m1.create_table("projecttemp", |t| { + t.add_column( + "id", + types::integer() + .primary(true) + .increments(true) + .nullable(false), + ); + t.add_column("name", types::text().nullable(false)); + t.add_column("description", types::text().nullable(false)); + t.add_column("public", types::boolean().nullable(false)); + t.add_column("rate", types::float().nullable(true)); + t.add_column("currency", types::text().nullable(true)); + t.add_column("createdate", types::integer().nullable(false)); + t.add_column("changeddate", types::integer().nullable(false)); + }); + + conn.execute_batch(m1.make::().as_str())?; + + // copy everything from current table.. + conn.execute( + "insert into projecttemp ( id, name, description, public, rate, currency, createdate, changeddate) + select id, name, description, public, rate, currency, createdate, changeddate from project ", + params![], + )?; + + let mut m2 = Migration::new(); + m2.drop_table("project"); + // timeclonk specific tables. + m2.create_table("project", |t| { + t.add_column( + "id", + types::integer() + .primary(true) + .increments(true) + .nullable(false), + ); + t.add_column("name", types::text().nullable(false)); + t.add_column("description", types::text().nullable(false)); + t.add_column("public", types::boolean().nullable(false)); + t.add_column("rate", types::float().nullable(true)); + t.add_column("currency", types::text().nullable(true)); + t.add_column("due_days", types::integer().nullable(true)); + t.add_column("extra_fields", types::text().nullable(true)); + t.add_column("invoice_id_template", types::text().nullable(false)); + t.add_column("invoice_seq", types::integer().nullable(false)); + t.add_column("payer", types::text().nullable(false)); + t.add_column("payee", types::text().nullable(false)); + t.add_column("generic_task", types::text().nullable(false)); + t.add_column("createdate", types::integer().nullable(false)); + t.add_column("changeddate", types::integer().nullable(false)); + }); + + conn.execute_batch(m2.make::().as_str())?; + + // copy everything from current table.. + conn.execute( + "insert into project (id, name, description, public, rate, currency, invoice_id_template ,invoice_seq, payer, payee, generic_task, createdate, changeddate) + select id, name, description, public, rate, currency, '', 0, '', '', '', createdate, changeddate from projecttemp ", + params![], + )?; + + Ok(()) +} diff --git a/server/src/sqldata.rs b/server/src/sqldata.rs index 2d0040b..0173abb 100644 --- a/server/src/sqldata.rs +++ b/server/src/sqldata.rs @@ -1,7 +1,7 @@ use crate::data::{ Allocation, ListProject, PayEntry, PayType, Project, ProjectEdit, ProjectMember, ProjectTime, - Role, SaveAllocation, SavePayEntry, SaveProject, SaveProjectEdit, SaveProjectTime, SaveTimeEntry, - SavedProject, SavedProjectEdit, TimeEntry, User, UserInviteData, + Role, SaveAllocation, SavePayEntry, SaveProject, SaveProjectEdit, SaveProjectInvoice, + SaveProjectTime, SaveTimeEntry, SavedProject, SavedProjectEdit, TimeEntry, User, UserInviteData, }; use crate::migrations as tm; use barrel::backend::Sqlite; @@ -9,6 +9,7 @@ use log::info; use orgauth::data::RegistrationData; use orgauth::util::now; use rusqlite::{params, Connection}; +use std::collections::HashMap; use std::path::Path; use std::str::FromStr; use std::time::Duration; @@ -185,6 +186,11 @@ pub fn dbinit( tm::udpate11(&dbfile)?; set_single_value(&conn, "migration_level", "11")?; } + if nlevel < 12 { + info!("udpate12"); + tm::udpate12(&dbfile)?; + set_single_value(&conn, "migration_level", "12")?; + } info!("db up to date."); @@ -308,6 +314,30 @@ pub fn save_project_edit( }) } +pub fn save_project_invoice( + conn: &Connection, + user: i64, + project: SaveProjectInvoice, +) -> Result { + let now = orgauth::util::now()?; + conn.execute( + "update project set invoice_seq = ?1, + extra_fields = ?2, + changeddate = ?3 + where id = ?4", + params![ + project.invoice_seq, + serde_json::to_value(project.extra_fields)?.to_string(), + now, + project.id + ], + )?; + + let proj = read_project(conn, project.id)?; + + Ok(proj) +} + pub fn save_project( conn: &Connection, user: i64, @@ -318,9 +348,36 @@ pub fn save_project( let proj = match project.id { Some(id) => { conn.execute( - "update project set name = ?1, description = ?2, public = ?3, rate = ?4, currency = ?5, changeddate = ?6 - where id = ?7", - params![project.name, project.description, project.public, project.rate, project.currency, now, id], + "update project set name = ?1, + description = ?2, + due_days = ?3, + extra_fields = ?4, + invoice_id_template = ?5, + invoice_seq = ?6, + payer = ?7, + payee = ?8, + generic_task = ?9, + public = ?10, + rate = ?11, + currency = ?12, + changeddate = ?13 + where id = ?14", + params![ + project.name, + project.description, + project.due_days, + serde_json::to_value(project.extra_fields)?.to_string(), + project.invoice_id_template, + project.invoice_seq, + project.payer, + project.payee, + project.generic_task, + project.public, + project.rate, + project.currency, + now, + id + ], )?; SavedProject { id: id, @@ -329,11 +386,18 @@ pub fn save_project( } None => { conn.execute( - "insert into project (name, description, public, rate, currency, createdate, changeddate) - values (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + "insert into project (name, description, due_days, extra_fields, invoice_id_template, invoice_seq, payer, payee, generic_task, public, rate, currency, createdate, changeddate) + values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)", params![ project.name, project.description, + project.due_days, + serde_json::to_value(project.extra_fields)?.to_string(), + project.invoice_id_template, + project.invoice_seq, + project.payer, + project.payee, + project.generic_task, project.public, project.rate, project.currency, @@ -358,7 +422,21 @@ pub fn save_project( pub fn read_project(conn: &Connection, projectid: i64) -> Result { let mut pstmt = conn.prepare( - "select project.id, project.name, project.description, project.public, project.rate, project.currency, project.createdate, project.changeddate + "select project.id, + project.name, + project.description, + project.due_days, + project.extra_fields, + project.invoice_id_template, + project.invoice_seq, + project.payer, + project.payee, + project.generic_task, + project.public, + project.rate, + project.currency, + project.createdate, + project.changeddate from project, projectmember where project.id = ?1", )?; @@ -367,11 +445,24 @@ pub fn read_project(conn: &Connection, projectid: i64) -> Result>(4)? + .unwrap_or("{}".to_string()) + .as_str(), + ) + .unwrap_or(Vec::new()), + invoice_id_template: row.get(5)?, + invoice_seq: row.get(6)?, + payer: row.get(7)?, + payee: row.get(8)?, + generic_task: row.get(9)?, + public: row.get(10)?, + rate: row.get(11)?, + currency: row.get(12)?, + createdate: row.get(13)?, + changeddate: row.get(14)?, }) })?); r