Merge branch 'schedule-events' into 'master'
Schedule recipes as calendar events Closes #6 See merge request nextcloud/cooking-schedule!1
This commit is contained in:
commit
fd55dba23f
|
@ -1,6 +1,8 @@
|
||||||
|
# Custom Dictionary Words
|
||||||
# SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
# SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
# SPDX-License-Identifier: CC0-1.0
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
# Custom Dictionary Words
|
cobertura
|
||||||
|
mkdir
|
||||||
montecristosoftware
|
montecristosoftware
|
||||||
reqwest
|
reqwest
|
||||||
webbrowser
|
webbrowser
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
# SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
# SPDX-License-Identifier: CC0-1.0
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
.~*#
|
||||||
/target
|
/target
|
||||||
|
|
|
@ -26,10 +26,10 @@ stages:
|
||||||
CARGO_COMMON_ARGS: --workspace --no-default-features
|
CARGO_COMMON_ARGS: --workspace --no-default-features
|
||||||
script:
|
script:
|
||||||
- mkdir -p .git/hooks # for cargo-husky
|
- mkdir -p .git/hooks # for cargo-husky
|
||||||
- cargo tarpaulin ${CARGO_COMMON_ARGS} --locked --verbose -o Xml
|
- cargo tarpaulin ${CARGO_COMMON_ARGS} --locked -o Xml
|
||||||
- cargo test ${CARGO_COMMON_ARGS} -- -Z unstable-options --format json | tee test-results.json
|
- cargo test ${CARGO_COMMON_ARGS} -- -Z unstable-options --format json | tee test-results.json
|
||||||
- cargo2junit < test-results.json > junit.xml
|
- cargo2junit < test-results.json > junit.xml
|
||||||
- cargo bench ${CARGO_COMMON_ARGS}
|
# - cargo bench ${CARGO_COMMON_ARGS} # DISABLED UNTIL WE HAVE BENCH TESTS
|
||||||
|
|
||||||
docker:build:
|
docker:build:
|
||||||
stage: build
|
stage: build
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug executable 'cook'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--bin=cook",
|
||||||
|
"--package=cooking-schedule"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "cook",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": ["schedule-csv", "Cucina", "examples/example-schedule.csv"],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in executable 'cook'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--bin=cook",
|
||||||
|
"--package=cooking-schedule"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "cook",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -2,6 +2,21 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.58"
|
version = "1.0.58"
|
||||||
|
@ -37,6 +52,18 @@ version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.10.0"
|
version = "3.10.0"
|
||||||
|
@ -67,6 +94,26 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"time",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chunked_transfer"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ci_info"
|
name = "ci_info"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
|
@ -117,14 +164,23 @@ version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"csv",
|
||||||
"directories",
|
"directories",
|
||||||
|
"futures",
|
||||||
|
"iana-time-zone",
|
||||||
|
"ics",
|
||||||
|
"minicaldav",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rusty-hook",
|
"rusty-hook",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"speedate",
|
||||||
|
"strum_macros",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
"ureq",
|
||||||
"webbrowser",
|
"webbrowser",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -144,6 +200,37 @@ version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "csv"
|
||||||
|
version = "1.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
|
||||||
|
dependencies = [
|
||||||
|
"bstr",
|
||||||
|
"csv-core",
|
||||||
|
"itoa 0.4.8",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "csv-core"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.13.4"
|
version = "0.13.4"
|
||||||
|
@ -227,6 +314,16 @@ dependencies = [
|
||||||
"instant",
|
"instant",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
@ -264,6 +361,21 @@ version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3"
|
checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
|
@ -271,6 +383,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -279,12 +392,34 @@ version = "0.3.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
|
@ -303,8 +438,11 @@ version = "0.3.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
@ -329,7 +467,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -357,6 +495,12 @@ version = "0.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3"
|
checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
|
@ -374,7 +518,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
"itoa",
|
"itoa 1.0.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -415,7 +559,7 @@ dependencies = [
|
||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
"httparse",
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa",
|
"itoa 1.0.2",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -437,6 +581,25 @@ dependencies = [
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00c0d80ad9ca8d30ca648bf6cb1e3e3326d75071b76dbe143dd4a9cedcd58975"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ics"
|
||||||
|
version = "0.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b891481ef6353e3b97118d4650469e379a39e4373a66908c12f99763182826b1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ident_case"
|
name = "ident_case"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -479,6 +642,12 @@ version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
|
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -553,6 +722,28 @@ version = "0.3.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minicaldav"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb263a7d12c40d5f200dda93b3665b9ae714d4fe64a6467938c92d974a579edb"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"log",
|
||||||
|
"ureq",
|
||||||
|
"url",
|
||||||
|
"xmltree",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
@ -561,7 +752,7 @@ checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -645,6 +836,25 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0"
|
checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
|
@ -805,6 +1015,12 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
@ -851,6 +1067,39 @@ dependencies = [
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.16.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"spin",
|
||||||
|
"untrusted",
|
||||||
|
"web-sys",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.20.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"ring",
|
||||||
|
"sct",
|
||||||
|
"webpki",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24c8ad4f0c00e1eb5bc7614d236a7f1300e3dbd76b68cac8e06fb00b015ad8d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusty-hook"
|
name = "rusty-hook"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
|
@ -888,6 +1137,16 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sct"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
|
@ -937,7 +1196,7 @@ version = "1.0.82"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
|
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa 1.0.2",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -949,7 +1208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"itoa",
|
"itoa 1.0.2",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -970,12 +1229,50 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "speedate"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "519ab0d5d5dc6d050a2327ea508d20b460dba1e3a76f1933c56b7c4da2b5c620"
|
||||||
|
dependencies = [
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.98"
|
version = "1.0.98"
|
||||||
|
@ -1036,6 +1333,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
@ -1172,6 +1480,30 @@ version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ureq"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"chunked_transfer",
|
||||||
|
"encoding_rs",
|
||||||
|
"flate2",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"rustls",
|
||||||
|
"url",
|
||||||
|
"webpki",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.2.2"
|
version = "2.2.2"
|
||||||
|
@ -1211,6 +1543,12 @@ dependencies = [
|
||||||
"try-lock",
|
"try-lock",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
@ -1307,6 +1645,25 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf"
|
||||||
|
dependencies = [
|
||||||
|
"webpki",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "widestring"
|
name = "widestring"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -1395,3 +1752,18 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xml-rs"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xmltree"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb"
|
||||||
|
dependencies = [
|
||||||
|
"xml-rs",
|
||||||
|
]
|
||||||
|
|
28
Cargo.toml
28
Cargo.toml
|
@ -22,6 +22,16 @@ version = "1.0"
|
||||||
[dependencies.base64]
|
[dependencies.base64]
|
||||||
version = "0.13"
|
version = "0.13"
|
||||||
|
|
||||||
|
[dependencies.chrono]
|
||||||
|
version = "0.4"
|
||||||
|
features = ["serde"]
|
||||||
|
|
||||||
|
[dependencies.iana-time-zone]
|
||||||
|
version = "0.1"
|
||||||
|
|
||||||
|
[dependencies.csv]
|
||||||
|
version = "1.1"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "3.2"
|
version = "3.2"
|
||||||
features = ["cargo"]
|
features = ["cargo"]
|
||||||
|
@ -29,6 +39,15 @@ features = ["cargo"]
|
||||||
[dependencies.directories]
|
[dependencies.directories]
|
||||||
version = "4.0"
|
version = "4.0"
|
||||||
|
|
||||||
|
[dependencies.futures]
|
||||||
|
version = "0.3"
|
||||||
|
|
||||||
|
[dependencies.ics]
|
||||||
|
version = "0.5"
|
||||||
|
|
||||||
|
[dependencies.minicaldav]
|
||||||
|
version = "0.2"
|
||||||
|
|
||||||
[dependencies.reqwest]
|
[dependencies.reqwest]
|
||||||
version = "0.11"
|
version = "0.11"
|
||||||
features = ["json", "blocking"]
|
features = ["json", "blocking"]
|
||||||
|
@ -40,6 +59,12 @@ features = ["derive"]
|
||||||
[dependencies.serde_json]
|
[dependencies.serde_json]
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
|
||||||
|
[dependencies.speedate]
|
||||||
|
version = "0.6"
|
||||||
|
|
||||||
|
[dependencies.strum_macros]
|
||||||
|
version = "0.24"
|
||||||
|
|
||||||
[dependencies.toml]
|
[dependencies.toml]
|
||||||
version = "0.5"
|
version = "0.5"
|
||||||
|
|
||||||
|
@ -47,5 +72,8 @@ version = "0.5"
|
||||||
version = "1"
|
version = "1"
|
||||||
features = ["rt-multi-thread", "net", "macros"]
|
features = ["rt-multi-thread", "net", "macros"]
|
||||||
|
|
||||||
|
[dependencies.ureq]
|
||||||
|
version = "2.5"
|
||||||
|
|
||||||
[dependencies.webbrowser]
|
[dependencies.webbrowser]
|
||||||
version = "0.7"
|
version = "0.7"
|
||||||
|
|
|
@ -0,0 +1,366 @@
|
||||||
|
"day","wday","lunch","dinner"
|
||||||
|
2022-01-01,"sabato",,
|
||||||
|
2022-01-02,"domenica",,
|
||||||
|
2022-01-03,"lunedì",,
|
||||||
|
2022-01-04,"martedì",,
|
||||||
|
2022-01-05,"mercoledì",,
|
||||||
|
2022-01-06,"giovedì",,
|
||||||
|
2022-01-07,"venerdì",,
|
||||||
|
2022-01-08,"sabato",,
|
||||||
|
2022-01-09,"domenica",,
|
||||||
|
2022-01-10,"lunedì",,
|
||||||
|
2022-01-11,"martedì",,
|
||||||
|
2022-01-12,"mercoledì",,
|
||||||
|
2022-01-13,"giovedì",,
|
||||||
|
2022-01-14,"venerdì",,
|
||||||
|
2022-01-15,"sabato",,
|
||||||
|
2022-01-16,"domenica",,
|
||||||
|
2022-01-17,"lunedì",,
|
||||||
|
2022-01-18,"martedì",,
|
||||||
|
2022-01-19,"mercoledì",,
|
||||||
|
2022-01-20,"giovedì",,
|
||||||
|
2022-01-21,"venerdì",,
|
||||||
|
2022-01-22,"sabato",,
|
||||||
|
2022-01-23,"domenica",,
|
||||||
|
2022-01-24,"lunedì",,
|
||||||
|
2022-01-25,"martedì",,
|
||||||
|
2022-01-26,"mercoledì",,
|
||||||
|
2022-01-27,"giovedì",,
|
||||||
|
2022-01-28,"venerdì",,
|
||||||
|
2022-01-29,"sabato",,
|
||||||
|
2022-01-30,"domenica",,
|
||||||
|
2022-01-31,"lunedì",,
|
||||||
|
2022-02-01,"martedì",,
|
||||||
|
2022-02-02,"mercoledì",,
|
||||||
|
2022-02-03,"giovedì",,
|
||||||
|
2022-02-04,"venerdì",,
|
||||||
|
2022-02-05,"sabato",,
|
||||||
|
2022-02-06,"domenica",,
|
||||||
|
2022-02-07,"lunedì",,
|
||||||
|
2022-02-08,"martedì",,
|
||||||
|
2022-02-09,"mercoledì",,
|
||||||
|
2022-02-10,"giovedì",,
|
||||||
|
2022-02-11,"venerdì",,
|
||||||
|
2022-02-12,"sabato",,
|
||||||
|
2022-02-13,"domenica",,
|
||||||
|
2022-02-14,"lunedì",,
|
||||||
|
2022-02-15,"martedì",,
|
||||||
|
2022-02-16,"mercoledì",,
|
||||||
|
2022-02-17,"giovedì",,
|
||||||
|
2022-02-18,"venerdì",,
|
||||||
|
2022-02-19,"sabato",,
|
||||||
|
2022-02-20,"domenica",,
|
||||||
|
2022-02-21,"lunedì",,
|
||||||
|
2022-02-22,"martedì",,
|
||||||
|
2022-02-23,"mercoledì",,
|
||||||
|
2022-02-24,"giovedì",,
|
||||||
|
2022-02-25,"venerdì",,
|
||||||
|
2022-02-26,"sabato",,
|
||||||
|
2022-02-27,"domenica",,
|
||||||
|
2022-02-28,"lunedì",,
|
||||||
|
2022-03-01,"martedì",,
|
||||||
|
2022-03-02,"mercoledì",,
|
||||||
|
2022-03-03,"giovedì",,
|
||||||
|
2022-03-04,"venerdì",,
|
||||||
|
2022-03-05,"sabato",,
|
||||||
|
2022-03-06,"domenica",,
|
||||||
|
2022-03-07,"lunedì",,
|
||||||
|
2022-03-08,"martedì",,
|
||||||
|
2022-03-09,"mercoledì",,
|
||||||
|
2022-03-10,"giovedì",,
|
||||||
|
2022-03-11,"venerdì",,
|
||||||
|
2022-03-12,"sabato",,
|
||||||
|
2022-03-13,"domenica",,
|
||||||
|
2022-03-14,"lunedì",,
|
||||||
|
2022-03-15,"martedì",,
|
||||||
|
2022-03-16,"mercoledì",,
|
||||||
|
2022-03-17,"giovedì",,
|
||||||
|
2022-03-18,"venerdì",,
|
||||||
|
2022-03-19,"sabato",,
|
||||||
|
2022-03-20,"domenica",,
|
||||||
|
2022-03-21,"lunedì",,
|
||||||
|
2022-03-22,"martedì",,
|
||||||
|
2022-03-23,"mercoledì",,
|
||||||
|
2022-03-24,"giovedì",,
|
||||||
|
2022-03-25,"venerdì",,
|
||||||
|
2022-03-26,"sabato",,
|
||||||
|
2022-03-27,"domenica",,
|
||||||
|
2022-03-28,"lunedì",,
|
||||||
|
2022-03-29,"martedì",,
|
||||||
|
2022-03-30,"mercoledì",,
|
||||||
|
2022-03-31,"giovedì",,
|
||||||
|
2022-04-01,"venerdì",,
|
||||||
|
2022-04-02,"sabato",,
|
||||||
|
2022-04-03,"domenica",,
|
||||||
|
2022-04-04,"lunedì",,
|
||||||
|
2022-04-05,"martedì",,
|
||||||
|
2022-04-06,"mercoledì",,
|
||||||
|
2022-04-07,"giovedì",,
|
||||||
|
2022-04-08,"venerdì",,
|
||||||
|
2022-04-09,"sabato",,
|
||||||
|
2022-04-10,"domenica",,
|
||||||
|
2022-04-11,"lunedì",,
|
||||||
|
2022-04-12,"martedì",,
|
||||||
|
2022-04-13,"mercoledì",,
|
||||||
|
2022-04-14,"giovedì",,
|
||||||
|
2022-04-15,"venerdì",,
|
||||||
|
2022-04-16,"sabato",,
|
||||||
|
2022-04-17,"domenica",,
|
||||||
|
2022-04-18,"lunedì",,
|
||||||
|
2022-04-19,"martedì",,
|
||||||
|
2022-04-20,"mercoledì",,
|
||||||
|
2022-04-21,"giovedì",,
|
||||||
|
2022-04-22,"venerdì",,
|
||||||
|
2022-04-23,"sabato",,
|
||||||
|
2022-04-24,"domenica",,
|
||||||
|
2022-04-25,"lunedì",,
|
||||||
|
2022-04-26,"martedì",,
|
||||||
|
2022-04-27,"mercoledì",,
|
||||||
|
2022-04-28,"giovedì",,
|
||||||
|
2022-04-29,"venerdì",,
|
||||||
|
2022-04-30,"sabato",,
|
||||||
|
2022-05-01,"domenica",,
|
||||||
|
2022-05-02,"lunedì",,
|
||||||
|
2022-05-03,"martedì",,
|
||||||
|
2022-05-04,"mercoledì",,
|
||||||
|
2022-05-05,"giovedì",,
|
||||||
|
2022-05-06,"venerdì",,
|
||||||
|
2022-05-07,"sabato",,
|
||||||
|
2022-05-08,"domenica",,
|
||||||
|
2022-05-09,"lunedì",,
|
||||||
|
2022-05-10,"martedì",,
|
||||||
|
2022-05-11,"mercoledì",,
|
||||||
|
2022-05-12,"giovedì",,
|
||||||
|
2022-05-13,"venerdì",,
|
||||||
|
2022-05-14,"sabato",,
|
||||||
|
2022-05-15,"domenica",,
|
||||||
|
2022-05-16,"lunedì",,
|
||||||
|
2022-05-17,"martedì",,
|
||||||
|
2022-05-18,"mercoledì",,
|
||||||
|
2022-05-19,"giovedì",,
|
||||||
|
2022-05-20,"venerdì",,
|
||||||
|
2022-05-21,"sabato",,
|
||||||
|
2022-05-22,"domenica",,
|
||||||
|
2022-05-23,"lunedì",,
|
||||||
|
2022-05-24,"martedì",,
|
||||||
|
2022-05-25,"mercoledì",,
|
||||||
|
2022-05-26,"giovedì",,
|
||||||
|
2022-05-27,"venerdì",,
|
||||||
|
2022-05-28,"sabato",,
|
||||||
|
2022-05-29,"domenica",,
|
||||||
|
2022-05-30,"lunedì",,
|
||||||
|
2022-05-31,"martedì",,
|
||||||
|
2022-06-01,"mercoledì",,
|
||||||
|
2022-06-02,"giovedì",,
|
||||||
|
2022-06-03,"venerdì",,
|
||||||
|
2022-06-04,"sabato",,
|
||||||
|
2022-06-05,"domenica",,
|
||||||
|
2022-06-06,"lunedì",,
|
||||||
|
2022-06-07,"martedì",,"https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html"
|
||||||
|
2022-06-08,"mercoledì",,
|
||||||
|
2022-06-09,"giovedì",,
|
||||||
|
2022-06-10,"venerdì",,
|
||||||
|
2022-06-11,"sabato",,
|
||||||
|
2022-06-12,"domenica",,
|
||||||
|
2022-06-13,"lunedì",,
|
||||||
|
2022-06-14,"martedì",,
|
||||||
|
2022-06-15,"mercoledì",,
|
||||||
|
2022-06-16,"giovedì",,
|
||||||
|
2022-06-17,"venerdì",,"https://ricette.giallozafferano.it/Salmorejo.html"
|
||||||
|
2022-06-18,"sabato",,
|
||||||
|
2022-06-19,"domenica",,
|
||||||
|
2022-06-20,"lunedì",,
|
||||||
|
2022-06-21,"martedì",,
|
||||||
|
2022-06-22,"mercoledì",,"https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html"
|
||||||
|
2022-06-23,"giovedì",,
|
||||||
|
2022-06-24,"venerdì",,
|
||||||
|
2022-06-25,"sabato",,
|
||||||
|
2022-06-26,"domenica",,
|
||||||
|
2022-06-27,"lunedì",,
|
||||||
|
2022-06-28,"martedì",,
|
||||||
|
2022-06-29,"mercoledì",,
|
||||||
|
2022-06-30,"giovedì",,
|
||||||
|
2022-07-01,"venerdì",,
|
||||||
|
2022-07-02,"sabato",,
|
||||||
|
2022-07-03,"domenica",,
|
||||||
|
2022-07-04,"lunedì",,
|
||||||
|
2022-07-05,"martedì",,"https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html"
|
||||||
|
2022-07-06,"mercoledì",,
|
||||||
|
2022-07-07,"giovedì",,
|
||||||
|
2022-07-08,"venerdì",,
|
||||||
|
2022-07-09,"sabato",,
|
||||||
|
2022-07-10,"domenica",,
|
||||||
|
2022-07-11,"lunedì",,
|
||||||
|
2022-07-12,"martedì",,
|
||||||
|
2022-07-13,"mercoledì",,
|
||||||
|
2022-07-14,"giovedì",,
|
||||||
|
2022-07-15,"venerdì",,"https://ricette.giallozafferano.it/Salmorejo.html"
|
||||||
|
2022-07-16,"sabato",,
|
||||||
|
2022-07-17,"domenica",,
|
||||||
|
2022-07-18,"lunedì",,
|
||||||
|
2022-07-19,"martedì",,
|
||||||
|
2022-07-20,"mercoledì",,
|
||||||
|
2022-07-21,"giovedì",,
|
||||||
|
2022-07-22,"venerdì",,
|
||||||
|
2022-07-23,"sabato",,
|
||||||
|
2022-07-24,"domenica",,
|
||||||
|
2022-07-25,"lunedì",,
|
||||||
|
2022-07-26,"martedì",,"https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html"
|
||||||
|
2022-07-27,"mercoledì",,
|
||||||
|
2022-07-28,"giovedì",,
|
||||||
|
2022-07-29,"venerdì",,
|
||||||
|
2022-07-30,"sabato",,
|
||||||
|
2022-07-31,"domenica",,
|
||||||
|
"2022-08-01","lunedì",,"https://ricette.giallozafferano.it/Riso-freddo-con-tonno-zucchine-e-limone.html"
|
||||||
|
"2022-08-02","martedì",,
|
||||||
|
"2022-08-03","mercoledì",,
|
||||||
|
"2022-08-04","giovedì",,
|
||||||
|
"2022-08-05","venerdì",,
|
||||||
|
"2022-08-06","sabato",,
|
||||||
|
"2022-08-07","domenica",,
|
||||||
|
"2022-08-08","lunedì",,
|
||||||
|
"2022-08-09","martedì",,
|
||||||
|
"2022-08-10","mercoledì",,
|
||||||
|
"2022-08-11","giovedì",,
|
||||||
|
"2022-08-12","venerdì",,
|
||||||
|
"2022-08-13","sabato",,
|
||||||
|
"2022-08-14","domenica",,
|
||||||
|
"2022-08-15","lunedì",,
|
||||||
|
"2022-08-16","martedì",,
|
||||||
|
"2022-08-17","mercoledì",,"https://ricette.giallozafferano.it/Pasta-fredda-con-pesto-senz-aglio.html"
|
||||||
|
"2022-08-18","giovedì",,
|
||||||
|
"2022-08-19","venerdì",,
|
||||||
|
"2022-08-20","sabato",,"https://ricette.giallozafferano.it/Salmorejo.html"
|
||||||
|
"2022-08-21","domenica",,
|
||||||
|
"2022-08-22","lunedì",,
|
||||||
|
"2022-08-23","martedì",,
|
||||||
|
"2022-08-24","mercoledì",,
|
||||||
|
"2022-08-25","giovedì",,
|
||||||
|
"2022-08-26","venerdì",,
|
||||||
|
"2022-08-27","sabato",,
|
||||||
|
"2022-08-28","domenica",,
|
||||||
|
"2022-08-29","lunedì",,
|
||||||
|
"2022-08-30","martedì",,
|
||||||
|
"2022-08-31","mercoledì",,
|
||||||
|
"2022-09-01","giovedì",,
|
||||||
|
"2022-09-02","venerdì",,
|
||||||
|
"2022-09-03","sabato",,
|
||||||
|
"2022-09-04","domenica",,
|
||||||
|
"2022-09-05","lunedì",,
|
||||||
|
"2022-09-06","martedì",,
|
||||||
|
"2022-09-07","mercoledì",,
|
||||||
|
"2022-09-08","giovedì",,
|
||||||
|
"2022-09-09","venerdì",,
|
||||||
|
"2022-09-10","sabato",,
|
||||||
|
"2022-09-11","domenica",,
|
||||||
|
"2022-09-12","lunedì",,
|
||||||
|
"2022-09-13","martedì",,
|
||||||
|
"2022-09-14","mercoledì",,
|
||||||
|
"2022-09-15","giovedì",,
|
||||||
|
"2022-09-16","venerdì",,
|
||||||
|
"2022-09-17","sabato",,
|
||||||
|
"2022-09-18","domenica",,
|
||||||
|
"2022-09-19","lunedì",,
|
||||||
|
"2022-09-20","martedì",,
|
||||||
|
"2022-09-21","mercoledì",,
|
||||||
|
"2022-09-22","giovedì",,
|
||||||
|
"2022-09-23","venerdì",,
|
||||||
|
"2022-09-24","sabato",,
|
||||||
|
"2022-09-25","domenica",,
|
||||||
|
"2022-09-26","lunedì",,
|
||||||
|
"2022-09-27","martedì",,
|
||||||
|
"2022-09-28","mercoledì",,
|
||||||
|
"2022-09-29","giovedì",,
|
||||||
|
"2022-09-30","venerdì",,
|
||||||
|
"2022-10-01","sabato",,
|
||||||
|
"2022-10-02","domenica",,
|
||||||
|
"2022-10-03","lunedì",,
|
||||||
|
"2022-10-04","martedì",,
|
||||||
|
"2022-10-05","mercoledì",,
|
||||||
|
"2022-10-06","giovedì",,
|
||||||
|
"2022-10-07","venerdì",,
|
||||||
|
"2022-10-08","sabato",,
|
||||||
|
"2022-10-09","domenica",,
|
||||||
|
"2022-10-10","lunedì",,
|
||||||
|
"2022-10-11","martedì",,
|
||||||
|
"2022-10-12","mercoledì",,
|
||||||
|
"2022-10-13","giovedì",,
|
||||||
|
"2022-10-14","venerdì",,
|
||||||
|
"2022-10-15","sabato",,
|
||||||
|
"2022-10-16","domenica",,
|
||||||
|
"2022-10-17","lunedì",,
|
||||||
|
"2022-10-18","martedì",,
|
||||||
|
"2022-10-19","mercoledì",,
|
||||||
|
"2022-10-20","giovedì",,
|
||||||
|
"2022-10-21","venerdì",,
|
||||||
|
"2022-10-22","sabato",,
|
||||||
|
"2022-10-23","domenica",,
|
||||||
|
"2022-10-24","lunedì",,
|
||||||
|
"2022-10-25","martedì",,
|
||||||
|
"2022-10-26","mercoledì",,
|
||||||
|
"2022-10-27","giovedì",,
|
||||||
|
"2022-10-28","venerdì",,
|
||||||
|
"2022-10-29","sabato",,
|
||||||
|
"2022-10-30","domenica",,
|
||||||
|
"2022-10-31","lunedì",,
|
||||||
|
"2022-11-01","martedì",,
|
||||||
|
"2022-11-02","mercoledì",,
|
||||||
|
"2022-11-03","giovedì",,
|
||||||
|
"2022-11-04","venerdì",,
|
||||||
|
"2022-11-05","sabato",,
|
||||||
|
"2022-11-06","domenica",,
|
||||||
|
"2022-11-07","lunedì",,
|
||||||
|
"2022-11-08","martedì",,
|
||||||
|
"2022-11-09","mercoledì",,
|
||||||
|
"2022-11-10","giovedì",,
|
||||||
|
"2022-11-11","venerdì",,
|
||||||
|
"2022-11-12","sabato",,
|
||||||
|
"2022-11-13","domenica",,
|
||||||
|
"2022-11-14","lunedì",,
|
||||||
|
"2022-11-15","martedì",,
|
||||||
|
"2022-11-16","mercoledì",,
|
||||||
|
"2022-11-17","giovedì",,
|
||||||
|
"2022-11-18","venerdì",,
|
||||||
|
"2022-11-19","sabato",,
|
||||||
|
"2022-11-20","domenica",,
|
||||||
|
"2022-11-21","lunedì",,
|
||||||
|
"2022-11-22","martedì",,
|
||||||
|
"2022-11-23","mercoledì",,
|
||||||
|
"2022-11-24","giovedì",,
|
||||||
|
"2022-11-25","venerdì",,
|
||||||
|
"2022-11-26","sabato",,
|
||||||
|
"2022-11-27","domenica",,
|
||||||
|
"2022-11-28","lunedì",,
|
||||||
|
"2022-11-29","martedì",,
|
||||||
|
"2022-11-30","mercoledì",,
|
||||||
|
"2022-12-01","giovedì",,
|
||||||
|
"2022-12-02","venerdì",,
|
||||||
|
"2022-12-03","sabato",,
|
||||||
|
"2022-12-04","domenica",,
|
||||||
|
"2022-12-05","lunedì",,
|
||||||
|
"2022-12-06","martedì",,
|
||||||
|
"2022-12-07","mercoledì",,
|
||||||
|
"2022-12-08","giovedì",,
|
||||||
|
"2022-12-09","venerdì",,
|
||||||
|
"2022-12-10","sabato",,
|
||||||
|
"2022-12-11","domenica",,
|
||||||
|
"2022-12-12","lunedì",,
|
||||||
|
"2022-12-13","martedì",,
|
||||||
|
"2022-12-14","mercoledì",,
|
||||||
|
"2022-12-15","giovedì",,
|
||||||
|
"2022-12-16","venerdì",,
|
||||||
|
"2022-12-17","sabato",,
|
||||||
|
"2022-12-18","domenica",,
|
||||||
|
"2022-12-19","lunedì",,
|
||||||
|
"2022-12-20","martedì",,
|
||||||
|
"2022-12-21","mercoledì",,
|
||||||
|
"2022-12-22","giovedì",,
|
||||||
|
"2022-12-23","venerdì",,
|
||||||
|
"2022-12-24","sabato",,
|
||||||
|
"2022-12-25","domenica",,
|
||||||
|
"2022-12-26","lunedì",,
|
||||||
|
"2022-12-27","martedì",,
|
||||||
|
"2022-12-28","mercoledì",,
|
||||||
|
"2022-12-29","giovedì",,
|
||||||
|
"2022-12-30","venerdì",,
|
||||||
|
"2022-12-31","sabato",,
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
|
@ -0,0 +1,103 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::config::Config, crate::constants, anyhow::anyhow, anyhow::Result,
|
||||||
|
base64::write::EncoderWriter as Base64Encoder, reqwest::Url, std::io::Write,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ApiClient {
|
||||||
|
rest: reqwest::Client,
|
||||||
|
agent: ureq::Agent,
|
||||||
|
base_url: Url,
|
||||||
|
caldav_base_url: Url,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiClient {
|
||||||
|
pub fn new(server_name: &str, configuration: &Config) -> Result<Self> {
|
||||||
|
let server = configuration
|
||||||
|
.credentials
|
||||||
|
.servers
|
||||||
|
.get(server_name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Unknown server {}. Did you use '{} init' first? Known servers: {:#?}",
|
||||||
|
server_name,
|
||||||
|
env!("CARGO_BIN_NAME"),
|
||||||
|
configuration.credentials.servers.keys().collect::<Vec<_>>()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
use reqwest::header;
|
||||||
|
let mut default_headers = header::HeaderMap::new();
|
||||||
|
let mut auth_header = b"Basic ".to_vec();
|
||||||
|
{
|
||||||
|
let mut encoder = Base64Encoder::new(&mut auth_header, base64::STANDARD);
|
||||||
|
write!(encoder, "{}:{}", server.login_name, server.password).unwrap();
|
||||||
|
}
|
||||||
|
let mut auth_header = header::HeaderValue::from_bytes(&auth_header)?;
|
||||||
|
auth_header.set_sensitive(true);
|
||||||
|
default_headers.insert(header::AUTHORIZATION, auth_header);
|
||||||
|
|
||||||
|
let rest_client = reqwest::Client::builder()
|
||||||
|
.user_agent(constants::USER_AGENT)
|
||||||
|
.default_headers(default_headers)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let base_url = Url::parse(&server.url)?;
|
||||||
|
let caldav_base_url = futures::executor::block_on(
|
||||||
|
rest_client
|
||||||
|
.head(base_url.join("/.well-known/caldav")?)
|
||||||
|
.send(),
|
||||||
|
)?
|
||||||
|
.url()
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
Ok(ApiClient {
|
||||||
|
base_url,
|
||||||
|
caldav_base_url,
|
||||||
|
username: server.login_name.clone(),
|
||||||
|
password: server.password.clone(),
|
||||||
|
rest: rest_client,
|
||||||
|
agent: ureq::Agent::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rest(&self) -> &reqwest::Client {
|
||||||
|
&self.rest
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base_url(&self) -> &Url {
|
||||||
|
&self.base_url
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn username(&self) -> &str {
|
||||||
|
&self.username
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_calendars(
|
||||||
|
&self,
|
||||||
|
) -> core::result::Result<Vec<minicaldav::Calendar>, minicaldav::Error> {
|
||||||
|
minicaldav::get_calendars(
|
||||||
|
self.agent.clone(),
|
||||||
|
self.username(),
|
||||||
|
&self.password,
|
||||||
|
&self.caldav_base_url,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_events(
|
||||||
|
&self,
|
||||||
|
calendar: &minicaldav::Calendar,
|
||||||
|
) -> core::result::Result<(Vec<minicaldav::Event>, Vec<minicaldav::Error>), minicaldav::Error>
|
||||||
|
{
|
||||||
|
minicaldav::get_events(
|
||||||
|
self.agent.clone(),
|
||||||
|
self.username(),
|
||||||
|
&self.password,
|
||||||
|
calendar,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use {crate::api_client::ApiClient, anyhow::Result, reqwest::StatusCode};
|
||||||
|
|
||||||
|
pub async fn with<UrlsIter>(api_client: &ApiClient, urls: UrlsIter) -> Result<()>
|
||||||
|
where
|
||||||
|
UrlsIter: std::iter::Iterator,
|
||||||
|
UrlsIter::Item: AsRef<str>,
|
||||||
|
{
|
||||||
|
for url in urls {
|
||||||
|
let response = api_client
|
||||||
|
.rest()
|
||||||
|
.post(api_client.base_url().join("apps/cookbook/import")?)
|
||||||
|
.json(&serde_json::json!({
|
||||||
|
"url": url.as_ref(),
|
||||||
|
}))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
if ![StatusCode::OK, StatusCode::CONFLICT].contains(&response.status()) {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Unable to import recipe {}, received status code {}",
|
||||||
|
url.as_ref(),
|
||||||
|
response.status()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use {crate::config::Config, anyhow::Result, reqwest::Url};
|
||||||
|
|
||||||
|
pub async fn with(configuration: &mut Config, server: &str) -> Result<()> {
|
||||||
|
tokio::task::block_in_place(move || -> anyhow::Result<()> {
|
||||||
|
configuration
|
||||||
|
.credentials
|
||||||
|
.add(Url::parse(server)?)
|
||||||
|
.expect("Unable to authenticate to NextCloud instance");
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
pub mod import;
|
||||||
|
pub mod init;
|
||||||
|
pub mod schedule;
|
||||||
|
pub mod schedule_csv;
|
|
@ -0,0 +1,14 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use {crate::api_client::ApiClient, crate::recipe, anyhow::Result};
|
||||||
|
|
||||||
|
pub async fn with(api_client: &ApiClient) -> Result<()> {
|
||||||
|
let recipes = api_client
|
||||||
|
.rest()
|
||||||
|
.get(api_client.base_url().join("apps/cookbook/api/recipes")?)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
println!("{:#?}", recipes.json::<Vec<recipe::Metadata>>().await?);
|
||||||
|
todo!();
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::api_client::ApiClient,
|
||||||
|
crate::commands::import,
|
||||||
|
crate::constants,
|
||||||
|
crate::event::{Event, Meal},
|
||||||
|
crate::recipe,
|
||||||
|
anyhow::{bail, Result},
|
||||||
|
chrono::naive::NaiveDate,
|
||||||
|
futures::future::try_join_all,
|
||||||
|
reqwest::StatusCode,
|
||||||
|
std::collections::{HashMap, HashSet},
|
||||||
|
std::fmt::Write,
|
||||||
|
std::iter::Iterator,
|
||||||
|
std::path::Path,
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct CsvRecord {
|
||||||
|
day: NaiveDate,
|
||||||
|
lunch: String,
|
||||||
|
dinner: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn with(api_client: &ApiClient, calendar: &str, csv_file: &Path) -> Result<()> {
|
||||||
|
let mut csv = csv::Reader::from_path(csv_file)?;
|
||||||
|
let records = csv.deserialize::<CsvRecord>().flatten().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let recipe_urls = urls_from_csv(records.iter())?;
|
||||||
|
import::with(&api_client, recipe_urls.into_iter()).await?;
|
||||||
|
let recipes = get_all_recipes(&api_client).await?;
|
||||||
|
|
||||||
|
let events = records
|
||||||
|
.iter()
|
||||||
|
.flat_map(|r| {
|
||||||
|
let lunch = recipes.get(&r.lunch);
|
||||||
|
let dinner = recipes.get(&r.dinner);
|
||||||
|
|
||||||
|
let events = [
|
||||||
|
lunch.map(|recipe| Event::new(r.day, Meal::Lunch, recipe.clone())),
|
||||||
|
dinner.map(|recipe| Event::new(r.day, Meal::Dinner, recipe.clone())),
|
||||||
|
];
|
||||||
|
|
||||||
|
events
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
publish_events(&api_client, calendar, events).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn urls_from_csv<'a, RecordsIter>(records: RecordsIter) -> Result<HashSet<String>>
|
||||||
|
where
|
||||||
|
RecordsIter: Iterator<Item = &'a CsvRecord>,
|
||||||
|
{
|
||||||
|
Ok(
|
||||||
|
records.fold(std::collections::HashSet::new(), |mut set, r| {
|
||||||
|
if !r.lunch.is_empty() {
|
||||||
|
set.insert(r.lunch.clone());
|
||||||
|
}
|
||||||
|
if !r.dinner.is_empty() {
|
||||||
|
set.insert(r.dinner.clone());
|
||||||
|
}
|
||||||
|
set
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_all_recipes(api_client: &ApiClient) -> Result<HashMap<String, Rc<recipe::Recipe>>> {
|
||||||
|
let metadata = api_client
|
||||||
|
.rest()
|
||||||
|
.get(api_client.base_url().join("apps/cookbook/api/recipes")?)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.json::<Vec<recipe::Metadata>>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let recipes = metadata.iter().map(|rm| async {
|
||||||
|
let response = api_client
|
||||||
|
.rest()
|
||||||
|
.get(
|
||||||
|
api_client
|
||||||
|
.base_url()
|
||||||
|
.join(&format!("apps/cookbook/api/recipes/{id}", id = rm.id))
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect(&format!(
|
||||||
|
"Cannot fetch recipe {} with id {}",
|
||||||
|
rm.name, rm.id
|
||||||
|
));
|
||||||
|
response.json::<recipe::Recipe>().await.map(|r| Rc::new(r))
|
||||||
|
});
|
||||||
|
|
||||||
|
let recipes = try_join_all(recipes).await?;
|
||||||
|
Ok(HashMap::from_iter(
|
||||||
|
recipes.into_iter().map(|r| (r.url.clone(), r)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn publish_events<'a, EventsIter>(
|
||||||
|
api_client: &ApiClient,
|
||||||
|
calendar: &str,
|
||||||
|
events: EventsIter,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
EventsIter: Iterator<Item = Event>,
|
||||||
|
{
|
||||||
|
let calendar_prototype: ics::ICalendar = ics::ICalendar::new(
|
||||||
|
"2.0",
|
||||||
|
format!(
|
||||||
|
"-//IDN {}//{} {}//EN",
|
||||||
|
constants::VENDOR,
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let dav_base = api_client
|
||||||
|
.rest()
|
||||||
|
.head(api_client.base_url().join("/.well-known/caldav")?)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let calendar_url = dav_base.url().join(&format!(
|
||||||
|
"calendars/{}/{}/",
|
||||||
|
&api_client.username(),
|
||||||
|
calendar.to_lowercase().as_str().replace(" ", "-")
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let calendar_prototype = &calendar_prototype;
|
||||||
|
let calendar_url = &calendar_url;
|
||||||
|
let update_requests = events.map(|ev| async move {
|
||||||
|
let url = calendar_url.join(&format!("{}.ics", ev.uid)).unwrap();
|
||||||
|
let mut cal = calendar_prototype.clone();
|
||||||
|
cal.add_event(ev.into());
|
||||||
|
|
||||||
|
api_client
|
||||||
|
.rest()
|
||||||
|
.put(url)
|
||||||
|
.header("Content-Type", "text/calendar; charset=utf-8")
|
||||||
|
.body(cal.to_string())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
});
|
||||||
|
|
||||||
|
let responses = try_join_all(update_requests).await?;
|
||||||
|
let failed_responses = responses.into_iter().filter(|response| {
|
||||||
|
![StatusCode::NO_CONTENT, StatusCode::CREATED].contains(&response.status())
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut errors = String::new();
|
||||||
|
for r in failed_responses {
|
||||||
|
write!(errors, "\n{}", r.text().await.unwrap())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
bail!("Error while updating calendar events: {}", errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -2,3 +2,4 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
pub const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
pub const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||||
|
pub const VENDOR: &str = "montecristosoftware.eu";
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::recipe::Recipe,
|
||||||
|
chrono::{DateTime, Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc},
|
||||||
|
ics::escape_text,
|
||||||
|
ics::properties as calprop,
|
||||||
|
ics::Event as CalEvent,
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Event {
|
||||||
|
pub uid: String,
|
||||||
|
pub ends_at: NaiveDateTime,
|
||||||
|
pub recipe: Rc<Recipe>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(strum_macros::Display)]
|
||||||
|
pub enum Meal {
|
||||||
|
Lunch,
|
||||||
|
Dinner,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event {
|
||||||
|
pub fn new(date: NaiveDate, meal: Meal, recipe: Rc<Recipe>) -> Self {
|
||||||
|
let uid = format!(
|
||||||
|
"{}-{}@{}.montecristosoftware.eu",
|
||||||
|
date,
|
||||||
|
meal,
|
||||||
|
env!("CARGO_PKG_NAME")
|
||||||
|
);
|
||||||
|
|
||||||
|
let meal_time = match meal {
|
||||||
|
Meal::Lunch => NaiveTime::from_hms(12, 00, 00),
|
||||||
|
Meal::Dinner => NaiveTime::from_hms(19, 00, 00),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ends_at = NaiveDateTime::new(date, meal_time);
|
||||||
|
|
||||||
|
Event {
|
||||||
|
uid,
|
||||||
|
ends_at,
|
||||||
|
recipe,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<Event> for CalEvent<'a> {
|
||||||
|
fn from(ev: Event) -> Self {
|
||||||
|
let start_time = Local
|
||||||
|
.from_local_datetime(&(*&ev.ends_at - ev.recipe.total_time()))
|
||||||
|
.unwrap();
|
||||||
|
let end_time = Local.from_local_datetime(&ev.ends_at).unwrap();
|
||||||
|
let timezone = iana_time_zone::get_timezone().unwrap();
|
||||||
|
|
||||||
|
let mut event = ics::Event::new(ev.uid.clone(), dt_utc_fmt(&Utc::now()));
|
||||||
|
event.push(calprop::Summary::new(escape_text(ev.recipe.name.clone())));
|
||||||
|
event.push(calprop::Description::new(format!(
|
||||||
|
"cookbook@{}",
|
||||||
|
ev.recipe.id
|
||||||
|
)));
|
||||||
|
event.push(calprop::Location::new(escape_text(ev.recipe.url.clone())));
|
||||||
|
|
||||||
|
let mut dtstart = calprop::DtStart::new(dt_fmt(&start_time));
|
||||||
|
dtstart.append(ics::parameters!("TZID" => timezone.clone()));
|
||||||
|
event.push(dtstart);
|
||||||
|
|
||||||
|
let mut dtend = calprop::DtEnd::new(dt_fmt(&end_time));
|
||||||
|
dtend.append(ics::parameters!("TZID" => timezone));
|
||||||
|
event.push(dtend);
|
||||||
|
|
||||||
|
// TODO make configurable yearly repetition, for now this suits my personal uses
|
||||||
|
const DAY_NAMES: [&str; 7] = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];
|
||||||
|
event.push(calprop::RRule::new(format!(
|
||||||
|
"FREQ=YEARLY;BYDAY={weekday};BYWEEKNO={weekno}",
|
||||||
|
weekday = DAY_NAMES
|
||||||
|
.get(start_time.weekday().num_days_from_monday() as usize)
|
||||||
|
.unwrap(),
|
||||||
|
weekno = start_time.iso_week().week(),
|
||||||
|
)));
|
||||||
|
|
||||||
|
let mut trigger = calprop::Trigger::new("-PT15M");
|
||||||
|
trigger.append(ics::parameters!("RELATED" => "START"));
|
||||||
|
let alarm = ics::Alarm::display(
|
||||||
|
trigger,
|
||||||
|
calprop::Description::new(escape_text(ev.recipe.name.clone())),
|
||||||
|
);
|
||||||
|
event.add_alarm(alarm);
|
||||||
|
event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dt_fmt(datetime: &DateTime<Local>) -> String {
|
||||||
|
datetime.format("%Y%m%dT%H%M%S").to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dt_utc_fmt(datetime: &DateTime<Utc>) -> String {
|
||||||
|
datetime.format("%Y%m%dT%H%M%SZ").to_string()
|
||||||
|
}
|
141
src/main.rs
141
src/main.rs
|
@ -1,19 +1,28 @@
|
||||||
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
mod api_client;
|
||||||
|
mod commands;
|
||||||
mod config;
|
mod config;
|
||||||
mod constants;
|
mod constants;
|
||||||
|
mod event;
|
||||||
|
mod recipe;
|
||||||
|
|
||||||
use {
|
use {
|
||||||
self::config::Config,
|
crate::api_client::ApiClient,
|
||||||
anyhow::anyhow,
|
crate::config::Config,
|
||||||
base64::write::EncoderWriter as Base64Encoder,
|
anyhow::Result,
|
||||||
clap::{arg, command, ArgMatches, Command},
|
clap::{arg, command, ArgMatches, Command},
|
||||||
reqwest::Url,
|
std::path::PathBuf,
|
||||||
std::io::Write,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn parse_args() -> ArgMatches {
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let args = setup_args();
|
||||||
|
parse_args(&args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_args() -> ArgMatches {
|
||||||
let server_arg = arg!(-s --server <server> "NextCloud server to connect to").required(false);
|
let server_arg = arg!(-s --server <server> "NextCloud server to connect to").required(false);
|
||||||
|
|
||||||
command!()
|
command!()
|
||||||
|
@ -28,15 +37,29 @@ fn parse_args() -> ArgMatches {
|
||||||
.subcommand(
|
.subcommand(
|
||||||
Command::new("import")
|
Command::new("import")
|
||||||
.about("Import the given URLs into NextCloud's cookbook")
|
.about("Import the given URLs into NextCloud's cookbook")
|
||||||
.arg(server_arg)
|
.arg(server_arg.clone())
|
||||||
.arg(arg!(<url> ... "One or more URLs each pointing to page with a recipe to import in NextCloud")),
|
.arg(arg!(<url> ... "One or more URLs each pointing to page with a recipe to import in NextCloud")),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
Command::new("schedule")
|
||||||
|
.about("")
|
||||||
|
.arg(server_arg.clone())
|
||||||
|
.arg(arg!(-d --days <days> "")
|
||||||
|
.value_parser(clap::builder::RangedU64ValueParser::<u32>::new().range(1..))
|
||||||
|
.required(false)
|
||||||
|
.default_value("7"))
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
Command::new("schedule-csv")
|
||||||
|
.about("TEMPORARY WIP FUNCTION USED FOR INTERNAL TESTING")
|
||||||
|
.arg(server_arg.clone())
|
||||||
|
.arg(arg!(<calendar_name> ""))
|
||||||
|
.arg(arg!(<csv_file> "").value_parser(clap::value_parser!(PathBuf)))
|
||||||
|
)
|
||||||
.get_matches()
|
.get_matches()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "multi_thread")]
|
async fn parse_args(args: &ArgMatches) -> Result<()> {
|
||||||
async fn main() -> anyhow::Result<()> {
|
|
||||||
let args = parse_args();
|
|
||||||
let mut configuration = Config::new();
|
let mut configuration = Config::new();
|
||||||
|
|
||||||
match args.subcommand() {
|
match args.subcommand() {
|
||||||
|
@ -44,15 +67,35 @@ async fn main() -> anyhow::Result<()> {
|
||||||
let server = sub_matches
|
let server = sub_matches
|
||||||
.get_one::<String>("server")
|
.get_one::<String>("server")
|
||||||
.expect("Mandatory parameter <server>");
|
.expect("Mandatory parameter <server>");
|
||||||
tokio::task::block_in_place(move || -> anyhow::Result<()> {
|
commands::init::with(&mut configuration, server).await
|
||||||
configuration
|
|
||||||
.credentials
|
|
||||||
.add(Url::parse(server)?)
|
|
||||||
.expect("Unable to authenticate to NextCloud instance");
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
Some(("import", sub_matches)) => {
|
Some(("import", sub_matches)) => {
|
||||||
|
let api_client = get_api_client(&sub_matches, &configuration)?;
|
||||||
|
let urls = sub_matches
|
||||||
|
.get_many::<String>("url")
|
||||||
|
.expect("At least one url is required")
|
||||||
|
.map(|s| s.as_str());
|
||||||
|
commands::import::with(&api_client, urls).await
|
||||||
|
}
|
||||||
|
Some(("schedule", sub_matches)) => {
|
||||||
|
let api_client = get_api_client(&sub_matches, &configuration)?;
|
||||||
|
commands::schedule::with(&api_client).await
|
||||||
|
}
|
||||||
|
Some(("schedule-csv", sub_matches)) => {
|
||||||
|
let api_client = get_api_client(&sub_matches, &configuration)?;
|
||||||
|
let csv_file = sub_matches
|
||||||
|
.get_one::<PathBuf>("csv_file")
|
||||||
|
.expect("<csv_file> is a mandatory parameter, it cannot be missing");
|
||||||
|
let calendar_name = sub_matches
|
||||||
|
.get_one::<String>("calendar_name")
|
||||||
|
.expect("<calendar_name> is a mandatory parameter, it cannot be missing");
|
||||||
|
commands::schedule_csv::with(&api_client, calendar_name.as_str(), &csv_file).await
|
||||||
|
}
|
||||||
|
_ => unreachable!("Exhausted list of subcommands and subcommand_required prevents `None`"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_api_client(sub_matches: &ArgMatches, configuration: &Config) -> Result<ApiClient> {
|
||||||
let server_name = sub_matches
|
let server_name = sub_matches
|
||||||
.get_one::<String>("server")
|
.get_one::<String>("server")
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
|
@ -64,67 +107,5 @@ async fn main() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let api_client = ApiClient::new(&server_name, &configuration)?;
|
ApiClient::new(&server_name, &configuration)
|
||||||
for url in sub_matches
|
|
||||||
.get_many::<String>("url")
|
|
||||||
.expect("At least one url is required")
|
|
||||||
{
|
|
||||||
let response = api_client
|
|
||||||
.client
|
|
||||||
.post(api_client.base_url.join("apps/cookbook/import")?)
|
|
||||||
.json(&serde_json::json!({
|
|
||||||
"url": url,
|
|
||||||
}))
|
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
println!("{:#?}", response); // TODO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!("Exhausted list of subcommands and subcommand_required prevents `None`"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ApiClient {
|
|
||||||
pub base_url: Url,
|
|
||||||
pub client: reqwest::Client,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApiClient {
|
|
||||||
pub fn new(server_name: &str, configuration: &Config) -> anyhow::Result<Self> {
|
|
||||||
let server = configuration
|
|
||||||
.credentials
|
|
||||||
.servers
|
|
||||||
.get(server_name)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
anyhow!(
|
|
||||||
"Unknown server {}. Did you use '{} init' first? Known servers: {:#?}",
|
|
||||||
server_name,
|
|
||||||
env!("CARGO_BIN_NAME"),
|
|
||||||
configuration.credentials.servers.keys().collect::<Vec<_>>()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
use reqwest::header;
|
|
||||||
let mut default_headers = header::HeaderMap::new();
|
|
||||||
let mut auth_header = b"Basic ".to_vec();
|
|
||||||
{
|
|
||||||
let mut encoder = Base64Encoder::new(&mut auth_header, base64::STANDARD);
|
|
||||||
write!(encoder, "{}:{}", server.login_name, server.password).unwrap();
|
|
||||||
}
|
|
||||||
let mut auth_header = header::HeaderValue::from_bytes(&auth_header)?;
|
|
||||||
auth_header.set_sensitive(true);
|
|
||||||
default_headers.insert(header::AUTHORIZATION, auth_header);
|
|
||||||
|
|
||||||
let client = reqwest::Client::builder()
|
|
||||||
.user_agent(constants::USER_AGENT)
|
|
||||||
.default_headers(default_headers)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
Ok(ApiClient {
|
|
||||||
base_url: Url::parse(&server.url)?,
|
|
||||||
client: client,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use {
|
||||||
|
chrono::Duration,
|
||||||
|
serde::{Deserialize, Deserializer},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Metadata {
|
||||||
|
#[serde(rename = "recipe_id")]
|
||||||
|
pub id: u32,
|
||||||
|
pub name: String,
|
||||||
|
pub keywords: String,
|
||||||
|
pub date_created: DateTime,
|
||||||
|
pub date_modified: DateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A recipe according to [schema.org](http://schema.org/Recipe)
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Recipe {
|
||||||
|
pub id: isize,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub url: String,
|
||||||
|
pub keywords: String,
|
||||||
|
|
||||||
|
#[serde(rename = "dateCreated")]
|
||||||
|
pub created: DateTime,
|
||||||
|
|
||||||
|
#[serde(rename = "dateModified")]
|
||||||
|
pub modified: Option<DateTime>,
|
||||||
|
|
||||||
|
pub image_url: String,
|
||||||
|
|
||||||
|
#[serde(deserialize_with = "deserialize_duration")]
|
||||||
|
pub prep_time: Duration,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(deserialize_with = "deserialize_maybe_duration")]
|
||||||
|
pub cook_time: Option<Duration>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
#[serde(deserialize_with = "deserialize_maybe_duration")]
|
||||||
|
pub total_time: Option<Duration>,
|
||||||
|
|
||||||
|
pub image: Option<String>,
|
||||||
|
pub recipe_yield: isize,
|
||||||
|
|
||||||
|
#[serde(rename = "recipeCategory")]
|
||||||
|
pub category: Option<String>,
|
||||||
|
|
||||||
|
pub tools: Option<Vec<Tool>>,
|
||||||
|
#[serde(rename = "recipeIngredient")]
|
||||||
|
pub ingredients: Vec<Ingredient>,
|
||||||
|
#[serde(rename = "recipeInstructions")]
|
||||||
|
pub instructions: Vec<Instruction>,
|
||||||
|
//pub nutrition: Nutrition,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Ingredient(String);
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Tool(String);
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Instruction(String);
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Nutrition {
|
||||||
|
pub calories: Option<String>,
|
||||||
|
pub carbohydrates_content: Option<String>,
|
||||||
|
pub cholesterol_content: Option<String>,
|
||||||
|
pub fat_content: Option<String>,
|
||||||
|
pub fiber_content: Option<String>,
|
||||||
|
pub protein_content: Option<String>,
|
||||||
|
pub saturated_fat_content: Option<String>,
|
||||||
|
pub serving_size: Option<String>,
|
||||||
|
pub sodium_content: Option<String>,
|
||||||
|
pub sugar_content: Option<String>,
|
||||||
|
pub trans_fat_content: Option<String>,
|
||||||
|
pub unsaturated_fat_content: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type DateTime = chrono::DateTime<chrono::Utc>;
|
||||||
|
|
||||||
|
fn deserialize_maybe_duration<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Ok(Some(deserialize_duration(deserializer)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_str(DurationVisitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DurationVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for DurationVisitor {
|
||||||
|
type Value = Duration;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a duration in ISO 8601 format")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
speedate::Duration::parse_str(value)
|
||||||
|
.map(|dt| Duration::seconds(dt.signed_total_seconds()))
|
||||||
|
.map_err(|e| E::custom(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Recipe {
|
||||||
|
pub fn total_time(&self) -> Duration {
|
||||||
|
self.total_time
|
||||||
|
.unwrap_or_else(|| self.prep_time + self.cook_time.unwrap_or(Duration::zero()))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue