From 4874b9291ac98bbaa0d322450999a589ba6f92bc Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 3 Mar 2026 11:38:03 +0000 Subject: [PATCH] feat: presentation artifact p1 (#13341) Part 1 of presentation tool artifact --- MODULE.bazel.lock | 41 + codex-rs/Cargo.lock | 631 ++- codex-rs/Cargo.toml | 3 + codex-rs/artifact-presentation/BUILD.bazel | 6 + codex-rs/artifact-presentation/Cargo.toml | 27 + codex-rs/artifact-presentation/src/lib.rs | 6 + .../src/presentation_artifact.rs | 4701 +++++++++++++++++ codex-rs/artifact-presentation/src/tests.rs | 952 ++++ codex-rs/core/Cargo.toml | 1 + codex-rs/core/config.schema.json | 6 + codex-rs/core/src/codex.rs | 12 + codex-rs/core/src/features.rs | 8 + codex-rs/core/src/state/session.rs | 3 + codex-rs/core/src/tools/handlers/mod.rs | 2 + .../tools/handlers/presentation_artifact.rs | 254 + codex-rs/core/src/tools/spec.rs | 67 + .../templates/tools/presentation_artifact.md | 95 + codex-rs/deny.toml | 1 + 18 files changed, 6755 insertions(+), 61 deletions(-) create mode 100644 codex-rs/artifact-presentation/BUILD.bazel create mode 100644 codex-rs/artifact-presentation/Cargo.toml create mode 100644 codex-rs/artifact-presentation/src/lib.rs create mode 100644 codex-rs/artifact-presentation/src/presentation_artifact.rs create mode 100644 codex-rs/artifact-presentation/src/tests.rs create mode 100644 codex-rs/core/src/tools/handlers/presentation_artifact.rs create mode 100644 codex-rs/core/templates/tools/presentation_artifact.md diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index d3dfd0346..9b459b994 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -675,6 +675,7 @@ "bytes_1.11.1": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"require-cas\"],\"name\":\"extra-platforms\",\"optional\":true,\"package\":\"portable-atomic\",\"req\":\"^1.3\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(loom)\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.60\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "bytestring_1.5.0": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"bytes\",\"req\":\"^1.2\"},{\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"}],\"features\":{\"serde\":[\"dep:serde_core\"]}}", "bzip2-sys_0.1.13+1.0.8": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.9\"}],\"features\":{\"__disabled\":[],\"static\":[]}}", + "bzip2_0.4.4": "{\"dependencies\":[{\"name\":\"bzip2-sys\",\"req\":\"^0.1.11\"},{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"features\":[\"quickcheck\"],\"kind\":\"dev\",\"name\":\"partial-io\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck6\",\"package\":\"quickcheck\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"tokio-core\",\"req\":\"^0.1\"},{\"name\":\"tokio-io\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{\"static\":[\"bzip2-sys/static\"],\"tokio\":[\"tokio-io\",\"futures\"]}}", "bzip2_0.5.2": "{\"dependencies\":[{\"name\":\"bzip2-sys\",\"optional\":true,\"req\":\"^0.1.13\"},{\"default_features\":false,\"features\":[\"rust-allocator\",\"semver-prefix\"],\"name\":\"libbz2-rs-sys\",\"optional\":true,\"req\":\"^0.1.3\"},{\"features\":[\"quickcheck1\"],\"kind\":\"dev\",\"name\":\"partial-io\",\"req\":\"^0.5.4\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"default\":[\"dep:bzip2-sys\"],\"libbz2-rs-sys\":[\"dep:libbz2-rs-sys\",\"bzip2-sys?/__disabled\"],\"static\":[\"bzip2-sys?/static\"]}}", "cached_0.56.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ahash\",\"optional\":true,\"req\":\"^0.8\"},{\"features\":[\"attributes\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.6\"},{\"name\":\"async-trait\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"cached_proc_macro\",\"optional\":true,\"req\":\"^0.25.0\"},{\"name\":\"cached_proc_macro_types\",\"optional\":true,\"req\":\"^0.1.1\"},{\"kind\":\"dev\",\"name\":\"copy_dir\",\"req\":\"^0.1.3\"},{\"name\":\"directories\",\"optional\":true,\"req\":\"^6.0\"},{\"default_features\":false,\"name\":\"futures\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"googletest\",\"req\":\"^0.11.0\"},{\"default_features\":false,\"features\":[\"inline-more\"],\"name\":\"hashbrown\",\"req\":\"^0.15\"},{\"name\":\"once_cell\",\"req\":\"^1\"},{\"name\":\"r2d2\",\"optional\":true,\"req\":\"^0.8\"},{\"features\":[\"r2d2\"],\"name\":\"redis\",\"optional\":true,\"req\":\"^0.32\"},{\"name\":\"rmp-serde\",\"optional\":true,\"req\":\"^1.1\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3\"},{\"name\":\"sled\",\"optional\":true,\"req\":\"^0.34\"},{\"kind\":\"dev\",\"name\":\"smartstring\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.10.1\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"features\":[\"macros\",\"time\",\"sync\",\"parking_lot\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"web-time\",\"req\":\"^1.1.0\"}],\"features\":{\"ahash\":[\"dep:ahash\",\"hashbrown/default\"],\"async\":[\"futures\",\"tokio\",\"async-trait\"],\"async_tokio_rt_multi_thread\":[\"async\",\"tokio/rt-multi-thread\"],\"default\":[\"proc_macro\",\"ahash\"],\"disk_store\":[\"sled\",\"serde\",\"rmp-serde\",\"directories\"],\"proc_macro\":[\"cached_proc_macro\",\"cached_proc_macro_types\"],\"redis_ahash\":[\"redis_store\",\"redis/ahash\"],\"redis_async_std\":[\"redis_store\",\"async\",\"redis/aio\",\"redis/async-std-comp\",\"redis/tls\",\"redis/async-std-tls-comp\"],\"redis_connection_manager\":[\"redis_store\",\"redis/connection-manager\"],\"redis_store\":[\"redis\",\"r2d2\",\"serde\",\"serde_json\"],\"redis_tokio\":[\"redis_store\",\"async\",\"redis/aio\",\"redis/tokio-comp\",\"redis/tls\",\"redis/tokio-native-tls-comp\"],\"wasm\":[]}}", "cached_proc_macro_0.25.0": "{\"dependencies\":[{\"name\":\"darling\",\"req\":\"^0.20.8\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.49\"},{\"name\":\"quote\",\"req\":\"^1.0.6\"},{\"name\":\"syn\",\"req\":\"^2.0.52\"}],\"features\":{}}", @@ -715,6 +716,7 @@ "const-oid_0.9.6": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3\"}],\"features\":{\"db\":[],\"std\":[]}}", "const_format_0.2.35": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"arrayvec\",\"req\":\"^0.7.0\"},{\"name\":\"const_format_proc_macros\",\"req\":\"=0.2.34\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^1.3.5\"},{\"default_features\":false,\"name\":\"konst\",\"optional\":true,\"req\":\"^0.2.13\"}],\"features\":{\"__debug\":[\"const_format_proc_macros/debug\"],\"__docsrs\":[],\"__inline_const_pat_tests\":[\"__test\",\"fmt\"],\"__only_new_tests\":[\"__test\"],\"__test\":[],\"all\":[\"fmt\",\"derive\",\"rust_1_64\",\"assert\"],\"assert\":[\"assertc\"],\"assertc\":[\"fmt\",\"assertcp\"],\"assertcp\":[\"rust_1_51\"],\"const_generics\":[\"rust_1_51\"],\"constant_time_as_str\":[\"fmt\"],\"default\":[],\"derive\":[\"fmt\",\"const_format_proc_macros/derive\"],\"fmt\":[\"rust_1_83\"],\"more_str_macros\":[\"rust_1_64\"],\"nightly_const_generics\":[\"const_generics\"],\"rust_1_51\":[],\"rust_1_64\":[\"rust_1_51\",\"konst\",\"konst/rust_1_64\"],\"rust_1_83\":[\"rust_1_64\"]}}", "const_format_proc_macros_0.2.34": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"fastrand\",\"req\":\"^1.3.4\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.19\"},{\"name\":\"quote\",\"req\":\"^1.0.7\"},{\"default_features\":false,\"features\":[\"parsing\",\"proc-macro\"],\"name\":\"syn\",\"optional\":true,\"req\":\"^1.0.38\"},{\"name\":\"unicode-xid\",\"req\":\"^0.2\"}],\"features\":{\"all\":[\"derive\"],\"debug\":[\"syn/extra-traits\"],\"default\":[],\"derive\":[\"syn\",\"syn/derive\",\"syn/printing\"]}}", + "constant_time_eq_0.1.5": "{\"dependencies\":[],\"features\":{}}", "constant_time_eq_0.3.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"count_instructions\",\"req\":\"^0.1.3\"},{\"features\":[\"cargo_bench_support\",\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"}],\"features\":{\"count_instructions_test\":[]}}", "convert_case_0.10.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"unicode-segmentation\",\"req\":\"^1.9.0\"}],\"features\":{}}", "convert_case_0.6.0": "{\"dependencies\":[{\"name\":\"rand\",\"optional\":true,\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"strum\",\"req\":\"^0.18.0\"},{\"kind\":\"dev\",\"name\":\"strum_macros\",\"req\":\"^0.18.0\"},{\"name\":\"unicode-segmentation\",\"req\":\"^1.9.0\"}],\"features\":{\"random\":[\"rand\"]}}", @@ -738,6 +740,8 @@ "crossterm_winapi_0.9.1": "{\"dependencies\":[{\"features\":[\"winbase\",\"consoleapi\",\"processenv\",\"handleapi\",\"synchapi\",\"impl-default\"],\"name\":\"winapi\",\"req\":\"^0.3.8\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "crunchy_0.2.4": "{\"dependencies\":[],\"features\":{\"default\":[\"limit_128\"],\"limit_1024\":[],\"limit_128\":[],\"limit_2048\":[],\"limit_256\":[],\"limit_512\":[],\"limit_64\":[],\"std\":[]}}", "crypto-common_0.1.7": "{\"dependencies\":[{\"features\":[\"more_lengths\"],\"name\":\"generic-array\",\"req\":\"=0.14.7\"},{\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"typenum\",\"req\":\"^1.14\"}],\"features\":{\"getrandom\":[\"rand_core/getrandom\"],\"std\":[]}}", + "cssparser-macros_0.6.1": "{\"dependencies\":[{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"full\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", + "cssparser_0.31.2": "{\"dependencies\":[{\"name\":\"cssparser-macros\",\"req\":\"^0.6.1\"},{\"kind\":\"dev\",\"name\":\"difference\",\"req\":\"^2.0\"},{\"name\":\"dtoa-short\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"encoding_rs\",\"req\":\"^0.8\"},{\"name\":\"itoa\",\"req\":\"^1.0\"},{\"features\":[\"macros\"],\"name\":\"phf\",\"req\":\">=0.8, <=0.11\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"smallvec\",\"req\":\"^1.0\"}],\"features\":{\"bench\":[],\"dummy_match_byte\":[]}}", "csv-core_0.1.13": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"arrayvec\",\"req\":\"^0.5\"},{\"default_features\":false,\"name\":\"memchr\",\"req\":\"^2\"}],\"features\":{\"default\":[],\"libc\":[\"memchr/libc\"]}}", "csv_1.4.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\",\"serde\"],\"kind\":\"dev\",\"name\":\"bstr\",\"req\":\"^1.7.0\"},{\"name\":\"csv-core\",\"req\":\"^0.1.11\"},{\"name\":\"itoa\",\"req\":\"^1\"},{\"name\":\"ryu\",\"req\":\"^1\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.221\"},{\"name\":\"serde_core\",\"req\":\"^1.0.221\"}],\"features\":{}}", "ctor-proc-macro_0.0.7": "{\"dependencies\":[],\"features\":{\"default\":[]}}", @@ -770,6 +774,7 @@ "derive_arbitrary_1.4.2": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"derive\",\"parsing\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", "derive_more-impl_1.0.0": "{\"dependencies\":[{\"name\":\"convert_case\",\"optional\":true,\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.13.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"syn\",\"req\":\"^2.0.45\"},{\"name\":\"unicode-xid\",\"optional\":true,\"req\":\"^0.2.2\"}],\"features\":{\"add\":[],\"add_assign\":[],\"as_ref\":[\"syn/extra-traits\",\"syn/visit\"],\"constructor\":[],\"debug\":[\"syn/extra-traits\",\"dep:unicode-xid\"],\"default\":[],\"deref\":[],\"deref_mut\":[],\"display\":[\"syn/extra-traits\",\"dep:unicode-xid\"],\"error\":[\"syn/extra-traits\"],\"from\":[\"syn/extra-traits\"],\"from_str\":[],\"full\":[\"add\",\"add_assign\",\"as_ref\",\"constructor\",\"debug\",\"deref\",\"deref_mut\",\"display\",\"error\",\"from\",\"from_str\",\"index\",\"index_mut\",\"into\",\"into_iterator\",\"is_variant\",\"mul\",\"mul_assign\",\"not\",\"sum\",\"try_from\",\"try_into\",\"try_unwrap\",\"unwrap\"],\"index\":[],\"index_mut\":[],\"into\":[\"syn/extra-traits\"],\"into_iterator\":[],\"is_variant\":[\"dep:convert_case\"],\"mul\":[\"syn/extra-traits\"],\"mul_assign\":[\"syn/extra-traits\"],\"not\":[\"syn/extra-traits\"],\"sum\":[],\"testing-helpers\":[\"dep:rustc_version\"],\"try_from\":[],\"try_into\":[\"syn/extra-traits\"],\"try_unwrap\":[\"dep:convert_case\"],\"unwrap\":[\"dep:convert_case\"]}}", "derive_more-impl_2.1.1": "{\"dependencies\":[{\"name\":\"convert_case\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"req\":\"^0.4\"},{\"name\":\"syn\",\"req\":\"^2.0.45\"},{\"name\":\"unicode-xid\",\"optional\":true,\"req\":\"^0.2.2\"}],\"features\":{\"add\":[\"syn/extra-traits\",\"syn/visit\"],\"add_assign\":[\"syn/extra-traits\",\"syn/visit\"],\"as_ref\":[\"syn/extra-traits\",\"syn/visit\"],\"constructor\":[],\"debug\":[\"syn/extra-traits\",\"dep:unicode-xid\"],\"default\":[],\"deref\":[],\"deref_mut\":[],\"display\":[\"syn/extra-traits\",\"dep:unicode-xid\",\"dep:convert_case\"],\"eq\":[\"syn/extra-traits\",\"syn/visit\"],\"error\":[\"syn/extra-traits\"],\"from\":[\"syn/extra-traits\"],\"from_str\":[\"syn/full\",\"syn/visit\",\"dep:convert_case\"],\"full\":[\"add\",\"add_assign\",\"as_ref\",\"constructor\",\"debug\",\"deref\",\"deref_mut\",\"display\",\"eq\",\"error\",\"from\",\"from_str\",\"index\",\"index_mut\",\"into\",\"into_iterator\",\"is_variant\",\"mul\",\"mul_assign\",\"not\",\"sum\",\"try_from\",\"try_into\",\"try_unwrap\",\"unwrap\"],\"index\":[],\"index_mut\":[],\"into\":[\"syn/extra-traits\",\"syn/visit-mut\"],\"into_iterator\":[],\"is_variant\":[\"dep:convert_case\"],\"mul\":[\"syn/extra-traits\",\"syn/visit\"],\"mul_assign\":[\"syn/extra-traits\",\"syn/visit\"],\"not\":[\"syn/extra-traits\"],\"sum\":[],\"testing-helpers\":[\"syn/full\"],\"try_from\":[],\"try_into\":[\"syn/extra-traits\",\"syn/full\",\"syn/visit-mut\"],\"try_unwrap\":[\"dep:convert_case\"],\"unwrap\":[\"dep:convert_case\"]}}", + "derive_more_0.99.20": "{\"dependencies\":[{\"name\":\"convert_case\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"build\",\"name\":\"peg\",\"optional\":true,\"req\":\"^0.5\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{\"add\":[],\"add_assign\":[],\"as_mut\":[],\"as_ref\":[],\"constructor\":[],\"default\":[\"add_assign\",\"add\",\"as_mut\",\"as_ref\",\"constructor\",\"deref\",\"deref_mut\",\"display\",\"error\",\"from\",\"from_str\",\"index\",\"index_mut\",\"into\",\"into_iterator\",\"iterator\",\"mul_assign\",\"mul\",\"not\",\"sum\",\"try_into\",\"is_variant\",\"unwrap\"],\"deref\":[],\"deref_mut\":[],\"display\":[\"syn/extra-traits\"],\"error\":[\"syn/extra-traits\"],\"from\":[\"syn/extra-traits\"],\"from_str\":[],\"generate-parsing-rs\":[\"peg\"],\"index\":[],\"index_mut\":[],\"into\":[\"syn/extra-traits\"],\"into_iterator\":[],\"is_variant\":[\"convert_case\"],\"iterator\":[],\"mul\":[\"syn/extra-traits\"],\"mul_assign\":[\"syn/extra-traits\"],\"nightly\":[],\"not\":[\"syn/extra-traits\"],\"sum\":[],\"testing-helpers\":[\"rustc_version\"],\"track-caller\":[],\"try_into\":[\"syn/extra-traits\"],\"unwrap\":[\"convert_case\",\"rustc_version\"]}}", "derive_more_1.0.0": "{\"dependencies\":[{\"name\":\"derive_more-impl\",\"req\":\"=1.0.0\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.56\"}],\"features\":{\"add\":[\"derive_more-impl/add\"],\"add_assign\":[\"derive_more-impl/add_assign\"],\"as_ref\":[\"derive_more-impl/as_ref\"],\"constructor\":[\"derive_more-impl/constructor\"],\"debug\":[\"derive_more-impl/debug\"],\"default\":[\"std\"],\"deref\":[\"derive_more-impl/deref\"],\"deref_mut\":[\"derive_more-impl/deref_mut\"],\"display\":[\"derive_more-impl/display\"],\"error\":[\"derive_more-impl/error\"],\"from\":[\"derive_more-impl/from\"],\"from_str\":[\"derive_more-impl/from_str\"],\"full\":[\"add\",\"add_assign\",\"as_ref\",\"constructor\",\"debug\",\"deref\",\"deref_mut\",\"display\",\"error\",\"from\",\"from_str\",\"index\",\"index_mut\",\"into\",\"into_iterator\",\"is_variant\",\"mul\",\"mul_assign\",\"not\",\"sum\",\"try_from\",\"try_into\",\"try_unwrap\",\"unwrap\"],\"index\":[\"derive_more-impl/index\"],\"index_mut\":[\"derive_more-impl/index_mut\"],\"into\":[\"derive_more-impl/into\"],\"into_iterator\":[\"derive_more-impl/into_iterator\"],\"is_variant\":[\"derive_more-impl/is_variant\"],\"mul\":[\"derive_more-impl/mul\"],\"mul_assign\":[\"derive_more-impl/mul_assign\"],\"not\":[\"derive_more-impl/not\"],\"std\":[],\"sum\":[\"derive_more-impl/sum\"],\"testing-helpers\":[\"derive_more-impl/testing-helpers\",\"dep:rustc_version\"],\"try_from\":[\"derive_more-impl/try_from\"],\"try_into\":[\"derive_more-impl/try_into\"],\"try_unwrap\":[\"derive_more-impl/try_unwrap\"],\"unwrap\":[\"derive_more-impl/unwrap\"]}}", "derive_more_2.1.1": "{\"dependencies\":[{\"name\":\"derive_more-impl\",\"req\":\"=2.1.1\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.56\"}],\"features\":{\"add\":[\"derive_more-impl/add\"],\"add_assign\":[\"derive_more-impl/add_assign\"],\"as_ref\":[\"derive_more-impl/as_ref\"],\"constructor\":[\"derive_more-impl/constructor\"],\"debug\":[\"derive_more-impl/debug\"],\"default\":[\"std\"],\"deref\":[\"derive_more-impl/deref\"],\"deref_mut\":[\"derive_more-impl/deref_mut\"],\"display\":[\"derive_more-impl/display\"],\"eq\":[\"derive_more-impl/eq\"],\"error\":[\"derive_more-impl/error\"],\"from\":[\"derive_more-impl/from\"],\"from_str\":[\"derive_more-impl/from_str\"],\"full\":[\"add\",\"add_assign\",\"as_ref\",\"constructor\",\"debug\",\"deref\",\"deref_mut\",\"display\",\"eq\",\"error\",\"from\",\"from_str\",\"index\",\"index_mut\",\"into\",\"into_iterator\",\"is_variant\",\"mul\",\"mul_assign\",\"not\",\"sum\",\"try_from\",\"try_into\",\"try_unwrap\",\"unwrap\"],\"index\":[\"derive_more-impl/index\"],\"index_mut\":[\"derive_more-impl/index_mut\"],\"into\":[\"derive_more-impl/into\"],\"into_iterator\":[\"derive_more-impl/into_iterator\"],\"is_variant\":[\"derive_more-impl/is_variant\"],\"mul\":[\"derive_more-impl/mul\"],\"mul_assign\":[\"derive_more-impl/mul_assign\"],\"not\":[\"derive_more-impl/not\"],\"std\":[],\"sum\":[\"derive_more-impl/sum\"],\"testing-helpers\":[\"derive_more-impl/testing-helpers\",\"dep:rustc_version\"],\"try_from\":[\"derive_more-impl/try_from\"],\"try_into\":[\"derive_more-impl/try_into\"],\"try_unwrap\":[\"derive_more-impl/try_unwrap\"],\"unwrap\":[\"derive_more-impl/unwrap\"]}}", "deunicode_1.6.2": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"default\":[\"alloc\"]}}", @@ -786,12 +791,15 @@ "displaydoc_0.2.5": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^0.6.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"},{\"name\":\"syn\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^1.0.24\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "dotenvy_0.15.7": "{\"dependencies\":[{\"name\":\"clap\",\"optional\":true,\"req\":\"^3.2\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.16.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.3.0\"}],\"features\":{\"cli\":[\"clap\"]}}", "downcast-rs_1.2.1": "{\"dependencies\":[],\"features\":{\"default\":[\"std\"],\"std\":[]}}", + "dtoa-short_0.3.5": "{\"dependencies\":[{\"name\":\"dtoa\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"float-cmp\",\"req\":\"^0.4\"}],\"features\":{}}", + "dtoa_1.0.11": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{}}", "dtor-proc-macro_0.0.6": "{\"dependencies\":[],\"features\":{\"default\":[]}}", "dtor_0.1.1": "{\"dependencies\":[{\"name\":\"dtor-proc-macro\",\"optional\":true,\"req\":\"=0.0.6\"},{\"kind\":\"dev\",\"name\":\"libc-print\",\"req\":\"^0.1.20\"}],\"features\":{\"__no_warn_on_missing_unsafe\":[],\"default\":[\"proc_macro\",\"__no_warn_on_missing_unsafe\"],\"proc_macro\":[\"dep:dtor-proc-macro\"],\"used_linker\":[]}}", "dunce_1.0.5": "{\"dependencies\":[],\"features\":{}}", "dupe_0.9.1": "{\"dependencies\":[{\"name\":\"dupe_derive\",\"req\":\"=0.9.1\"}],\"features\":{}}", "dupe_derive_0.9.1": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0.3\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", "dyn-clone_1.0.20": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.66\"}],\"features\":{}}", + "ego-tree_0.6.3": "{\"dependencies\":[],\"features\":{}}", "either_1.15.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\",\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.95\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[],\"use_std\":[\"std\"]}}", "ena_0.14.3": "{\"dependencies\":[{\"name\":\"dogged\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"log\",\"req\":\"^0.4\"}],\"features\":{\"bench\":[],\"persistent\":[\"dogged\"]}}", "encode_unicode_1.0.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ascii\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.0\",\"target\":\"cfg(unix)\"},{\"features\":[\"https-native\"],\"kind\":\"dev\",\"name\":\"minreq\",\"req\":\"^2.6\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", @@ -844,6 +852,7 @@ "form_urlencoded_1.2.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"percent-encoding\",\"req\":\"^2.3.0\"}],\"features\":{\"alloc\":[\"percent-encoding/alloc\"],\"default\":[\"std\"],\"std\":[\"alloc\",\"percent-encoding/std\"]}}", "fs_extra_1.3.0": "{\"dependencies\":[],\"features\":{}}", "fsevent-sys_4.1.0": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.68\"}],\"features\":{}}", + "futf_0.1.5": "{\"dependencies\":[{\"name\":\"mac\",\"req\":\"^0.1.0\"},{\"name\":\"new_debug_unreachable\",\"req\":\"^1.0.2\"}],\"features\":{}}", "futures-channel_0.3.31": "{\"dependencies\":[{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.31\"},{\"default_features\":false,\"name\":\"futures-sink\",\"optional\":true,\"req\":\"^0.3.31\"}],\"features\":{\"alloc\":[\"futures-core/alloc\"],\"cfg-target-has-atomic\":[],\"default\":[\"std\"],\"sink\":[\"futures-sink\"],\"std\":[\"alloc\",\"futures-core/std\"],\"unstable\":[]}}", "futures-core_0.3.31": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"require-cas\"],\"name\":\"portable-atomic\",\"optional\":true,\"req\":\"^1.3\"}],\"features\":{\"alloc\":[],\"cfg-target-has-atomic\":[],\"default\":[\"std\"],\"std\":[\"alloc\"],\"unstable\":[]}}", "futures-executor_0.3.31": "{\"dependencies\":[{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.31\"},{\"default_features\":false,\"name\":\"futures-task\",\"req\":\"^0.3.31\"},{\"default_features\":false,\"name\":\"futures-util\",\"req\":\"^0.3.31\"},{\"name\":\"num_cpus\",\"optional\":true,\"req\":\"^1.8.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"futures-core/std\",\"futures-task/std\",\"futures-util/std\"],\"thread-pool\":[\"std\",\"num_cpus\"]}}", @@ -873,6 +882,7 @@ "git+https://github.com/openai-oss-forks/tungstenite-rs?rev=9200079d3b54a1ff51072e24d81fd354f085156f#9200079d3b54a1ff51072e24d81fd354f085156f_tungstenite": "{\"dependencies\":[{\"name\":\"bytes\"},{\"default_features\":true,\"features\":[],\"name\":\"data-encoding\",\"optional\":true},{\"default_features\":false,\"features\":[\"zlib\"],\"name\":\"flate2\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"headers\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"http\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"httparse\",\"optional\":true},{\"name\":\"log\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\"},{\"name\":\"rand\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"sha1\",\"optional\":true},{\"name\":\"thiserror\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":true},{\"name\":\"utf-8\"},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\"],\"default\":[\"handshake\"],\"deflate\":[\"headers\",\"flate2\"],\"handshake\":[\"data-encoding\",\"headers\",\"httparse\",\"sha1\"],\"headers\":[\"http\",\"dep:headers\"],\"native-tls\":[\"native-tls-crate\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\"],\"proxy\":[\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"url\":[\"dep:url\"]},\"strip_prefix\":\"\"}", "glob_0.3.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"tempdir\",\"req\":\"^0.3\"}],\"features\":{}}", "globset_0.4.18": "{\"dependencies\":[{\"name\":\"aho-corasick\",\"req\":\"^1.1.1\"},{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.6.2\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3.1\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"features\":[\"std\",\"perf\",\"syntax\",\"meta\",\"nfa\",\"hybrid\"],\"name\":\"regex-automata\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"regex-syntax\",\"req\":\"^0.8.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.188\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.107\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"default\":[\"log\"],\"serde1\":[\"serde\"],\"simd-accel\":[]}}", + "h2_0.3.27": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-sink\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"http\",\"req\":\"^0.2\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"},{\"name\":\"slab\",\"req\":\"^0.4.2\"},{\"features\":[\"io-util\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\",\"sync\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-rustls\",\"req\":\"^0.24\"},{\"features\":[\"codec\",\"io\"],\"name\":\"tokio-util\",\"req\":\"^0.7.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^0.25\"}],\"features\":{\"stream\":[],\"unstable\":[]}}", "h2_0.4.13": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"req\":\"^1.0.0\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-sink\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"http\",\"req\":\"^1\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"},{\"name\":\"slab\",\"req\":\"^0.4.2\"},{\"features\":[\"io-util\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\",\"sync\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-rustls\",\"req\":\"^0.26\"},{\"features\":[\"codec\",\"io\"],\"name\":\"tokio-util\",\"req\":\"^0.7.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"}],\"features\":{\"stream\":[],\"unstable\":[]}}", "half_2.7.1": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.4.1\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.4.1\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"crunchy\",\"req\":\"^0.2.2\",\"target\":\"cfg(target_arch = \\\"spirv\\\")\"},{\"kind\":\"dev\",\"name\":\"crunchy\",\"req\":\"^0.2.2\"},{\"default_features\":false,\"features\":[\"libm\"],\"name\":\"num-traits\",\"optional\":true,\"req\":\"^0.2.16\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"thread_rng\"],\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9.0\"},{\"default_features\":false,\"name\":\"rand_distr\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"rkyv\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"derive\",\"simd\"],\"name\":\"zerocopy\",\"req\":\"^0.8.26\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"nightly\":[],\"rand_distr\":[\"dep:rand\",\"dep:rand_distr\"],\"std\":[\"alloc\"],\"use-intrinsics\":[],\"zerocopy\":[]}}", "hashbrown_0.12.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ahash\",\"optional\":true,\"req\":\"^0.7.0\"},{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"bumpalo\",\"optional\":true,\"req\":\"^3.5.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3.1\"},{\"kind\":\"dev\",\"name\":\"fnv\",\"req\":\"^1.0.7\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.25\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"ahash-compile-time-rng\":[\"ahash/compile-time-rng\"],\"default\":[\"ahash\",\"inline-more\"],\"inline-more\":[],\"nightly\":[],\"raw\":[],\"rustc-dep-of-std\":[\"nightly\",\"core\",\"compiler_builtins\",\"alloc\",\"rustc-internal-api\"],\"rustc-internal-api\":[]}}", @@ -892,7 +902,9 @@ "home_0.5.12": "{\"dependencies\":[{\"features\":[\"Win32_Foundation\",\"Win32_UI_Shell\",\"Win32_System_Com\"],\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "hostname_0.4.2": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(any(unix, target_os = \\\"redox\\\"))\"},{\"kind\":\"dev\",\"name\":\"similar-asserts\",\"req\":\"^1.6.1\"},{\"kind\":\"dev\",\"name\":\"version-sync\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"windows-bindgen\",\"req\":\"^0.65\"},{\"name\":\"windows-link\",\"req\":\"^0.2\",\"target\":\"cfg(target_os = \\\"windows\\\")\"}],\"features\":{\"default\":[],\"set\":[]}}", "hound_3.5.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"cpal\",\"req\":\"^0.2.12\"}],\"features\":{}}", + "html5ever_0.26.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\",\"target\":\"cfg(bench)\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"mac\",\"req\":\"^0.1\"},{\"name\":\"markup5ever\",\"req\":\"^0.11\"},{\"kind\":\"build\",\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"kind\":\"build\",\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"extra-traits\",\"full\",\"fold\"],\"kind\":\"build\",\"name\":\"syn\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"typed-arena\",\"req\":\"^1.3.0\"}],\"features\":{}}", "http-body-util_0.1.3": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"http\",\"req\":\"^1\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"macros\",\"rt\",\"sync\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"}],\"features\":{\"channel\":[\"dep:tokio\"],\"default\":[],\"full\":[\"channel\"]}}", + "http-body_0.4.6": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"http\",\"req\":\"^0.2\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"features\":[\"macros\",\"rt\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"}],\"features\":{}}", "http-body_1.0.1": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"http\",\"req\":\"^1\"}],\"features\":{}}", "http-range-header_0.4.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"quickcheck_macros\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.8.3\"}],\"features\":{}}", "http_0.2.12": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"kind\":\"dev\",\"name\":\"indexmap\",\"req\":\"<=1.8\"},{\"name\":\"itoa\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7.0\"},{\"kind\":\"dev\",\"name\":\"seahash\",\"req\":\"^3.0.5\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{}}", @@ -901,8 +913,10 @@ "httpdate_1.0.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"}],\"features\":{}}", "hyper-rustls_0.27.7": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"cfg-if\",\"req\":\"^1\"},{\"name\":\"http\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"hyper\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"client-legacy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"default_features\":false,\"features\":[\"server-auto\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.4\"},{\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"rustls\",\"req\":\"^0.23\"},{\"default_features\":false,\"features\":[\"tls12\"],\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rustls-pemfile\",\"req\":\"^2\"},{\"name\":\"rustls-platform-verifier\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"tokio\",\"req\":\"^1.0\"},{\"features\":[\"io-std\",\"macros\",\"net\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"tokio-rustls\",\"req\":\"^0.26\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"aws-lc-rs\":[\"rustls/aws_lc_rs\"],\"default\":[\"native-tokio\",\"http1\",\"tls12\",\"logging\",\"aws-lc-rs\"],\"fips\":[\"aws-lc-rs\",\"rustls/fips\"],\"http1\":[\"hyper-util/http1\"],\"http2\":[\"hyper-util/http2\"],\"logging\":[\"log\",\"tokio-rustls/logging\",\"rustls/logging\"],\"native-tokio\":[\"rustls-native-certs\"],\"ring\":[\"rustls/ring\"],\"tls12\":[\"tokio-rustls/tls12\",\"rustls/tls12\"],\"webpki-tokio\":[\"webpki-roots\"]}}", "hyper-timeout_0.5.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"name\":\"hyper\",\"req\":\"^1.1\"},{\"features\":[\"http1\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"hyper-tls\",\"req\":\"^0.6\"},{\"features\":[\"client-legacy\",\"http1\"],\"name\":\"hyper-util\",\"req\":\"^0.1.10\"},{\"features\":[\"client-legacy\",\"http1\",\"server\",\"server-graceful\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.10\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"name\":\"tokio\",\"req\":\"^1.35\"},{\"features\":[\"io-std\",\"io-util\",\"macros\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.35\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"}],\"features\":{}}", + "hyper-tls_0.5.0": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"tcp\",\"client\"],\"name\":\"hyper\",\"req\":\"^0.14.2\"},{\"default_features\":false,\"features\":[\"http1\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^0.14.2\"},{\"name\":\"native-tls\",\"req\":\"^0.2.1\"},{\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"io-std\",\"macros\",\"io-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0.0\"},{\"name\":\"tokio-native-tls\",\"req\":\"^0.3\"}],\"features\":{\"vendored\":[\"native-tls/vendored\"]}}", "hyper-tls_0.6.0": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"hyper\",\"req\":\"^1\"},{\"features\":[\"client-legacy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1.0\"},{\"features\":[\"http1\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.0\"},{\"name\":\"native-tls\",\"req\":\"^0.2.1\"},{\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"io-std\",\"macros\",\"io-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0.0\"},{\"name\":\"tokio-native-tls\",\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"}],\"features\":{\"alpn\":[\"native-tls/alpn\"],\"vendored\":[\"native-tls/vendored\"]}}", "hyper-util_0.1.19": "{\"dependencies\":[{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"bytes\",\"req\":\"^1.7.1\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.16\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.16\"},{\"name\":\"http\",\"req\":\"^1.0\"},{\"name\":\"http-body\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1.0\"},{\"name\":\"hyper\",\"req\":\"^1.8.0\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.4.0\"},{\"name\":\"ipnet\",\"optional\":true,\"req\":\"^2.9\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.3\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pnet_datalink\",\"req\":\"^0.35.0\",\"target\":\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"macos\\\"))\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\">=0.5.9, <0.7\"},{\"name\":\"system-configuration\",\"optional\":true,\"req\":\">=0.5, <0.7\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"features\":[\"macros\",\"test-util\",\"signal\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"name\":\"tower-layer\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"tower-service\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"tower-test\",\"req\":\"^0.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"windows-registry\",\"optional\":true,\"req\":\">=0.3, <0.7\",\"target\":\"cfg(windows)\"}],\"features\":{\"__internal_happy_eyeballs_tests\":[],\"client\":[\"hyper/client\",\"tokio/net\",\"dep:tracing\",\"dep:futures-channel\",\"dep:tower-service\"],\"client-legacy\":[\"client\",\"dep:socket2\",\"tokio/sync\",\"dep:libc\",\"dep:futures-util\"],\"client-pool\":[\"client\",\"dep:futures-util\",\"dep:tower-layer\"],\"client-proxy\":[\"client\",\"dep:base64\",\"dep:ipnet\",\"dep:percent-encoding\"],\"client-proxy-system\":[\"dep:system-configuration\",\"dep:windows-registry\"],\"default\":[],\"full\":[\"client\",\"client-legacy\",\"client-pool\",\"client-proxy\",\"client-proxy-system\",\"server\",\"server-auto\",\"server-graceful\",\"service\",\"http1\",\"http2\",\"tokio\",\"tracing\"],\"http1\":[\"hyper/http1\"],\"http2\":[\"hyper/http2\"],\"server\":[\"hyper/server\"],\"server-auto\":[\"server\",\"http1\",\"http2\"],\"server-graceful\":[\"server\",\"tokio/sync\"],\"service\":[\"dep:tower-service\"],\"tokio\":[\"dep:tokio\",\"tokio/rt\",\"tokio/time\"],\"tracing\":[\"dep:tracing\"]}}", + "hyper_0.14.32": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.3.24\"},{\"name\":\"http\",\"req\":\"^0.2\"},{\"name\":\"http-body\",\"req\":\"^0.4\"},{\"name\":\"httparse\",\"req\":\"^1.8\"},{\"name\":\"httpdate\",\"req\":\"^1.0\"},{\"name\":\"itoa\",\"req\":\"^1\"},{\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"matches\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.0\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pnet_datalink\",\"req\":\"^0.27.2\",\"target\":\"cfg(any(target_os = \\\"linux\\\", target_os = \\\"macos\\\"))\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.4\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"all\"],\"name\":\"socket2\",\"optional\":true,\"req\":\">=0.4.7, <0.6.0\"},{\"kind\":\"dev\",\"name\":\"spmc\",\"req\":\"^0.3\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"req\":\"^1.27\"},{\"features\":[\"fs\",\"macros\",\"io-std\",\"io-util\",\"rt\",\"rt-multi-thread\",\"sync\",\"time\",\"test-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.27\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"features\":[\"codec\"],\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.7\"},{\"default_features\":false,\"features\":[\"make\",\"util\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.4\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"url\",\"req\":\"^2.2\"},{\"name\":\"want\",\"req\":\"^0.3\"}],\"features\":{\"__internal_happy_eyeballs_tests\":[],\"backports\":[],\"client\":[],\"default\":[],\"deprecated\":[],\"ffi\":[\"libc\"],\"full\":[\"client\",\"http1\",\"http2\",\"server\",\"stream\",\"runtime\"],\"http1\":[],\"http2\":[\"h2\"],\"nightly\":[],\"runtime\":[\"tcp\",\"tokio/rt\",\"tokio/time\"],\"server\":[],\"stream\":[],\"tcp\":[\"socket2\",\"tokio/net\",\"tokio/rt\",\"tokio/time\"]}}", "hyper_1.8.1": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"optional\":true,\"req\":\"^1.1.2\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"kind\":\"dev\",\"name\":\"form_urlencoded\",\"req\":\"^1\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"features\":[\"sink\"],\"kind\":\"dev\",\"name\":\"futures-channel\",\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3.31\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"alloc\",\"sink\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4.2\"},{\"name\":\"http\",\"req\":\"^1\"},{\"name\":\"http-body\",\"req\":\"^1\"},{\"name\":\"http-body-util\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"name\":\"httparse\",\"optional\":true,\"req\":\"^1.9\"},{\"name\":\"httpdate\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"itoa\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"pin-project-lite\",\"optional\":true,\"req\":\"^0.2.4\"},{\"kind\":\"dev\",\"name\":\"pin-project-lite\",\"req\":\"^0.2.4\"},{\"name\":\"pin-utils\",\"optional\":true,\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"pretty_env_logger\",\"req\":\"^0.5\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"optional\":true,\"req\":\"^1.12\"},{\"kind\":\"dev\",\"name\":\"spmc\",\"req\":\"^0.3\"},{\"features\":[\"sync\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"fs\",\"macros\",\"net\",\"io-std\",\"io-util\",\"rt\",\"rt-multi-thread\",\"sync\",\"time\",\"test-util\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-test\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"tokio-util\",\"req\":\"^0.7.10\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"want\",\"optional\":true,\"req\":\"^0.3\"}],\"features\":{\"capi\":[],\"client\":[\"dep:want\",\"dep:pin-project-lite\",\"dep:smallvec\"],\"default\":[],\"ffi\":[\"dep:http-body-util\",\"dep:futures-util\"],\"full\":[\"client\",\"http1\",\"http2\",\"server\"],\"http1\":[\"dep:atomic-waker\",\"dep:futures-channel\",\"dep:futures-core\",\"dep:httparse\",\"dep:itoa\",\"dep:pin-utils\"],\"http2\":[\"dep:futures-channel\",\"dep:futures-core\",\"dep:h2\"],\"nightly\":[],\"server\":[\"dep:httpdate\",\"dep:pin-project-lite\",\"dep:smallvec\"],\"tracing\":[\"dep:tracing\"]}}", "i18n-config_0.4.8": "{\"dependencies\":[{\"name\":\"basic-toml\",\"req\":\"^0.1\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"features\":[\"serde\"],\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{}}", "i18n-embed-fl_0.9.4": "{\"dependencies\":[{\"name\":\"dashmap\",\"optional\":true,\"req\":\"^6.0\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"name\":\"find-crate\",\"req\":\"^0.6\"},{\"name\":\"fluent\",\"req\":\"^0.16\"},{\"name\":\"fluent-syntax\",\"req\":\"^0.11\"},{\"name\":\"i18n-config\",\"req\":\"^0.4.7\"},{\"features\":[\"fluent-system\",\"filesystem-assets\"],\"name\":\"i18n-embed\",\"req\":\"^0.15.4\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4\"},{\"name\":\"proc-macro-error2\",\"req\":\"^2.0.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rust-embed\",\"req\":\"^8.0\"},{\"name\":\"strsim\",\"req\":\"^0.11\"},{\"features\":[\"derive\",\"proc-macro\",\"parsing\",\"printing\",\"extra-traits\",\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"name\":\"unic-langid\",\"req\":\"^0.9\"}],\"features\":{\"dashmap\":[\"dep:dashmap\"]}}", @@ -991,8 +1005,10 @@ "lsp-types_0.94.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^1.0.1\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"req\":\"^1.0.34\"},{\"name\":\"serde_json\",\"req\":\"^1.0.50\"},{\"name\":\"serde_repr\",\"req\":\"^0.1\"},{\"features\":[\"serde\"],\"name\":\"url\",\"req\":\"^2.0.0\"}],\"features\":{\"default\":[],\"proposed\":[]}}", "lzma-rs_0.3.0": "{\"dependencies\":[{\"name\":\"byteorder\",\"req\":\"^1.4.3\"},{\"name\":\"crc\",\"req\":\"^3.0.0\"},{\"name\":\"env_logger\",\"optional\":true,\"req\":\"^0.9.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"},{\"kind\":\"dev\",\"name\":\"rust-lzma\",\"req\":\"^0.5\"}],\"features\":{\"enable_logging\":[\"env_logger\",\"log\"],\"raw_decoder\":[],\"stream\":[]}}", "lzma-sys_0.1.20": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.34\"},{\"name\":\"libc\",\"req\":\"^0.2.51\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.14\"}],\"features\":{\"static\":[]}}", + "mac_0.1.1": "{\"dependencies\":[],\"features\":{}}", "mach2_0.4.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_os = \\\"macos\\\", target_os = \\\"ios\\\"))\"}],\"features\":{\"default\":[],\"unstable\":[]}}", "maplit_1.0.2": "{\"dependencies\":[],\"features\":{}}", + "markup5ever_0.11.0": "{\"dependencies\":[{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"phf\",\"req\":\"^0.10\"},{\"kind\":\"build\",\"name\":\"phf_codegen\",\"req\":\"^0.10\"},{\"name\":\"string_cache\",\"req\":\"^0.8\"},{\"kind\":\"build\",\"name\":\"string_cache_codegen\",\"req\":\"^0.5.1\"},{\"name\":\"tendril\",\"req\":\"^0.4\"}],\"features\":{}}", "matchers_0.2.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"syntax\",\"dfa-build\",\"dfa-search\"],\"name\":\"regex-automata\",\"req\":\"^0.4\"}],\"features\":{\"unicode\":[\"regex-automata/unicode\"]}}", "matchit_0.8.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-router\",\"req\":\"^0.2.7\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.4\"},{\"kind\":\"dev\",\"name\":\"gonzales\",\"req\":\"^0.0.3-beta\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^0.14\"},{\"kind\":\"dev\",\"name\":\"path-tree\",\"req\":\"^0.2.2\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.5.4\"},{\"kind\":\"dev\",\"name\":\"route-recognizer\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"routefinder\",\"req\":\"^0.5.2\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"make\",\"util\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.4\"}],\"features\":{\"__test_helpers\":[],\"default\":[]}}", "matchit_0.9.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-router\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"gonzales\",\"req\":\"^0.0.3-beta\"},{\"kind\":\"dev\",\"name\":\"http-body-util\",\"req\":\"^0.1\"},{\"features\":[\"http1\",\"server\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1\"},{\"features\":[\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"path-tree\",\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"route-recognizer\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"routefinder\",\"req\":\"^0.5\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"make\",\"util\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5.2\"},{\"kind\":\"dev\",\"name\":\"wayfind\",\"req\":\"^0.8\"}],\"features\":{\"__test_helpers\":[],\"default\":[]}}", @@ -1084,17 +1100,26 @@ "parking_2.2.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"easy-parallel\",\"req\":\"^3.0.0\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\",\"target\":\"cfg(loom)\"}],\"features\":{}}", "parking_lot_0.12.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.3\"},{\"name\":\"lock_api\",\"req\":\"^0.4.14\"},{\"name\":\"parking_lot_core\",\"req\":\"^0.9.12\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.3\"}],\"features\":{\"arc_lock\":[\"lock_api/arc_lock\"],\"deadlock_detection\":[\"parking_lot_core/deadlock_detection\"],\"default\":[],\"hardware-lock-elision\":[],\"nightly\":[\"parking_lot_core/nightly\",\"lock_api/nightly\"],\"owning_ref\":[\"lock_api/owning_ref\"],\"send_guard\":[],\"serde\":[\"lock_api/serde\"]}}", "parking_lot_core_0.9.12": "{\"dependencies\":[{\"name\":\"backtrace\",\"optional\":true,\"req\":\"^0.3.60\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"libc\",\"req\":\"^0.2.95\",\"target\":\"cfg(unix)\"},{\"name\":\"petgraph\",\"optional\":true,\"req\":\"^0.6.0\"},{\"name\":\"redox_syscall\",\"req\":\"^0.5\",\"target\":\"cfg(target_os = \\\"redox\\\")\"},{\"name\":\"smallvec\",\"req\":\"^1.6.1\"},{\"name\":\"windows-link\",\"req\":\"^0.2.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"deadlock_detection\":[\"petgraph\",\"backtrace\"],\"nightly\":[]}}", + "password-hash_0.4.2": "{\"dependencies\":[{\"name\":\"base64ct\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2\"}],\"features\":{\"alloc\":[\"base64ct/alloc\"],\"default\":[\"rand_core\"],\"std\":[\"alloc\",\"base64ct/std\",\"rand_core/std\"]}}", "paste_1.0.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"paste-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1.0\"},{\"features\":[\"diff\"],\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.49\"}],\"features\":{}}", "pastey_0.2.1": "{\"dependencies\":[],\"features\":{}}", "path-absolutize_3.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"path-dedot\",\"req\":\"^3.1.1\"},{\"kind\":\"dev\",\"name\":\"slash-formatter\",\"req\":\"^3\",\"target\":\"cfg(windows)\"}],\"features\":{\"lazy_static_cache\":[\"path-dedot/lazy_static_cache\"],\"once_cell_cache\":[\"path-dedot/once_cell_cache\"],\"unsafe_cache\":[\"path-dedot/unsafe_cache\"],\"use_unix_paths_on_wasm\":[\"path-dedot/use_unix_paths_on_wasm\"]}}", "path-dedot_3.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"lazy_static\",\"optional\":true,\"req\":\"^1.4\"},{\"name\":\"once_cell\",\"req\":\"^1.4\"}],\"features\":{\"lazy_static_cache\":[\"lazy_static\"],\"once_cell_cache\":[],\"unsafe_cache\":[],\"use_unix_paths_on_wasm\":[]}}", "pathdiff_0.2.3": "{\"dependencies\":[{\"name\":\"camino\",\"optional\":true,\"req\":\"^1.0.5\"},{\"kind\":\"dev\",\"name\":\"cfg-if\",\"req\":\"^1.0.0\"}],\"features\":{}}", + "pbkdf2_0.11.0": "{\"dependencies\":[{\"features\":[\"mac\"],\"name\":\"digest\",\"req\":\"^0.10.3\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12\"},{\"kind\":\"dev\",\"name\":\"hmac\",\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"rand_core\"],\"name\":\"password-hash\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.2\"},{\"default_features\":false,\"name\":\"sha1\",\"optional\":true,\"package\":\"sha-1\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha1\",\"package\":\"sha-1\",\"req\":\"^0.10\"},{\"default_features\":false,\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"streebog\",\"req\":\"^0.10\"}],\"features\":{\"default\":[\"simple\"],\"parallel\":[\"rayon\",\"std\"],\"simple\":[\"hmac\",\"password-hash\",\"sha2\"],\"std\":[\"password-hash/std\"]}}", "pbkdf2_0.12.2": "{\"dependencies\":[{\"features\":[\"mac\"],\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12\"},{\"kind\":\"dev\",\"name\":\"hmac\",\"req\":\"^0.12\"},{\"default_features\":false,\"features\":[\"rand_core\"],\"name\":\"password-hash\",\"optional\":true,\"req\":\"^0.5\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.7\"},{\"default_features\":false,\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha1\",\"req\":\"^0.10\"},{\"default_features\":false,\"name\":\"sha2\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"streebog\",\"req\":\"^0.10\"}],\"features\":{\"default\":[\"hmac\"],\"parallel\":[\"rayon\",\"std\"],\"simple\":[\"hmac\",\"password-hash\",\"sha2\"],\"std\":[\"password-hash/std\"]}}", "pem-rfc7468_0.7.0": "{\"dependencies\":[{\"name\":\"base64ct\",\"req\":\"^1.4\"}],\"features\":{\"alloc\":[\"base64ct/alloc\"],\"std\":[\"alloc\",\"base64ct/std\"]}}", "pem_3.0.6": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"base64\",\"req\":\"^0.22.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.0\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"}],\"features\":{\"default\":[\"std\"],\"serde\":[\"dep:serde_core\"],\"std\":[\"base64/std\",\"serde_core?/std\"]}}", "percent-encoding_2.3.2": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "petgraph_0.6.5": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.7.2\"},{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.3\"},{\"kind\":\"dev\",\"name\":\"defmac\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"name\":\"fixedbitset\",\"req\":\"^0.4.0\"},{\"kind\":\"dev\",\"name\":\"fxhash\",\"req\":\"^0.2.1\"},{\"name\":\"indexmap\",\"req\":\"^2.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.12.1\"},{\"kind\":\"dev\",\"name\":\"odds\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.5.5\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.5.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"all\":[\"unstable\",\"quickcheck\",\"matrix_graph\",\"stable_graph\",\"graphmap\",\"rayon\"],\"default\":[\"graphmap\",\"stable_graph\",\"matrix_graph\"],\"generate\":[],\"graphmap\":[],\"matrix_graph\":[],\"rayon\":[\"dep:rayon\",\"indexmap/rayon\"],\"serde-1\":[\"serde\",\"serde_derive\"],\"stable_graph\":[],\"unstable\":[\"generate\"]}}", "petgraph_0.8.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.7.2\"},{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.3\"},{\"kind\":\"dev\",\"name\":\"defmac\",\"req\":\"^0.2.1\"},{\"name\":\"dot-parser\",\"optional\":true,\"req\":\"^0.5.1\"},{\"name\":\"dot-parser-macros\",\"optional\":true,\"req\":\"^0.5.1\"},{\"default_features\":false,\"name\":\"fixedbitset\",\"req\":\"^0.5.7\"},{\"kind\":\"dev\",\"name\":\"fxhash\",\"req\":\"^0.2.1\"},{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"req\":\"^0.15.0\"},{\"default_features\":false,\"name\":\"indexmap\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.12.1\"},{\"kind\":\"dev\",\"name\":\"odds\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"quickcheck\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.5.5\"},{\"name\":\"rayon\",\"optional\":true,\"req\":\"^1.5.3\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"all\":[\"unstable\",\"quickcheck\",\"matrix_graph\",\"stable_graph\",\"graphmap\",\"rayon\",\"dot_parser\"],\"default\":[\"std\",\"graphmap\",\"stable_graph\",\"matrix_graph\"],\"dot_parser\":[\"std\",\"dep:dot-parser\",\"dep:dot-parser-macros\"],\"generate\":[],\"graphmap\":[],\"matrix_graph\":[],\"quickcheck\":[\"std\",\"dep:quickcheck\",\"graphmap\",\"stable_graph\"],\"rayon\":[\"std\",\"dep:rayon\",\"indexmap/rayon\",\"hashbrown/rayon\"],\"serde-1\":[\"serde\",\"serde_derive\"],\"stable_graph\":[\"serde?/alloc\"],\"std\":[\"indexmap/std\"],\"unstable\":[\"generate\"]}}", + "phf_0.10.1": "{\"dependencies\":[{\"name\":\"phf_macros\",\"optional\":true,\"req\":\"^0.10.0\"},{\"default_features\":false,\"name\":\"phf_shared\",\"req\":\"^0.10.0\"},{\"name\":\"proc-macro-hack\",\"optional\":true,\"req\":\"^0.5.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"macros\":[\"phf_macros\",\"proc-macro-hack\"],\"std\":[\"phf_shared/std\"],\"uncased\":[\"phf_shared/uncased\"],\"unicase\":[\"phf_shared/unicase\"]}}", + "phf_0.11.3": "{\"dependencies\":[{\"name\":\"phf_macros\",\"optional\":true,\"req\":\"^0.11.3\"},{\"default_features\":false,\"name\":\"phf_shared\",\"req\":\"^0.11.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"macros\":[\"phf_macros\"],\"std\":[\"phf_shared/std\"],\"uncased\":[\"phf_shared/uncased\"],\"unicase\":[\"phf_macros?/unicase\",\"phf_shared/unicase\"]}}", + "phf_codegen_0.10.0": "{\"dependencies\":[{\"name\":\"phf_generator\",\"req\":\"^0.10.0\"},{\"name\":\"phf_shared\",\"req\":\"^0.10.0\"}],\"features\":{}}", + "phf_generator_0.10.0": "{\"dependencies\":[{\"name\":\"criterion\",\"optional\":true,\"req\":\"=0.3.4\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"=0.3.4\"},{\"default_features\":false,\"name\":\"phf_shared\",\"req\":\"^0.10.0\"},{\"features\":[\"small_rng\"],\"name\":\"rand\",\"req\":\"^0.8\"}],\"features\":{}}", + "phf_generator_0.11.3": "{\"dependencies\":[{\"name\":\"criterion\",\"optional\":true,\"req\":\"^0.3.6\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"name\":\"phf_shared\",\"req\":\"^0.11.2\"},{\"default_features\":false,\"features\":[\"small_rng\"],\"name\":\"rand\",\"req\":\"^0.8\"}],\"features\":{}}", + "phf_macros_0.11.3": "{\"dependencies\":[{\"name\":\"phf_generator\",\"req\":\"^0.11.1\"},{\"default_features\":false,\"name\":\"phf_shared\",\"req\":\"^0.11.2\"},{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2\"},{\"name\":\"unicase_\",\"optional\":true,\"package\":\"unicase\",\"req\":\"^2.4.0\"}],\"features\":{\"unicase\":[\"unicase_\",\"phf_shared/unicase\"]}}", + "phf_shared_0.10.0": "{\"dependencies\":[{\"name\":\"siphasher\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"uncased\",\"optional\":true,\"req\":\"^0.9.6\"},{\"name\":\"unicase\",\"optional\":true,\"req\":\"^2.4.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "phf_shared_0.11.3": "{\"dependencies\":[{\"name\":\"siphasher\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"uncased\",\"optional\":true,\"req\":\"^0.9.9\"},{\"name\":\"unicase\",\"optional\":true,\"req\":\"^2.4.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "pin-project-internal_1.1.10": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"name\":\"quote\",\"req\":\"^1.0.25\"},{\"default_features\":false,\"features\":[\"parsing\",\"printing\",\"clone-impls\",\"proc-macro\",\"full\",\"visit-mut\"],\"name\":\"syn\",\"req\":\"^2.0.1\"}],\"features\":{}}", "pin-project-lite_0.2.16": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1\"}],\"features\":{}}", @@ -1113,6 +1138,7 @@ "portable-pty_0.9.0": "{\"dependencies\":[{\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"name\":\"bitflags\",\"req\":\"^1.3\",\"target\":\"cfg(windows)\"},{\"name\":\"downcast-rs\",\"req\":\"^1.0\"},{\"name\":\"filedescriptor\",\"req\":\"^0.8.3\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"lazy_static\",\"req\":\"^1.4\",\"target\":\"cfg(windows)\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"features\":[\"term\",\"fs\"],\"name\":\"nix\",\"req\":\"^0.28\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serial2\",\"req\":\"^0.2\"},{\"name\":\"shared_library\",\"req\":\"^0.1\",\"target\":\"cfg(windows)\"},{\"name\":\"shell-words\",\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"smol\",\"req\":\"^2.0\"},{\"features\":[\"winuser\",\"consoleapi\",\"handleapi\",\"fileapi\",\"namedpipeapi\",\"synchapi\"],\"name\":\"winapi\",\"req\":\"^0.3\",\"target\":\"cfg(windows)\"},{\"name\":\"winreg\",\"req\":\"^0.10\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[],\"serde_support\":[\"serde\",\"serde_derive\"]}}", "potential_utf_0.1.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"default_features\":false,\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"default_features\":false,\"name\":\"writeable\",\"optional\":true,\"req\":\"^0.6.0\"},{\"default_features\":false,\"name\":\"zerovec\",\"optional\":true,\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\",\"writeable/alloc\",\"zerovec?/alloc\"],\"databake\":[\"dep:databake\"],\"default\":[\"alloc\"],\"serde\":[\"dep:serde_core\"],\"writeable\":[\"dep:writeable\"],\"zerovec\":[\"dep:zerovec\"]}}", "powerfmt_0.2.0": "{\"dependencies\":[{\"name\":\"powerfmt-macros\",\"optional\":true,\"req\":\"=0.1.0\"}],\"features\":{\"alloc\":[],\"default\":[\"std\",\"macros\"],\"macros\":[\"dep:powerfmt-macros\"],\"std\":[\"alloc\"]}}", + "ppt-rs_0.2.6": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"clock\",\"std\"],\"name\":\"chrono\",\"req\":\"^0.4\"},{\"features\":[\"derive\"],\"name\":\"clap\",\"req\":\"^4.5\"},{\"name\":\"pulldown-cmark\",\"req\":\"^0.10\"},{\"name\":\"regex\",\"req\":\"^1.10\"},{\"features\":[\"blocking\"],\"name\":\"reqwest\",\"optional\":true,\"req\":\"^0.11\"},{\"name\":\"scraper\",\"optional\":true,\"req\":\"^0.18\"},{\"name\":\"syntect\",\"req\":\"^5.2\"},{\"name\":\"thiserror\",\"req\":\"^1.0\"},{\"name\":\"url\",\"optional\":true,\"req\":\"^2.5\"},{\"features\":[\"v4\"],\"name\":\"uuid\",\"req\":\"^1.0\"},{\"name\":\"xml-rs\",\"req\":\"^0.8\"},{\"name\":\"zip\",\"req\":\"^0.6\"}],\"features\":{\"default\":[\"web2ppt\"],\"web2ppt\":[\"reqwest\",\"scraper\",\"url\"]}}", "ppv-lite86_0.2.21": "{\"dependencies\":[{\"features\":[\"simd\"],\"name\":\"zerocopy\",\"req\":\"^0.8.23\"}],\"features\":{\"default\":[\"std\"],\"no_simd\":[],\"simd\":[],\"std\":[]}}", "precomputed-hash_0.1.1": "{\"dependencies\":[],\"features\":{}}", "predicates-core_1.0.9": "{\"dependencies\":[],\"features\":{}}", @@ -1179,6 +1205,7 @@ "regex-syntax_0.6.29": "{\"dependencies\":[],\"features\":{\"default\":[\"unicode\"],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\"],\"unicode-age\":[],\"unicode-bool\":[],\"unicode-case\":[],\"unicode-gencat\":[],\"unicode-perl\":[],\"unicode-script\":[],\"unicode-segment\":[]}}", "regex-syntax_0.8.8": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3.0\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"default\":[\"std\",\"unicode\"],\"std\":[],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\"],\"unicode-age\":[],\"unicode-bool\":[],\"unicode-case\":[],\"unicode-gencat\":[],\"unicode-perl\":[],\"unicode-script\":[],\"unicode-segment\":[]}}", "regex_1.12.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aho-corasick\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.69\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"atty\",\"humantime\",\"termcolor\"],\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.9.3\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.6.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"default_features\":false,\"features\":[\"alloc\",\"syntax\",\"meta\",\"nfa-pikevm\"],\"name\":\"regex-automata\",\"req\":\"^0.4.12\"},{\"default_features\":false,\"name\":\"regex-syntax\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"regex-test\",\"req\":\"^0.1.0\"}],\"features\":{\"default\":[\"std\",\"perf\",\"unicode\",\"regex-syntax/default\"],\"logging\":[\"aho-corasick?/logging\",\"memchr?/logging\",\"regex-automata/logging\"],\"pattern\":[],\"perf\":[\"perf-cache\",\"perf-dfa\",\"perf-onepass\",\"perf-backtrack\",\"perf-inline\",\"perf-literal\"],\"perf-backtrack\":[\"regex-automata/nfa-backtrack\"],\"perf-cache\":[],\"perf-dfa\":[\"regex-automata/hybrid\"],\"perf-dfa-full\":[\"regex-automata/dfa-build\",\"regex-automata/dfa-search\"],\"perf-inline\":[\"regex-automata/perf-inline\"],\"perf-literal\":[\"dep:aho-corasick\",\"dep:memchr\",\"regex-automata/perf-literal\"],\"perf-onepass\":[\"regex-automata/dfa-onepass\"],\"std\":[\"aho-corasick?/std\",\"memchr?/std\",\"regex-automata/std\",\"regex-syntax/std\"],\"unicode\":[\"unicode-age\",\"unicode-bool\",\"unicode-case\",\"unicode-gencat\",\"unicode-perl\",\"unicode-script\",\"unicode-segment\",\"regex-automata/unicode\",\"regex-syntax/unicode\"],\"unicode-age\":[\"regex-automata/unicode-age\",\"regex-syntax/unicode-age\"],\"unicode-bool\":[\"regex-automata/unicode-bool\",\"regex-syntax/unicode-bool\"],\"unicode-case\":[\"regex-automata/unicode-case\",\"regex-syntax/unicode-case\"],\"unicode-gencat\":[\"regex-automata/unicode-gencat\",\"regex-syntax/unicode-gencat\"],\"unicode-perl\":[\"regex-automata/unicode-perl\",\"regex-automata/unicode-word-boundary\",\"regex-syntax/unicode-perl\"],\"unicode-script\":[\"regex-automata/unicode-script\",\"regex-syntax/unicode-script\"],\"unicode-segment\":[\"regex-automata/unicode-segment\",\"regex-syntax/unicode-segment\"],\"unstable\":[\"pattern\"],\"use_std\":[\"std\"]}}", + "reqwest_0.11.27": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"tokio\"],\"name\":\"async-compression\",\"optional\":true,\"req\":\"^0.4.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"base64\",\"req\":\"^0.21\"},{\"kind\":\"dev\",\"name\":\"brotli_crate\",\"package\":\"brotli\",\"req\":\"^3.3.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"bytes\",\"req\":\"^1.0\"},{\"name\":\"cookie_crate\",\"optional\":true,\"package\":\"cookie\",\"req\":\"^0.17.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"cookie_store\",\"optional\":true,\"req\":\"^0.20.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"encoding_rs\",\"req\":\"^0.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.0\"},{\"default_features\":false,\"name\":\"futures-util\",\"req\":\"^0.3.0\"},{\"default_features\":false,\"features\":[\"std\",\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h2\",\"req\":\"^0.3.14\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3\",\"optional\":true,\"req\":\"^0.0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3-quinn\",\"optional\":true,\"req\":\"^0.0.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"tokio-runtime\"],\"name\":\"hickory-resolver\",\"optional\":true,\"req\":\"^0.24\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"http\",\"req\":\"^0.2\"},{\"name\":\"http-body\",\"req\":\"^0.4.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"tcp\",\"http1\",\"http2\",\"client\",\"runtime\"],\"name\":\"hyper\",\"req\":\"^0.14.21\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"tcp\",\"stream\",\"http1\",\"http2\",\"client\",\"server\",\"runtime\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^0.14\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"hyper-rustls\",\"optional\":true,\"req\":\"^0.24.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"hyper-tls\",\"optional\":true,\"req\":\"^0.5\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"ipnet\",\"req\":\"^2.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"js-sys\",\"req\":\"^0.3.45\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"libflate\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"log\",\"req\":\"^0.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"mime\",\"req\":\"^0.3.16\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0\"},{\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"^0.2.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"once_cell\",\"req\":\"^1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"percent-encoding\",\"req\":\"^2.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"tls-rustls\",\"ring\",\"runtime-tokio\"],\"name\":\"quinn\",\"optional\":true,\"req\":\"^0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"dangerous_configuration\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.21.6\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.6\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"rustls-pemfile\",\"optional\":true,\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde_json\",\"req\":\"^1.0\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_urlencoded\",\"req\":\"^0.7.1\"},{\"name\":\"sync_wrapper\",\"req\":\"^0.1.2\"},{\"name\":\"system-configuration\",\"req\":\"^0.5.1\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"default_features\":false,\"features\":[\"net\",\"time\"],\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tokio-native-tls\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"^0.24\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tokio-socks\",\"optional\":true,\"req\":\"^0.5.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"codec\",\"io\"],\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tower-service\",\"req\":\"^0.3\"},{\"name\":\"url\",\"req\":\"^2.2\"},{\"name\":\"wasm-bindgen\",\"req\":\"^0.2.68\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"serde-serialize\"],\"kind\":\"dev\",\"name\":\"wasm-bindgen\",\"req\":\"^0.2.68\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-bindgen-futures\",\"req\":\"^0.4.18\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-streams\",\"optional\":true,\"req\":\"^0.4\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"AbortController\",\"AbortSignal\",\"Headers\",\"Request\",\"RequestInit\",\"RequestMode\",\"Response\",\"Window\",\"FormData\",\"Blob\",\"BlobPropertyBag\",\"ServiceWorkerGlobalScope\",\"RequestCredentials\",\"File\",\"ReadableStream\"],\"name\":\"web-sys\",\"req\":\"^0.3.25\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"winreg\",\"req\":\"^0.50.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"__internal_proxy_sys_no_cache\":[],\"__rustls\":[\"hyper-rustls\",\"tokio-rustls\",\"rustls\",\"__tls\"],\"__tls\":[\"dep:rustls-pemfile\"],\"blocking\":[\"futures-util/io\",\"tokio/sync\"],\"brotli\":[\"async-compression\",\"async-compression/brotli\",\"tokio-util\"],\"cookies\":[\"cookie_crate\",\"cookie_store\"],\"default\":[\"default-tls\"],\"default-tls\":[\"hyper-tls\",\"native-tls-crate\",\"__tls\",\"tokio-native-tls\"],\"deflate\":[\"async-compression\",\"async-compression/zlib\",\"tokio-util\"],\"gzip\":[\"async-compression\",\"async-compression/gzip\",\"tokio-util\"],\"hickory-dns\":[\"hickory-resolver\"],\"http3\":[\"rustls-tls-manual-roots\",\"h3\",\"h3-quinn\",\"quinn\",\"futures-channel\"],\"json\":[\"serde_json\"],\"multipart\":[\"mime_guess\"],\"native-tls\":[\"default-tls\"],\"native-tls-alpn\":[\"native-tls\",\"native-tls-crate/alpn\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\"],\"rustls-tls\":[\"rustls-tls-webpki-roots\"],\"rustls-tls-manual-roots\":[\"__rustls\"],\"rustls-tls-native-roots\":[\"rustls-native-certs\",\"__rustls\"],\"rustls-tls-webpki-roots\":[\"webpki-roots\",\"__rustls\"],\"socks\":[\"tokio-socks\"],\"stream\":[\"tokio/fs\",\"tokio-util\",\"wasm-streams\"],\"trust-dns\":[\"hickory-dns\"]}}", "reqwest_0.12.28": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"brotli_crate\",\"package\":\"brotli\",\"req\":\"^8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"bytes\",\"req\":\"^1.2\"},{\"name\":\"cookie_crate\",\"optional\":true,\"package\":\"cookie\",\"req\":\"^0.18.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"cookie_store\",\"optional\":true,\"req\":\"^0.22.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"encoding_rs\",\"optional\":true,\"req\":\"^0.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0.13\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3.28\"},{\"default_features\":false,\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.28\"},{\"default_features\":false,\"features\":[\"std\",\"alloc\"],\"kind\":\"dev\",\"name\":\"futures-util\",\"req\":\"^0.3.28\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h2\",\"optional\":true,\"req\":\"^0.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3\",\"optional\":true,\"req\":\"^0.0.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"h3-quinn\",\"optional\":true,\"req\":\"^0.0.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"tokio\"],\"name\":\"hickory-resolver\",\"optional\":true,\"req\":\"^0.25\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"http\",\"req\":\"^1.1\"},{\"name\":\"http-body\",\"req\":\"^1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"http-body-util\",\"req\":\"^0.1.2\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"client\"],\"name\":\"hyper\",\"req\":\"^1.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"http1\",\"http2\",\"client\",\"server\"],\"kind\":\"dev\",\"name\":\"hyper\",\"req\":\"^1.1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"http1\",\"tls12\"],\"name\":\"hyper-rustls\",\"optional\":true,\"req\":\"^0.27.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"hyper-tls\",\"optional\":true,\"req\":\"^0.6\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"client\",\"client-legacy\",\"client-proxy\",\"tokio\"],\"name\":\"hyper-util\",\"req\":\"^0.1.12\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"http1\",\"http2\",\"client\",\"client-legacy\",\"server-auto\",\"server-graceful\",\"tokio\"],\"kind\":\"dev\",\"name\":\"hyper-util\",\"req\":\"^0.1.12\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"js-sys\",\"req\":\"^0.3.77\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0\"},{\"name\":\"log\",\"req\":\"^0.4.17\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"mime\",\"optional\":true,\"req\":\"^0.3.16\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"name\":\"mime_guess\",\"optional\":true,\"req\":\"^2.0\"},{\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"^0.2.10\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.18\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"percent-encoding\",\"req\":\"^2.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2.11\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"rustls\",\"runtime-tokio\"],\"name\":\"quinn\",\"optional\":true,\"req\":\"^0.11.1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"std\",\"tls12\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"^0.23.4\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"^0.8.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"features\":[\"std\"],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"^1.9.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"serde_json\",\"req\":\"^1.0\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_urlencoded\",\"req\":\"^0.7.1\"},{\"features\":[\"futures\"],\"name\":\"sync_wrapper\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"net\",\"time\"],\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"macros\",\"rt-multi-thread\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tokio-native-tls\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"tls12\"],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"^0.26\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"io\"],\"name\":\"tokio-util\",\"optional\":true,\"req\":\"^0.7.9\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"retry\",\"timeout\",\"util\"],\"name\":\"tower\",\"req\":\"^0.5.2\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"limit\"],\"kind\":\"dev\",\"name\":\"tower\",\"req\":\"^0.5.2\"},{\"default_features\":false,\"features\":[\"follow-redirect\"],\"name\":\"tower-http\",\"req\":\"^0.6.8\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"tower-service\",\"req\":\"^0.3\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"name\":\"url\",\"req\":\"^2.4\"},{\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"serde-serialize\"],\"kind\":\"dev\",\"name\":\"wasm-bindgen\",\"req\":\"^0.2.89\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-bindgen-futures\",\"req\":\"^0.4.18\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"wasm-streams\",\"optional\":true,\"req\":\"^0.4\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"features\":[\"AbortController\",\"AbortSignal\",\"Headers\",\"Request\",\"RequestInit\",\"RequestMode\",\"Response\",\"Window\",\"FormData\",\"Blob\",\"BlobPropertyBag\",\"ServiceWorkerGlobalScope\",\"RequestCredentials\",\"File\",\"ReadableStream\",\"RequestCache\"],\"name\":\"web-sys\",\"req\":\"^0.3.28\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"kind\":\"dev\",\"name\":\"zstd_crate\",\"package\":\"zstd\",\"req\":\"^0.13\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"}],\"features\":{\"__rustls\":[\"dep:hyper-rustls\",\"dep:tokio-rustls\",\"dep:rustls\",\"__tls\"],\"__rustls-ring\":[\"hyper-rustls?/ring\",\"tokio-rustls?/ring\",\"rustls?/ring\",\"quinn?/ring\"],\"__tls\":[\"dep:rustls-pki-types\",\"tokio/io-util\"],\"blocking\":[\"dep:futures-channel\",\"futures-channel?/sink\",\"dep:futures-util\",\"futures-util?/io\",\"futures-util?/sink\",\"tokio/sync\"],\"brotli\":[\"tower-http/decompression-br\"],\"charset\":[\"dep:encoding_rs\",\"dep:mime\"],\"cookies\":[\"dep:cookie_crate\",\"dep:cookie_store\"],\"default\":[\"default-tls\",\"charset\",\"http2\",\"system-proxy\"],\"default-tls\":[\"dep:hyper-tls\",\"dep:native-tls-crate\",\"__tls\",\"dep:tokio-native-tls\"],\"deflate\":[\"tower-http/decompression-deflate\"],\"gzip\":[\"tower-http/decompression-gzip\"],\"hickory-dns\":[\"dep:hickory-resolver\",\"dep:once_cell\"],\"http2\":[\"h2\",\"hyper/http2\",\"hyper-util/http2\",\"hyper-rustls?/http2\"],\"http3\":[\"rustls-tls-manual-roots\",\"dep:h3\",\"dep:h3-quinn\",\"dep:quinn\",\"tokio/macros\"],\"json\":[\"dep:serde_json\"],\"macos-system-configuration\":[\"system-proxy\"],\"multipart\":[\"dep:mime_guess\",\"dep:futures-util\"],\"native-tls\":[\"default-tls\"],\"native-tls-alpn\":[\"native-tls\",\"native-tls-crate?/alpn\",\"hyper-tls?/alpn\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate?/vendored\"],\"rustls-tls\":[\"rustls-tls-webpki-roots\"],\"rustls-tls-manual-roots\":[\"rustls-tls-manual-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-manual-roots-no-provider\":[\"__rustls\"],\"rustls-tls-native-roots\":[\"rustls-tls-native-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-native-roots-no-provider\":[\"dep:rustls-native-certs\",\"hyper-rustls?/native-tokio\",\"__rustls\"],\"rustls-tls-no-provider\":[\"rustls-tls-manual-roots-no-provider\"],\"rustls-tls-webpki-roots\":[\"rustls-tls-webpki-roots-no-provider\",\"__rustls-ring\"],\"rustls-tls-webpki-roots-no-provider\":[\"dep:webpki-roots\",\"hyper-rustls?/webpki-tokio\",\"__rustls\"],\"socks\":[],\"stream\":[\"tokio/fs\",\"dep:futures-util\",\"dep:tokio-util\",\"dep:wasm-streams\"],\"system-proxy\":[\"hyper-util/client-proxy-system\"],\"trust-dns\":[],\"zstd\":[\"tower-http/decompression-zstd\"]}}", "resolv-conf_0.7.6": "{\"dependencies\":[],\"features\":{\"system\":[]}}", "ring_0.17.14": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.2.8\"},{\"default_features\":false,\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"getrandom\",\"req\":\"^0.2.10\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.148\",\"target\":\"cfg(all(any(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), all(target_arch = \\\"arm\\\", target_endian = \\\"little\\\")), any(target_os = \\\"android\\\", target_os = \\\"linux\\\")))\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.155\",\"target\":\"cfg(all(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), target_vendor = \\\"apple\\\", any(target_os = \\\"ios\\\", target_os = \\\"macos\\\", target_os = \\\"tvos\\\", target_os = \\\"visionos\\\", target_os = \\\"watchos\\\")))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.148\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.37\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", target_os = \\\"unknown\\\"))\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Threading\"],\"name\":\"windows-sys\",\"req\":\"^0.52\",\"target\":\"cfg(all(all(target_arch = \\\"aarch64\\\", target_endian = \\\"little\\\"), target_os = \\\"windows\\\"))\"}],\"features\":{\"alloc\":[],\"default\":[\"alloc\",\"dev_urandom_fallback\"],\"dev_urandom_fallback\":[],\"less-safe-getrandom-custom-or-rdrand\":[],\"less-safe-getrandom-espidf\":[],\"slow_tests\":[],\"std\":[\"alloc\"],\"test_logging\":[],\"unstable-testing-arm-no-hw\":[],\"unstable-testing-arm-no-neon\":[],\"wasm32_unknown_unknown_js\":[\"getrandom/js\"]}}", @@ -1197,6 +1224,7 @@ "rustix_0.38.44": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.49\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\",\"target\":\"cfg(all(criterion, not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"itoa\",\"optional\":true,\"req\":\"^1.0.13\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.161\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.161\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.161\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"name\":\"libc_errno\",\"optional\":true,\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\"},{\"default_features\":false,\"features\":[\"general\",\"ioctl\",\"no_std\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.4.14\",\"target\":\"cfg(all(any(target_os = \\\"android\\\", target_os = \\\"linux\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"features\":[\"general\",\"errno\",\"ioctl\",\"no_std\",\"elf\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.4.14\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", target_arch = \\\"s390x\\\"), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.5.2\",\"target\":\"cfg(any(target_os = \\\"android\\\", target_os = \\\"linux\\\"))\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\",\"Win32_NetworkManagement_IpHelper\",\"Win32_System_Threading\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <=0.59\",\"target\":\"cfg(windows)\"}],\"features\":{\"all-apis\":[\"event\",\"fs\",\"io_uring\",\"mm\",\"mount\",\"net\",\"param\",\"pipe\",\"process\",\"procfs\",\"pty\",\"rand\",\"runtime\",\"shm\",\"stdio\",\"system\",\"termios\",\"thread\",\"time\"],\"alloc\":[],\"cc\":[],\"default\":[\"std\",\"use-libc-auxv\"],\"event\":[],\"fs\":[],\"io_uring\":[\"event\",\"fs\",\"net\",\"linux-raw-sys/io_uring\"],\"libc-extra-traits\":[\"libc?/extra_traits\"],\"linux_4_11\":[],\"linux_latest\":[\"linux_4_11\"],\"mm\":[],\"mount\":[],\"net\":[\"linux-raw-sys/net\",\"linux-raw-sys/netlink\",\"linux-raw-sys/if_ether\",\"linux-raw-sys/xdp\"],\"param\":[\"fs\"],\"pipe\":[],\"process\":[\"linux-raw-sys/prctl\"],\"procfs\":[\"once_cell\",\"itoa\",\"fs\"],\"pty\":[\"itoa\",\"fs\"],\"rand\":[],\"runtime\":[\"linux-raw-sys/prctl\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\",\"compiler_builtins\",\"linux-raw-sys/rustc-dep-of-std\",\"bitflags/rustc-dep-of-std\",\"compiler_builtins?/rustc-dep-of-std\"],\"shm\":[\"fs\"],\"std\":[\"bitflags/std\",\"alloc\",\"libc?/std\",\"libc_errno?/std\",\"libc-extra-traits\"],\"stdio\":[],\"system\":[\"linux-raw-sys/system\"],\"termios\":[],\"thread\":[\"linux-raw-sys/prctl\"],\"time\":[],\"try_close\":[],\"use-explicitly-provided-auxv\":[],\"use-libc\":[\"libc_errno\",\"libc\",\"libc-extra-traits\"],\"use-libc-auxv\":[]}}", "rustix_1.1.3": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bitflags\",\"req\":\"^2.4.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4\",\"target\":\"cfg(all(criterion, not(any(target_os = \\\"emscripten\\\", target_os = \\\"wasi\\\"))))\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"libc\",\"req\":\"^0.2.177\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.177\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"libc\",\"req\":\"^0.2.171\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(windows)\"},{\"default_features\":false,\"name\":\"libc_errno\",\"optional\":true,\"package\":\"errno\",\"req\":\"^0.3.10\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"libc_errno\",\"package\":\"errno\",\"req\":\"^0.3.10\"},{\"default_features\":false,\"features\":[\"general\",\"ioctl\",\"no_std\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.11.0\",\"target\":\"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\"},{\"default_features\":false,\"features\":[\"auxvec\",\"general\",\"errno\",\"ioctl\",\"no_std\",\"elf\"],\"name\":\"linux-raw-sys\",\"req\":\"^0.11.0\",\"target\":\"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\"},{\"kind\":\"dev\",\"name\":\"memoffset\",\"req\":\"^0.9.0\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.20.3\",\"target\":\"cfg(windows)\"},{\"name\":\"rustc-std-workspace-alloc\",\"optional\":true,\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"all-apis\":[\"event\",\"fs\",\"io_uring\",\"mm\",\"mount\",\"net\",\"param\",\"pipe\",\"process\",\"pty\",\"rand\",\"runtime\",\"shm\",\"stdio\",\"system\",\"termios\",\"thread\",\"time\"],\"alloc\":[],\"default\":[\"std\"],\"event\":[],\"fs\":[],\"io_uring\":[\"event\",\"fs\",\"net\",\"thread\",\"linux-raw-sys/io_uring\"],\"linux_4_11\":[],\"linux_5_1\":[\"linux_4_11\"],\"linux_5_11\":[\"linux_5_1\"],\"linux_latest\":[\"linux_5_11\"],\"mm\":[],\"mount\":[],\"net\":[\"linux-raw-sys/net\",\"linux-raw-sys/netlink\",\"linux-raw-sys/if_ether\",\"linux-raw-sys/xdp\"],\"param\":[],\"pipe\":[],\"process\":[\"linux-raw-sys/prctl\"],\"pty\":[\"fs\"],\"rand\":[],\"runtime\":[\"linux-raw-sys/prctl\"],\"rustc-dep-of-std\":[\"core\",\"rustc-std-workspace-alloc\",\"linux-raw-sys/rustc-dep-of-std\",\"bitflags/rustc-dep-of-std\"],\"shm\":[\"fs\"],\"std\":[\"bitflags/std\",\"alloc\",\"libc?/std\",\"libc_errno?/std\"],\"stdio\":[],\"system\":[\"linux-raw-sys/system\"],\"termios\":[],\"thread\":[\"linux-raw-sys/prctl\"],\"time\":[],\"try_close\":[],\"use-explicitly-provided-auxv\":[],\"use-libc\":[\"libc_errno\",\"libc\"],\"use-libc-auxv\":[]}}", "rustls-native-certs_0.8.3": "{\"dependencies\":[{\"name\":\"openssl-probe\",\"req\":\"^0.2\",\"target\":\"cfg(all(unix, not(target_os = \\\"macos\\\")))\"},{\"features\":[\"std\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.10\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17\"},{\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"kind\":\"dev\",\"name\":\"rustls-webpki\",\"req\":\"^0.103\"},{\"name\":\"schannel\",\"req\":\"^0.1\",\"target\":\"cfg(windows)\"},{\"name\":\"security-framework\",\"req\":\"^3\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5\"},{\"kind\":\"dev\",\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.18\"}],\"features\":{}}", + "rustls-pemfile_1.0.4": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"^0.21\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"}],\"features\":{}}", "rustls-pki-types_1.14.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"crabgrind\",\"req\":\"=0.1.9\",\"target\":\"cfg(all(target_os = \\\"linux\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"dep:zeroize\"],\"default\":[\"alloc\"],\"std\":[\"alloc\"],\"web\":[\"web-time\"]}}", "rustls-webpki_0.103.9": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.14\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"kind\":\"dev\",\"name\":\"bzip2\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.17.2\"},{\"default_features\":false,\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14.2\"},{\"default_features\":false,\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"untrusted\",\"req\":\"^0.9\"}],\"features\":{\"alloc\":[\"ring?/alloc\",\"pki-types/alloc\"],\"aws-lc-rs\":[\"dep:aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"aws-lc-rs-fips\":[\"dep:aws-lc-rs\",\"aws-lc-rs/fips\"],\"aws-lc-rs-unstable\":[\"aws-lc-rs\",\"aws-lc-rs/unstable\"],\"default\":[\"std\"],\"ring\":[\"dep:ring\"],\"std\":[\"alloc\",\"pki-types/std\"]}}", "rustls_0.23.36": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.14\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"brotli\",\"optional\":true,\"req\":\"^8\"},{\"name\":\"brotli-decompressor\",\"optional\":true,\"req\":\"^5.0.0\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"macro_rules_attribute\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4.4\"},{\"default_features\":false,\"features\":[\"alloc\",\"race\"],\"name\":\"once_cell\",\"req\":\"^1.16\"},{\"features\":[\"alloc\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"pem\",\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"kind\":\"build\",\"name\":\"rustversion\",\"optional\":true,\"req\":\"^1.0.6\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103.5\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17\"},{\"name\":\"zeroize\",\"req\":\"^1.8\"},{\"name\":\"zlib-rs\",\"optional\":true,\"req\":\"^0.5\"}],\"features\":{\"aws-lc-rs\":[\"aws_lc_rs\"],\"aws_lc_rs\":[\"dep:aws-lc-rs\",\"webpki/aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"brotli\":[\"dep:brotli\",\"dep:brotli-decompressor\",\"std\"],\"custom-provider\":[],\"default\":[\"aws_lc_rs\",\"logging\",\"prefer-post-quantum\",\"std\",\"tls12\"],\"fips\":[\"aws_lc_rs\",\"aws-lc-rs?/fips\",\"webpki/aws-lc-rs-fips\"],\"logging\":[\"log\"],\"prefer-post-quantum\":[\"aws_lc_rs\"],\"read_buf\":[\"rustversion\",\"std\"],\"ring\":[\"dep:ring\",\"webpki/ring\"],\"std\":[\"webpki/std\",\"pki-types/std\",\"once_cell/std\"],\"tls12\":[],\"zlib\":[\"dep:zlib-rs\"]}}", @@ -1217,6 +1245,7 @@ "schemars_derive_1.2.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.2.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.74\"},{\"name\":\"quote\",\"req\":\"^1.0.35\"},{\"name\":\"serde_derive_internals\",\"req\":\"^0.29.1\"},{\"name\":\"syn\",\"req\":\"^2.0.46\"},{\"features\":[\"extra-traits\"],\"kind\":\"dev\",\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", "scoped-tls_1.0.1": "{\"dependencies\":[],\"features\":{}}", "scopeguard_1.2.0": "{\"dependencies\":[],\"features\":{\"default\":[\"use_std\"],\"use_std\":[]}}", + "scraper_0.18.1": "{\"dependencies\":[{\"name\":\"ahash\",\"req\":\"^0.8\"},{\"name\":\"cssparser\",\"req\":\"^0.31.0\"},{\"name\":\"ego-tree\",\"req\":\"^0.6.2\"},{\"name\":\"getopts\",\"optional\":true,\"req\":\"^0.2.21\"},{\"name\":\"html5ever\",\"req\":\"^0.26\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.0.2\"},{\"name\":\"once_cell\",\"req\":\"^1.0\"},{\"name\":\"selectors\",\"req\":\"^0.25.0\"},{\"name\":\"tendril\",\"req\":\"^0.4.3\"}],\"features\":{\"atomic\":[],\"default\":[\"main\",\"errors\"],\"deterministic\":[\"indexmap\"],\"errors\":[],\"main\":[\"getopts\"]}}", "scrypt_0.11.0": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"rand_core\"],\"name\":\"password-hash\",\"optional\":true,\"req\":\"^0.5\"},{\"features\":[\"rand_core\"],\"kind\":\"dev\",\"name\":\"password-hash\",\"req\":\"^0.5\"},{\"name\":\"pbkdf2\",\"req\":\"^0.12\"},{\"default_features\":false,\"name\":\"salsa20\",\"req\":\"^0.10.2\"},{\"default_features\":false,\"name\":\"sha2\",\"req\":\"^0.10\"}],\"features\":{\"default\":[\"simple\",\"std\"],\"simple\":[\"password-hash\"],\"std\":[\"password-hash/std\"]}}", "sdd_3.0.10": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.6\"},{\"name\":\"loom\",\"optional\":true,\"req\":\"^0.7\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1.1\"}],\"features\":{}}", "seccompiler_0.5.0": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.153\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.27\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0.9\"}],\"features\":{\"json\":[\"serde\",\"serde_json\"]}}", @@ -1225,6 +1254,7 @@ "security-framework-sys_2.15.0": "{\"dependencies\":[{\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\"},{\"name\":\"libc\",\"req\":\"^0.2.150\"}],\"features\":{\"OSX_10_10\":[\"OSX_10_9\"],\"OSX_10_11\":[\"OSX_10_10\"],\"OSX_10_12\":[\"OSX_10_11\"],\"OSX_10_13\":[\"OSX_10_12\"],\"OSX_10_14\":[\"OSX_10_13\"],\"OSX_10_15\":[\"OSX_10_14\"],\"OSX_10_9\":[],\"default\":[\"OSX_10_12\"]}}", "security-framework_2.11.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.6\"},{\"name\":\"core-foundation\",\"req\":\"^0.9.4\"},{\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"libc\",\"req\":\"^0.2.139\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"name\":\"num-bigint\",\"optional\":true,\"req\":\"^0.4.6\"},{\"default_features\":false,\"name\":\"security-framework-sys\",\"req\":\"^2.11.1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.3.0\"},{\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.17\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.16\"}],\"features\":{\"OSX_10_10\":[\"OSX_10_9\",\"security-framework-sys/OSX_10_10\"],\"OSX_10_11\":[\"OSX_10_10\",\"security-framework-sys/OSX_10_11\"],\"OSX_10_12\":[\"OSX_10_11\",\"security-framework-sys/OSX_10_12\"],\"OSX_10_13\":[\"OSX_10_12\",\"security-framework-sys/OSX_10_13\",\"alpn\",\"session-tickets\",\"serial-number-bigint\"],\"OSX_10_14\":[\"OSX_10_13\",\"security-framework-sys/OSX_10_14\"],\"OSX_10_15\":[\"OSX_10_14\",\"security-framework-sys/OSX_10_15\"],\"OSX_10_9\":[\"security-framework-sys/OSX_10_9\"],\"alpn\":[],\"default\":[\"OSX_10_12\"],\"job-bless\":[],\"nightly\":[],\"serial-number-bigint\":[\"dep:num-bigint\"],\"session-tickets\":[]}}", "security-framework_3.5.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.6\"},{\"name\":\"core-foundation\",\"req\":\"^0.10\"},{\"name\":\"core-foundation-sys\",\"req\":\"^0.8.6\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"libc\",\"req\":\"^0.2.139\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"name\":\"security-framework-sys\",\"req\":\"^2.15\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.12.0\"},{\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.23\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.16\"}],\"features\":{\"OSX_10_12\":[\"security-framework-sys/OSX_10_12\"],\"OSX_10_13\":[\"OSX_10_12\",\"security-framework-sys/OSX_10_13\",\"alpn\",\"session-tickets\"],\"OSX_10_14\":[\"OSX_10_13\",\"security-framework-sys/OSX_10_14\"],\"OSX_10_15\":[\"OSX_10_14\",\"security-framework-sys/OSX_10_15\"],\"alpn\":[],\"default\":[\"OSX_10_12\"],\"job-bless\":[],\"nightly\":[],\"session-tickets\":[],\"sync-keychain\":[\"OSX_10_13\"]}}", + "selectors_0.25.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"cssparser\",\"req\":\"^0.31\"},{\"default_features\":false,\"features\":[\"add\",\"add_assign\"],\"name\":\"derive_more\",\"req\":\"^0.99\"},{\"name\":\"fxhash\",\"req\":\"^0.2\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"new_debug_unreachable\",\"req\":\"^1\"},{\"name\":\"phf\",\"req\":\"^0.10\"},{\"kind\":\"build\",\"name\":\"phf_codegen\",\"req\":\"^0.10\"},{\"name\":\"precomputed-hash\",\"req\":\"^0.1\"},{\"name\":\"servo_arc\",\"req\":\"^0.3\"},{\"name\":\"smallvec\",\"req\":\"^1.0\"}],\"features\":{\"bench\":[]}}", "self_cell_0.10.3": "{\"dependencies\":[{\"name\":\"new_self_cell\",\"package\":\"self_cell\",\"req\":\"^1\"}],\"features\":{\"old_rust\":[\"new_self_cell/old_rust\"]}}", "self_cell_1.2.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"=1.1.0\"},{\"name\":\"rustversion\",\"optional\":true,\"req\":\">=1\"}],\"features\":{\"old_rust\":[\"rustversion\"]}}", "semver_1.0.27": "{\"dependencies\":[{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"package\":\"serde_core\",\"req\":\"^1.0.220\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\",\"target\":\"cfg(any())\"}],\"features\":{\"default\":[\"std\"],\"serde\":[\"dep:serde\"],\"std\":[]}}", @@ -1253,6 +1283,7 @@ "serial2_0.2.33": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert2\",\"req\":\"^0.3.11\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\",\"target\":\"cfg(unix)\"},{\"name\":\"libc\",\"req\":\"^0.2.109\",\"target\":\"cfg(unix)\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.108\"},{\"features\":[\"commapi\",\"fileapi\",\"handleapi\",\"ioapiset\",\"std\",\"synchapi\",\"winbase\",\"winerror\",\"winreg\"],\"name\":\"winapi\",\"req\":\"^0.3.9\",\"target\":\"cfg(windows)\"}],\"features\":{\"doc\":[],\"doc-cfg\":[],\"rs4xx\":[],\"serde\":[\"dep:serde\"],\"unix\":[],\"windows\":[]}}", "serial_test_3.3.1": "{\"dependencies\":[{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"env_logger\",\"optional\":true,\"req\":\">=0.6.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"fslock\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-executor\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"use_std\"],\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\">=0.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\">=0.4.4\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19\"},{\"default_features\":false,\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"default_features\":false,\"name\":\"scc\",\"req\":\"^2\"},{\"name\":\"serial_test_derive\",\"req\":\"~3.3.1\"}],\"features\":{\"async\":[\"dep:futures-executor\",\"dep:futures-util\",\"serial_test_derive/async\"],\"default\":[\"logging\",\"async\"],\"docsrs\":[\"dep:document-features\"],\"file_locks\":[\"dep:fslock\"],\"logging\":[\"dep:log\"],\"test_logging\":[\"logging\",\"dep:env_logger\",\"serial_test_derive/test_logging\"]}}", "serial_test_derive_3.3.1": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\">=0.6.1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"prettyplease\",\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"proc-macro\"],\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"full\",\"printing\",\"parsing\",\"clone-impls\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{\"async\":[],\"default\":[],\"file_locks\":[],\"test_logging\":[]}}", + "servo_arc_0.3.0": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"stable_deref_trait\",\"req\":\"^1.0.0\"}],\"features\":{\"gecko_refcount_logging\":[],\"servo\":[\"serde\"]}}", "sha1_0.10.6": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"cpufeatures\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"name\":\"sha1-asm\",\"optional\":true,\"req\":\"^0.5\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86\\\", target_arch = \\\"x86_64\\\"))\"}],\"features\":{\"asm\":[\"sha1-asm\"],\"compress\":[],\"default\":[\"std\"],\"force-soft\":[],\"loongarch64_asm\":[],\"oid\":[\"digest/oid\"],\"std\":[\"digest/std\"]}}", "sha1_smol_1.0.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"openssl\",\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"alloc\":[],\"std\":[\"alloc\"]}}", "sha2_0.10.9": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"cpufeatures\",\"req\":\"^0.2\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\"},{\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"digest\",\"req\":\"^0.10.7\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.2.2\"},{\"name\":\"sha2-asm\",\"optional\":true,\"req\":\"^0.6.1\",\"target\":\"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\"}],\"features\":{\"asm\":[\"sha2-asm\"],\"asm-aarch64\":[\"asm\"],\"compress\":[],\"default\":[\"std\"],\"force-soft\":[],\"force-soft-compact\":[],\"loongarch64_asm\":[],\"oid\":[\"digest/oid\"],\"std\":[\"digest/std\"]}}", @@ -1267,6 +1298,7 @@ "simd-adler32_0.3.8": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"adler\",\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"adler32\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"}],\"features\":{\"const-generics\":[],\"default\":[\"std\",\"const-generics\"],\"nightly\":[],\"std\":[]}}", "simdutf8_0.1.5": "{\"dependencies\":[],\"features\":{\"aarch64_neon\":[],\"aarch64_neon_prefetch\":[],\"default\":[\"std\"],\"hints\":[],\"public_imp\":[],\"std\":[]}}", "similar_2.7.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"optional\":true,\"req\":\"^1.5.0\"},{\"kind\":\"dev\",\"name\":\"console\",\"req\":\"^0.15.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.10.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.130\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.68\"},{\"name\":\"unicode-segmentation\",\"optional\":true,\"req\":\"^1.7.1\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1.1\"}],\"features\":{\"bytes\":[\"bstr\",\"text\"],\"default\":[\"text\"],\"inline\":[\"text\"],\"text\":[],\"unicode\":[\"text\",\"unicode-segmentation\",\"bstr?/unicode\",\"bstr?/std\"],\"wasm32_web_time\":[\"web-time\"]}}", + "siphasher_0.3.11": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"serde_no_std\":[\"serde/alloc\"],\"serde_std\":[\"std\",\"serde/std\"],\"std\":[]}}", "siphasher_1.0.2": "{\"dependencies\":[{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"default\":[\"std\"],\"serde_no_std\":[\"serde/alloc\"],\"serde_std\":[\"std\",\"serde/std\"],\"std\":[]}}", "slab_0.4.12": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.95\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "smallvec_1.15.1": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"bincode\",\"optional\":true,\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"bincode1\",\"package\":\"bincode\",\"req\":\"^1.0.1\"},{\"kind\":\"dev\",\"name\":\"debugger_test\",\"req\":\"^0.1.0\"},{\"kind\":\"dev\",\"name\":\"debugger_test_parser\",\"req\":\"^0.1.0\"},{\"default_features\":false,\"name\":\"malloc_size_of\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"name\":\"unty\",\"optional\":true,\"req\":\"^0.0.4\"}],\"features\":{\"const_generics\":[],\"const_new\":[\"const_generics\"],\"debugger_visualizer\":[],\"drain_filter\":[],\"drain_keep_rest\":[\"drain_filter\"],\"impl_bincode\":[\"bincode\",\"unty\"],\"may_dangle\":[],\"specialization\":[],\"union\":[],\"write\":[]}}", @@ -1293,6 +1325,7 @@ "stop-words_0.9.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"human_regex\",\"req\":\"^0.3.0\"},{\"kind\":\"build\",\"name\":\"serde_json\",\"req\":\"^1\"}],\"features\":{\"constructed\":[],\"default\":[\"iso\"],\"iso\":[],\"nltk\":[],\"unimplemented\":[]}}", "streaming-iterator_0.1.9": "{\"dependencies\":[],\"features\":{\"alloc\":[],\"std\":[\"alloc\"]}}", "string_cache_0.8.9": "{\"dependencies\":[{\"default_features\":false,\"name\":\"malloc_size_of\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"new_debug_unreachable\",\"req\":\"^1.0.2\"},{\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"name\":\"phf_shared\",\"req\":\"^0.11\"},{\"name\":\"precomputed-hash\",\"req\":\"^0.1\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"default\":[\"serde_support\"],\"serde_support\":[\"serde\"]}}", + "string_cache_codegen_0.5.4": "{\"dependencies\":[{\"name\":\"phf_generator\",\"req\":\"^0.11\"},{\"name\":\"phf_shared\",\"req\":\"^0.11\"},{\"name\":\"proc-macro2\",\"req\":\"^1\"},{\"name\":\"quote\",\"req\":\"^1\"}],\"features\":{}}", "stringprep_0.1.5": "{\"dependencies\":[{\"name\":\"unicode-bidi\",\"req\":\"^0.3\"},{\"name\":\"unicode-normalization\",\"req\":\"^0.1\"},{\"name\":\"unicode-properties\",\"req\":\"^0.1.1\"}],\"features\":{}}", "strsim_0.10.0": "{\"dependencies\":[],\"features\":{}}", "strsim_0.11.1": "{\"dependencies\":[],\"features\":{}}", @@ -1305,14 +1338,18 @@ "supports-color_3.0.2": "{\"dependencies\":[{\"name\":\"is_ci\",\"req\":\"^1.2.0\"}],\"features\":{}}", "syn_1.0.109": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.46\"},{\"default_features\":false,\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.0\"},{\"features\":[\"blocking\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"syn-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4.16\"},{\"kind\":\"dev\",\"name\":\"termcolor\",\"req\":\"^1.0\"},{\"name\":\"unicode-ident\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.1\"}],\"features\":{\"clone-impls\":[],\"default\":[\"derive\",\"parsing\",\"printing\",\"clone-impls\",\"proc-macro\"],\"derive\":[],\"extra-traits\":[],\"fold\":[],\"full\":[],\"parsing\":[],\"printing\":[\"quote\"],\"proc-macro\":[\"proc-macro2/proc-macro\",\"quote/proc-macro\"],\"test\":[\"syn-test-suite/all-features\"],\"visit\":[],\"visit-mut\":[]}}", "syn_2.0.114": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"flate2\",\"req\":\"^1\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.91\"},{\"default_features\":false,\"name\":\"quote\",\"optional\":true,\"req\":\"^1.0.35\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"ref-cast\",\"req\":\"^1\"},{\"features\":[\"blocking\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.13\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"syn-test-suite\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"tar\",\"req\":\"^0.4.16\",\"target\":\"cfg(not(miri))\"},{\"kind\":\"dev\",\"name\":\"termcolor\",\"req\":\"^1\"},{\"name\":\"unicode-ident\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\",\"target\":\"cfg(not(miri))\"}],\"features\":{\"clone-impls\":[],\"default\":[\"derive\",\"parsing\",\"printing\",\"clone-impls\",\"proc-macro\"],\"derive\":[],\"extra-traits\":[],\"fold\":[],\"full\":[],\"parsing\":[],\"printing\":[\"dep:quote\"],\"proc-macro\":[\"proc-macro2/proc-macro\",\"quote?/proc-macro\"],\"test\":[\"syn-test-suite/all-features\"],\"visit\":[],\"visit-mut\":[]}}", + "sync_wrapper_0.1.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"pin-project-lite\",\"req\":\"^0.2.7\"}],\"features\":{\"futures\":[\"futures-core\"]}}", "sync_wrapper_1.0.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"pin-project-lite\",\"req\":\"^0.2.7\"}],\"features\":{\"futures\":[\"futures-core\"]}}", "synstructure_0.13.2": "{\"dependencies\":[{\"default_features\":false,\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"default_features\":false,\"name\":\"quote\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"derive\",\"parsing\",\"printing\",\"clone-impls\",\"visit\",\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"synstructure_test_traits\",\"req\":\"^0.1\"}],\"features\":{\"default\":[\"proc-macro\"],\"proc-macro\":[\"proc-macro2/proc-macro\",\"syn/proc-macro\",\"quote/proc-macro\"]}}", "syntect_5.3.0": "{\"dependencies\":[{\"name\":\"bincode\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"fancy-regex\",\"optional\":true,\"req\":\"^0.16.2\"},{\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"fnv\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"getopts\",\"req\":\"^0.2\"},{\"name\":\"once_cell\",\"req\":\"^1.8\"},{\"default_features\":false,\"name\":\"onig\",\"optional\":true,\"req\":\"^6.5.1\"},{\"name\":\"plist\",\"optional\":true,\"req\":\"^1.3\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"public-api\",\"req\":\"^0.50.1\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1.0\"},{\"name\":\"regex-syntax\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"rustdoc-json\",\"req\":\"^0.9.7\"},{\"kind\":\"dev\",\"name\":\"rustup-toolchain\",\"req\":\"^0.1.5\"},{\"name\":\"serde\",\"req\":\"^1.0\"},{\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.12\"},{\"name\":\"walkdir\",\"req\":\"^2.0\"},{\"name\":\"yaml-rust\",\"optional\":true,\"req\":\"^0.4.5\"}],\"features\":{\"default\":[\"default-onig\"],\"default-fancy\":[\"parsing\",\"default-syntaxes\",\"default-themes\",\"html\",\"plist-load\",\"yaml-load\",\"dump-load\",\"dump-create\",\"regex-fancy\"],\"default-onig\":[\"parsing\",\"default-syntaxes\",\"default-themes\",\"html\",\"plist-load\",\"yaml-load\",\"dump-load\",\"dump-create\",\"regex-onig\"],\"default-syntaxes\":[\"parsing\",\"dump-load\"],\"default-themes\":[\"dump-load\"],\"dump-create\":[\"flate2\",\"bincode\"],\"dump-load\":[\"flate2\",\"bincode\"],\"html\":[\"parsing\"],\"metadata\":[\"parsing\",\"plist-load\",\"dep:serde_json\"],\"parsing\":[\"regex-syntax\",\"fnv\",\"dump-create\",\"dump-load\"],\"plist-load\":[\"plist\",\"dep:serde_json\"],\"regex-fancy\":[\"fancy-regex\"],\"regex-onig\":[\"onig\"],\"yaml-load\":[\"yaml-rust\",\"parsing\"]}}", "sys-locale_0.3.2": "{\"dependencies\":[{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"features\":[\"Window\",\"WorkerGlobalScope\",\"Navigator\",\"WorkerNavigator\"],\"name\":\"web-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"}],\"features\":{\"js\":[\"js-sys\",\"wasm-bindgen\",\"web-sys\"]}}", + "system-configuration-sys_0.5.0": "{\"dependencies\":[{\"name\":\"core-foundation-sys\",\"req\":\"^0.8\"},{\"name\":\"libc\",\"req\":\"^0.2.49\"}],\"features\":{}}", "system-configuration-sys_0.6.0": "{\"dependencies\":[{\"name\":\"core-foundation-sys\",\"req\":\"^0.8\"},{\"name\":\"libc\",\"req\":\"^0.2.149\"}],\"features\":{}}", + "system-configuration_0.5.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^1\"},{\"name\":\"core-foundation\",\"req\":\"^0.9\"},{\"name\":\"system-configuration-sys\",\"req\":\"^0.5\"}],\"features\":{}}", "system-configuration_0.6.1": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"core-foundation\",\"req\":\"^0.9\"},{\"name\":\"system-configuration-sys\",\"req\":\"^0.6\"}],\"features\":{}}", "tagptr_0.2.0": "{\"dependencies\":[],\"features\":{}}", "tempfile_3.24.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"name\":\"fastrand\",\"req\":\"^2.1.1\"},{\"default_features\":false,\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.0\",\"target\":\"cfg(any(unix, windows, target_os = \\\"wasi\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"once_cell\",\"req\":\"^1.19.0\"},{\"features\":[\"fs\"],\"name\":\"rustix\",\"req\":\"^1.1.3\",\"target\":\"cfg(any(unix, target_os = \\\"wasi\\\"))\"},{\"features\":[\"Win32_Storage_FileSystem\",\"Win32_Foundation\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <0.62\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[\"getrandom\"],\"nightly\":[]}}", + "tendril_0.4.3": "{\"dependencies\":[{\"name\":\"encoding\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"encoding_rs\",\"optional\":true,\"req\":\"^0.8.12\"},{\"name\":\"futf\",\"req\":\"^0.1.5\"},{\"name\":\"mac\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.4\"},{\"name\":\"utf-8\",\"req\":\"^0.7\"}],\"features\":{\"bench\":[]}}", "term_0.7.0": "{\"dependencies\":[{\"name\":\"dirs-next\",\"req\":\"^2\"},{\"name\":\"rustversion\",\"req\":\"^1\",\"target\":\"cfg(windows)\"},{\"features\":[\"consoleapi\",\"wincon\",\"handleapi\",\"fileapi\"],\"name\":\"winapi\",\"req\":\"^0.3\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[]}}", "termcolor_1.4.1": "{\"dependencies\":[{\"name\":\"winapi-util\",\"req\":\"^0.1.3\",\"target\":\"cfg(windows)\"}],\"features\":{}}", "terminal_size_0.4.3": "{\"dependencies\":[{\"features\":[\"termios\"],\"name\":\"rustix\",\"req\":\"^1.0.1\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Console\"],\"name\":\"windows-sys\",\"req\":\"^0.60.0\",\"target\":\"cfg(windows)\"}],\"features\":{}}", @@ -1523,6 +1560,7 @@ "x25519-dalek_2.0.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"default_features\":false,\"name\":\"curve25519-dalek\",\"req\":\"^4\"},{\"default_features\":false,\"name\":\"rand_core\",\"req\":\"^0.6\"},{\"default_features\":false,\"features\":[\"getrandom\"],\"kind\":\"dev\",\"name\":\"rand_core\",\"req\":\"^0.6\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"curve25519-dalek/alloc\",\"serde?/alloc\",\"zeroize?/alloc\"],\"default\":[\"alloc\",\"precomputed-tables\",\"zeroize\"],\"getrandom\":[\"rand_core/getrandom\"],\"precomputed-tables\":[\"curve25519-dalek/precomputed-tables\"],\"reusable_secrets\":[],\"serde\":[\"dep:serde\",\"curve25519-dalek/serde\"],\"static_secrets\":[],\"zeroize\":[\"dep:zeroize\",\"curve25519-dalek/zeroize\"]}}", "x509-parser_0.18.1": "{\"dependencies\":[{\"features\":[\"datetime\"],\"name\":\"asn1-rs\",\"req\":\"^0.7.0\"},{\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"data-encoding\",\"req\":\"^2.2.1\"},{\"features\":[\"bigint\"],\"name\":\"der-parser\",\"req\":\"^10.0\"},{\"name\":\"lazy_static\",\"req\":\"^1.4\"},{\"name\":\"nom\",\"req\":\"^7.0\"},{\"features\":[\"crypto\",\"x509\",\"x962\"],\"name\":\"oid-registry\",\"req\":\"^0.8.1\"},{\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17.12\"},{\"name\":\"rusticata-macros\",\"req\":\"^4.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0\"},{\"features\":[\"formatting\"],\"name\":\"time\",\"req\":\"^0.3.35\"}],\"features\":{\"default\":[],\"validate\":[],\"verify\":[\"ring\"],\"verify-aws\":[\"aws-lc-rs\"]}}", "xdg-home_1.3.0": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"features\":[\"Win32_Foundation\",\"Win32_UI_Shell\",\"Win32_System_Com\"],\"name\":\"windows-sys\",\"req\":\"^0.59\",\"target\":\"cfg(windows)\"}],\"features\":{}}", + "xml-rs_0.8.28": "{\"dependencies\":[],\"features\":{}}", "xz2_0.1.7": "{\"dependencies\":[{\"name\":\"futures\",\"optional\":true,\"req\":\"^0.1.26\"},{\"name\":\"lzma-sys\",\"req\":\"^0.1.18\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.0\"},{\"kind\":\"dev\",\"name\":\"tokio-core\",\"req\":\"^0.1.17\"},{\"name\":\"tokio-io\",\"optional\":true,\"req\":\"^0.1.12\"}],\"features\":{\"static\":[\"lzma-sys/static\"],\"tokio\":[\"tokio-io\",\"futures\"]}}", "yaml-rust_0.4.5": "{\"dependencies\":[{\"name\":\"linked-hash-map\",\"req\":\"^0.5.3\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"}],\"features\":{}}", "yansi_1.0.1": "{\"dependencies\":[{\"name\":\"is-terminal\",\"optional\":true,\"req\":\"^0.4.11\"}],\"features\":{\"_nightly\":[],\"alloc\":[],\"default\":[\"std\"],\"detect-env\":[\"std\"],\"detect-tty\":[\"is-terminal\",\"std\"],\"hyperlink\":[\"std\"],\"std\":[\"alloc\"]}}", @@ -1541,11 +1579,14 @@ "zerotrie_0.2.3": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"litemap\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"zerofrom\",\"optional\":true,\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"zerovec\",\"optional\":true,\"req\":\"^0.11.3\"}],\"features\":{\"alloc\":[],\"databake\":[\"dep:databake\",\"zerovec?/databake\"],\"default\":[],\"litemap\":[\"dep:litemap\",\"alloc\"],\"serde\":[\"dep:serde_core\",\"dep:litemap\",\"alloc\",\"litemap/serde\",\"zerovec?/serde\"],\"yoke\":[\"dep:yoke\"],\"zerofrom\":[\"dep:zerofrom\"]}}", "zerovec-derive_0.11.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"bincode\",\"req\":\"^1.3.1\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0.61\"},{\"name\":\"quote\",\"req\":\"^1.0.28\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.220\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.45\"},{\"features\":[\"extra-traits\"],\"name\":\"syn\",\"req\":\"^2.0.21\"}],\"features\":{}}", "zerovec_0.11.5": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"databake\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.220\"},{\"default_features\":false,\"features\":[\"xxhash64\"],\"name\":\"twox-hash\",\"optional\":true,\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"yoke\",\"optional\":true,\"req\":\"^0.8.0\"},{\"default_features\":false,\"name\":\"zerofrom\",\"req\":\"^0.1.3\"},{\"default_features\":false,\"name\":\"zerovec-derive\",\"optional\":true,\"req\":\"^0.11.1\"}],\"features\":{\"alloc\":[\"serde?/alloc\"],\"databake\":[\"dep:databake\"],\"derive\":[\"dep:zerovec-derive\"],\"hashmap\":[\"dep:twox-hash\",\"alloc\"],\"serde\":[\"dep:serde\"],\"std\":[],\"yoke\":[\"dep:yoke\"]}}", + "zip_0.6.6": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8.2\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"byteorder\",\"req\":\"^1.4.3\"},{\"name\":\"bzip2\",\"optional\":true,\"req\":\"^0.4.3\"},{\"name\":\"constant_time_eq\",\"optional\":true,\"req\":\"^0.1.5\"},{\"name\":\"crc32fast\",\"req\":\"^1.3.2\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.8\",\"target\":\"cfg(any(all(target_arch = \\\"arm\\\", target_pointer_width = \\\"32\\\"), target_arch = \\\"mips\\\", target_arch = \\\"powerpc\\\"))\"},{\"default_features\":false,\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0.23\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.5\"},{\"features\":[\"reset\"],\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12.1\"},{\"name\":\"pbkdf2\",\"optional\":true,\"req\":\"^0.11.0\"},{\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.7\"},{\"features\":[\"formatting\",\"macros\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.7\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.11.2\"}],\"features\":{\"aes-crypto\":[\"aes\",\"constant_time_eq\",\"hmac\",\"pbkdf2\",\"sha1\"],\"default\":[\"aes-crypto\",\"bzip2\",\"deflate\",\"time\",\"zstd\"],\"deflate\":[\"flate2/rust_backend\"],\"deflate-miniz\":[\"flate2/default\"],\"deflate-zlib\":[\"flate2/zlib\"],\"unreserved\":[]}}", "zip_2.4.2": "{\"dependencies\":[{\"name\":\"aes\",\"optional\":true,\"req\":\"^0.8\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.95\"},{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"req\":\"^1.4.1\",\"target\":\"cfg(fuzzing)\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"name\":\"bzip2\",\"optional\":true,\"req\":\"^0.5.0\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"=4.4.18\"},{\"name\":\"constant_time_eq\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"crc32fast\",\"req\":\"^1.4\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.21\",\"target\":\"cfg(any(all(target_arch = \\\"arm\\\", target_pointer_width = \\\"32\\\"), target_arch = \\\"mips\\\", target_arch = \\\"powerpc\\\"))\"},{\"name\":\"deflate64\",\"optional\":true,\"req\":\"^0.1.9\"},{\"default_features\":false,\"name\":\"displaydoc\",\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"flate2\",\"optional\":true,\"req\":\"^1.0\"},{\"features\":[\"wasm_js\",\"std\"],\"name\":\"getrandom\",\"optional\":true,\"req\":\"^0.3.1\"},{\"features\":[\"wasm_js\",\"std\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.3.1\"},{\"features\":[\"reset\"],\"name\":\"hmac\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"lzma-rs\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"memchr\",\"req\":\"^2.7\"},{\"default_features\":false,\"name\":\"nt-time\",\"optional\":true,\"req\":\"^0.10.6\"},{\"name\":\"pbkdf2\",\"optional\":true,\"req\":\"^0.12\"},{\"name\":\"sha1\",\"optional\":true,\"req\":\"^0.10\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.15\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"time\",\"optional\":true,\"req\":\"^0.3.37\"},{\"default_features\":false,\"features\":[\"formatting\",\"macros\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.37\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5\"},{\"name\":\"xz2\",\"optional\":true,\"req\":\"^0.1.7\"},{\"features\":[\"zeroize_derive\"],\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.8\"},{\"name\":\"zopfli\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"zstd\",\"optional\":true,\"req\":\"^0.13\"}],\"features\":{\"_all-features\":[],\"_deflate-any\":[],\"aes-crypto\":[\"aes\",\"constant_time_eq\",\"hmac\",\"pbkdf2\",\"sha1\",\"getrandom\",\"zeroize\"],\"chrono\":[\"chrono/default\"],\"default\":[\"aes-crypto\",\"bzip2\",\"deflate64\",\"deflate\",\"lzma\",\"time\",\"zstd\",\"xz\"],\"deflate\":[\"flate2/rust_backend\",\"deflate-zopfli\",\"deflate-flate2\"],\"deflate-flate2\":[\"_deflate-any\"],\"deflate-miniz\":[\"deflate\",\"deflate-flate2\"],\"deflate-zlib\":[\"flate2/zlib\",\"deflate-flate2\"],\"deflate-zlib-ng\":[\"flate2/zlib-ng\",\"deflate-flate2\"],\"deflate-zopfli\":[\"zopfli\",\"_deflate-any\"],\"lzma\":[\"lzma-rs/stream\"],\"nt-time\":[\"dep:nt-time\"],\"unreserved\":[],\"xz\":[\"dep:xz2\"]}}", "zmij_1.0.19": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1.36\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"num-integer\",\"req\":\"^0.1\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1.8\"},{\"kind\":\"dev\",\"name\":\"opt-level\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"ryu\",\"req\":\"^1\"}],\"features\":{}}", "zopfli_0.8.3": "{\"dependencies\":[{\"name\":\"bumpalo\",\"req\":\"^3.19.0\"},{\"default_features\":false,\"name\":\"crc32fast\",\"optional\":true,\"req\":\"^1.5.0\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.28\"},{\"kind\":\"dev\",\"name\":\"miniz_oxide\",\"req\":\"^0.8.9\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"kind\":\"dev\",\"name\":\"proptest-derive\",\"req\":\"^0.6.0\"},{\"default_features\":false,\"name\":\"simd-adler32\",\"optional\":true,\"req\":\"^0.3.7\"}],\"features\":{\"default\":[\"gzip\",\"std\",\"zlib\"],\"gzip\":[\"dep:crc32fast\"],\"nightly\":[\"crc32fast?/nightly\"],\"std\":[\"crc32fast?/std\",\"dep:log\",\"simd-adler32?/std\"],\"zlib\":[\"dep:simd-adler32\"]}}", + "zstd-safe_5.0.2+zstd.1.5.2": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2.21\"},{\"default_features\":false,\"name\":\"zstd-sys\",\"req\":\"^2.0.1\"}],\"features\":{\"arrays\":[],\"bindgen\":[\"zstd-sys/bindgen\"],\"debug\":[\"zstd-sys/debug\"],\"default\":[\"legacy\",\"arrays\",\"zdict_builder\"],\"doc-cfg\":[],\"experimental\":[\"zstd-sys/experimental\"],\"legacy\":[\"zstd-sys/legacy\"],\"no_asm\":[\"zstd-sys/no_asm\"],\"pkg-config\":[\"zstd-sys/pkg-config\"],\"std\":[\"zstd-sys/std\"],\"thin\":[\"zstd-sys/thin\"],\"zdict_builder\":[\"zstd-sys/zdict_builder\"],\"zstdmt\":[\"zstd-sys/zstdmt\"]}}", "zstd-safe_7.2.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"zstd-sys\",\"req\":\"^2.0.15\"}],\"features\":{\"arrays\":[],\"bindgen\":[\"zstd-sys/bindgen\"],\"debug\":[\"zstd-sys/debug\"],\"default\":[\"legacy\",\"arrays\",\"zdict_builder\"],\"doc-cfg\":[],\"experimental\":[\"zstd-sys/experimental\"],\"fat-lto\":[\"zstd-sys/fat-lto\"],\"legacy\":[\"zstd-sys/legacy\"],\"no_asm\":[\"zstd-sys/no_asm\"],\"pkg-config\":[\"zstd-sys/pkg-config\"],\"seekable\":[\"zstd-sys/seekable\"],\"std\":[\"zstd-sys/std\"],\"thin\":[\"zstd-sys/thin\"],\"thin-lto\":[\"zstd-sys/thin-lto\"],\"zdict_builder\":[\"zstd-sys/zdict_builder\"],\"zstdmt\":[\"zstd-sys/zstdmt\"]}}", "zstd-sys_2.0.16+zstd.1.5.7": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"runtime\"],\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.72\"},{\"features\":[\"parallel\"],\"kind\":\"build\",\"name\":\"cc\",\"req\":\"^1.0.45\"},{\"kind\":\"build\",\"name\":\"pkg-config\",\"req\":\"^0.3.28\"}],\"features\":{\"debug\":[],\"default\":[\"legacy\",\"zdict_builder\",\"bindgen\"],\"experimental\":[],\"fat-lto\":[],\"legacy\":[],\"no_asm\":[],\"no_wasm_shim\":[],\"non-cargo\":[],\"pkg-config\":[],\"seekable\":[],\"std\":[],\"thin\":[],\"thin-lto\":[],\"zdict_builder\":[],\"zstdmt\":[]}}", + "zstd_0.11.2+zstd.1.5.2": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^3.0\"},{\"kind\":\"dev\",\"name\":\"humansize\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"partial-io\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"zstd-safe\",\"req\":\"^5.0.1\"}],\"features\":{\"arrays\":[\"zstd-safe/arrays\"],\"bindgen\":[\"zstd-safe/bindgen\"],\"debug\":[\"zstd-safe/debug\"],\"default\":[\"legacy\",\"arrays\",\"zdict_builder\"],\"doc-cfg\":[],\"experimental\":[\"zstd-safe/experimental\"],\"legacy\":[\"zstd-safe/legacy\"],\"no_asm\":[\"zstd-safe/no_asm\"],\"pkg-config\":[\"zstd-safe/pkg-config\"],\"thin\":[\"zstd-safe/thin\"],\"wasm\":[],\"zdict_builder\":[\"zstd-safe/zdict_builder\"],\"zstdmt\":[\"zstd-safe/zstdmt\"]}}", "zstd_0.13.3": "{\"dependencies\":[{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"clap\",\"req\":\"^4.0\"},{\"kind\":\"dev\",\"name\":\"humansize\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"partial-io\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"zstd-safe\",\"req\":\"^7.1.0\"}],\"features\":{\"arrays\":[\"zstd-safe/arrays\"],\"bindgen\":[\"zstd-safe/bindgen\"],\"debug\":[\"zstd-safe/debug\"],\"default\":[\"legacy\",\"arrays\",\"zdict_builder\"],\"doc-cfg\":[],\"experimental\":[\"zstd-safe/experimental\"],\"fat-lto\":[\"zstd-safe/fat-lto\"],\"legacy\":[\"zstd-safe/legacy\"],\"no_asm\":[\"zstd-safe/no_asm\"],\"pkg-config\":[\"zstd-safe/pkg-config\"],\"thin\":[\"zstd-safe/thin\"],\"thin-lto\":[\"zstd-safe/thin-lto\"],\"wasm\":[],\"zdict_builder\":[\"zstd-safe/zdict_builder\"],\"zstdmt\":[\"zstd-safe/zstdmt\"]}}", "zune-core_0.4.12": "{\"dependencies\":[{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.17\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.52\"}],\"features\":{\"std\":[]}}", "zune-core_0.5.1": "{\"dependencies\":[{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"}],\"features\":{\"std\":[]}}", diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index ed6f377d0..41f0bc6f8 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -831,9 +831,9 @@ dependencies = [ "form_urlencoded", "futures-util", "http 1.4.0", - "http-body", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-util", "itoa", "matchit 0.8.4", @@ -845,7 +845,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower", "tower-layer", @@ -862,11 +862,11 @@ dependencies = [ "bytes", "futures-core", "http 1.4.0", - "http-body", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -1096,6 +1096,16 @@ dependencies = [ "bytes", ] +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + [[package]] name = "bzip2" version = "0.5.2" @@ -1393,7 +1403,7 @@ dependencies = [ "http 1.4.0", "pretty_assertions", "regex-lite", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.18", @@ -1530,6 +1540,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "codex-artifact-presentation" +version = "0.0.0" +dependencies = [ + "base64 0.22.1", + "image", + "ppt-rs", + "pretty_assertions", + "reqwest 0.12.28", + "serde", + "serde_json", + "tempfile", + "thiserror 2.0.18", + "tiny_http", + "uuid", +] + [[package]] name = "codex-async-utils" version = "0.0.0" @@ -1549,7 +1576,7 @@ dependencies = [ "codex-core", "codex-protocol", "pretty_assertions", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", ] @@ -1637,7 +1664,7 @@ dependencies = [ "opentelemetry", "opentelemetry_sdk", "rand 0.9.2", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.18", @@ -1645,7 +1672,7 @@ dependencies = [ "tracing", "tracing-opentelemetry", "tracing-subscriber", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -1689,7 +1716,7 @@ dependencies = [ "owo-colors", "pretty_assertions", "ratatui", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "supports-color 3.0.2", @@ -1758,6 +1785,7 @@ dependencies = [ "codex-app-server-protocol", "codex-apply-patch", "codex-arg0", + "codex-artifact-presentation", "codex-async-utils", "codex-client", "codex-config", @@ -1812,7 +1840,7 @@ dependencies = [ "pretty_assertions", "rand 0.9.2", "regex-lite", - "reqwest", + "reqwest 0.12.28", "rmcp", "schemars 0.8.22", "seccompiler", @@ -1844,8 +1872,8 @@ dependencies = [ "wildmatch", "windows-sys 0.52.0", "wiremock", - "zip", - "zstd", + "zip 2.4.2", + "zstd 0.13.3", ] [[package]] @@ -2037,7 +2065,7 @@ name = "codex-lmstudio" version = "0.0.0" dependencies = [ "codex-core", - "reqwest", + "reqwest 0.12.28", "serde_json", "tokio", "tracing", @@ -2056,7 +2084,7 @@ dependencies = [ "codex-core", "core_test_support", "rand 0.9.2", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha2", @@ -2137,7 +2165,7 @@ dependencies = [ "codex-core", "futures", "pretty_assertions", - "reqwest", + "reqwest 0.12.28", "semver", "serde_json", "tokio", @@ -2164,7 +2192,7 @@ dependencies = [ "opentelemetry_sdk", "os_info", "pretty_assertions", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "strum_macros 0.27.2", @@ -2220,7 +2248,7 @@ dependencies = [ "codex-process-hardening", "ctor 0.6.3", "libc", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "tiny_http", @@ -2242,7 +2270,7 @@ dependencies = [ "keyring", "oauth2", "pretty_assertions", - "reqwest", + "reqwest 0.12.28", "rmcp", "schemars 0.8.22", "serde", @@ -2428,7 +2456,7 @@ dependencies = [ "ratatui", "ratatui-macros", "regex-lite", - "reqwest", + "reqwest 0.12.28", "rmcp", "serde", "serde_json", @@ -2770,6 +2798,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -2845,7 +2879,7 @@ dependencies = [ "notify", "pretty_assertions", "regex-lite", - "reqwest", + "reqwest 0.12.28", "serde_json", "shlex", "tempfile", @@ -2853,7 +2887,7 @@ dependencies = [ "tokio-tungstenite", "walkdir", "wiremock", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -3022,6 +3056,29 @@ dependencies = [ "typenum", ] +[[package]] +name = "cssparser" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.3", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.114", +] + [[package]] name = "csv" version = "1.4.0" @@ -3342,6 +3399,17 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -3511,6 +3579,21 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + [[package]] name = "dtor" version = "0.1.1" @@ -3558,6 +3641,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ego-tree" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a0bb14ac04a9fcf170d0bbbef949b44cc492f4452bd20c095636956f653642" + [[package]] name = "either" version = "1.15.0" @@ -4021,6 +4110,16 @@ dependencies = [ "libc", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.31" @@ -4249,6 +4348,25 @@ dependencies = [ "regex-syntax 0.8.8", ] +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.13" @@ -4458,6 +4576,20 @@ version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "http" version = "0.2.12" @@ -4479,6 +4611,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -4498,7 +4641,7 @@ dependencies = [ "bytes", "futures-core", "http 1.4.0", - "http-body", + "http-body 1.0.1", "pin-project-lite", ] @@ -4520,6 +4663,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.8.1" @@ -4530,9 +4697,9 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", + "h2 0.4.13", "http 1.4.0", - "http-body", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -4550,7 +4717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.4.0", - "hyper", + "hyper 1.8.1", "hyper-util", "rustls", "rustls-native-certs", @@ -4567,13 +4734,26 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper", + "hyper 1.8.1", "hyper-util", "pin-project-lite", "tokio", "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.32", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -4582,7 +4762,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-util", "native-tls", "tokio", @@ -4602,14 +4782,14 @@ dependencies = [ "futures-core", "futures-util", "http 1.4.0", - "http-body", - "hyper", + "http-body 1.0.1", + "hyper 1.8.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2 0.6.2", - "system-configuration", + "system-configuration 0.6.1", "tokio", "tower-service", "tracing", @@ -5517,6 +5697,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + [[package]] name = "mach2" version = "0.4.3" @@ -5532,6 +5718,20 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + [[package]] name = "matchers" version = "0.2.0" @@ -6029,7 +6229,7 @@ dependencies = [ "getrandom 0.2.17", "http 1.4.0", "rand 0.8.5", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "serde_path_to_error", @@ -6390,7 +6590,7 @@ dependencies = [ "bytes", "http 1.4.0", "opentelemetry", - "reqwest", + "reqwest 0.12.28", ] [[package]] @@ -6405,7 +6605,7 @@ dependencies = [ "opentelemetry-proto", "opentelemetry_sdk", "prost", - "reqwest", + "reqwest 0.12.28", "serde_json", "thiserror 2.0.18", "tokio", @@ -6534,6 +6734,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -6570,6 +6781,18 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + [[package]] name = "pbkdf2" version = "0.12.2" @@ -6626,13 +6849,84 @@ dependencies = [ "indexmap 2.13.0", ] +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher", + "siphasher 1.0.2", ] [[package]] @@ -6809,6 +7103,26 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppt-rs" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2135454fb8669ee8eef79c87a7e9ad6f5d563d13fb1041746e2d3e3e4d716749" +dependencies = [ + "chrono", + "clap", + "pulldown-cmark", + "regex", + "reqwest 0.11.27", + "scraper", + "syntect", + "thiserror 1.0.69", + "url", + "uuid", + "xml-rs", + "zip 0.6.6", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -7189,7 +7503,7 @@ version = "0.3.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3ff6a3c8ae690be8167e43777ba0bf6b0c8c2f6de165c538666affe2a32fd81" dependencies = [ - "h2", + "h2 0.4.13", "pin-project-lite", "rama-core", "rama-http", @@ -7261,7 +7575,7 @@ dependencies = [ "const_format", "fnv", "http 1.4.0", - "http-body", + "http-body 1.0.1", "http-body-util", "itoa", "memchr", @@ -7276,7 +7590,7 @@ dependencies = [ "rand 0.9.2", "serde", "serde_json", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", ] @@ -7646,6 +7960,46 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-tls 0.5.0", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.50.0", +] + [[package]] name = "reqwest" version = "0.12.28" @@ -7658,13 +8012,13 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.4.13", "http 1.4.0", - "http-body", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-rustls", - "hyper-tls", + "hyper-tls 0.6.0", "hyper-util", "js-sys", "log", @@ -7680,7 +8034,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", "tokio-rustls", @@ -7729,14 +8083,14 @@ dependencies = [ "chrono", "futures", "http 1.4.0", - "http-body", + "http-body 1.0.1", "http-body-util", "oauth2", "pastey", "pin-project-lite", "process-wrap", "rand 0.9.2", - "reqwest", + "reqwest 0.12.28", "rmcp-macros", "schemars 1.2.1", "serde", @@ -7924,6 +8278,15 @@ dependencies = [ "security-framework 3.5.1", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pki-types" version = "1.14.0" @@ -8132,13 +8495,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scraper" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585480e3719b311b78a573db1c9d9c4c1f8010c2dee4cc59c2efe58ea4dbc3e1" +dependencies = [ + "ahash", + "cssparser", + "ego-tree", + "getopts", + "html5ever", + "once_cell", + "selectors", + "tendril", +] + [[package]] name = "scrypt" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" dependencies = [ - "pbkdf2", + "pbkdf2 0.12.2", "salsa20", "sha2", ] @@ -8222,6 +8601,25 @@ dependencies = [ "libc", ] +[[package]] +name = "selectors" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" +dependencies = [ + "bitflags 2.10.0", + "cssparser", + "derive_more 0.99.20", + "fxhash", + "log", + "new_debug_unreachable", + "phf 0.10.1", + "phf_codegen", + "precomputed-hash", + "servo_arc", + "smallvec", +] + [[package]] name = "self_cell" version = "0.10.3" @@ -8251,7 +8649,7 @@ checksum = "2f925d575b468e88b079faf590a8dd0c9c99e2ec29e9bab663ceb8b45056312f" dependencies = [ "httpdate", "native-tls", - "reqwest", + "reqwest 0.12.28", "sentry-actix", "sentry-backtrace", "sentry-contexts", @@ -8556,6 +8954,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "servo_arc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "sha1" version = "0.10.6" @@ -8674,6 +9081,12 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +[[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.2" @@ -8960,7 +9373,7 @@ checksum = "eb4dc4d33c68ec1f27d386b5610a351922656e1fdf5c05bbaad930cd1519479a" dependencies = [ "bytes", "futures-util", - "http-body", + "http-body 1.0.1", "http-body-util", "pin-project-lite", ] @@ -9090,8 +9503,21 @@ checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.11.3", "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", ] [[package]] @@ -9204,6 +9630,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -9254,6 +9686,17 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys 0.5.0", +] + [[package]] name = "system-configuration" version = "0.6.1" @@ -9262,7 +9705,17 @@ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.10.0", "core-foundation 0.9.4", - "system-configuration-sys", + "system-configuration-sys 0.6.0", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -9294,6 +9747,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "term" version = "0.7.0" @@ -9745,15 +10209,15 @@ dependencies = [ "base64 0.22.1", "bytes", "http 1.4.0", - "http-body", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", "rustls-native-certs", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tokio-rustls", "tokio-stream", @@ -9785,7 +10249,7 @@ dependencies = [ "indexmap 2.13.0", "pin-project-lite", "slab", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tokio-util", "tower-layer", @@ -9803,7 +10267,7 @@ dependencies = [ "bytes", "futures-util", "http 1.4.0", - "http-body", + "http-body 1.0.1", "iri-string", "pin-project-lite", "tower", @@ -11244,7 +11708,7 @@ dependencies = [ "futures", "http 1.4.0", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-util", "log", "once_cell", @@ -11343,6 +11807,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + [[package]] name = "xz2" version = "0.1.7" @@ -11556,6 +12026,26 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2 0.4.4", + "constant_time_eq 0.1.5", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2 0.11.0", + "sha1", + "time", + "zstd 0.11.2+zstd.1.5.2", +] + [[package]] name = "zip" version = "2.4.2" @@ -11564,8 +12054,8 @@ checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ "aes", "arbitrary", - "bzip2", - "constant_time_eq", + "bzip2 0.5.2", + "constant_time_eq 0.3.1", "crc32fast", "crossbeam-utils", "deflate64", @@ -11576,14 +12066,14 @@ dependencies = [ "indexmap 2.13.0", "lzma-rs", "memchr", - "pbkdf2", + "pbkdf2 0.12.2", "sha1", "thiserror 2.0.18", "time", "xz2", "zeroize", "zopfli", - "zstd", + "zstd 0.13.3", ] [[package]] @@ -11604,13 +12094,32 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", +] + [[package]] name = "zstd" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ - "zstd-safe", + "zstd-safe 7.2.4", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", ] [[package]] diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 01836cde0..396babea3 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -33,6 +33,7 @@ members = [ "mcp-server", "network-proxy", "ollama", + "artifact-presentation", "process-hardening", "protocol", "rmcp-client", @@ -109,6 +110,7 @@ codex-mcp-server = { path = "mcp-server" } codex-network-proxy = { path = "network-proxy" } codex-ollama = { path = "ollama" } codex-otel = { path = "otel" } +codex-artifact-presentation = { path = "artifact-presentation" } codex-process-hardening = { path = "process-hardening" } codex-protocol = { path = "protocol" } codex-responses-api-proxy = { path = "responses-api-proxy" } @@ -215,6 +217,7 @@ owo-colors = "4.3.0" path-absolutize = "3.1.1" pathdiff = "0.2" portable-pty = "0.9.0" +ppt-rs = "0.2.6" predicates = "3" pretty_assertions = "1.4.1" pulldown-cmark = "0.10" diff --git a/codex-rs/artifact-presentation/BUILD.bazel b/codex-rs/artifact-presentation/BUILD.bazel new file mode 100644 index 000000000..d89e395b3 --- /dev/null +++ b/codex-rs/artifact-presentation/BUILD.bazel @@ -0,0 +1,6 @@ +load("//:defs.bzl", "codex_rust_crate") + +codex_rust_crate( + name = "artifact-presentation", + crate_name = "codex_artifact_presentation", +) diff --git a/codex-rs/artifact-presentation/Cargo.toml b/codex-rs/artifact-presentation/Cargo.toml new file mode 100644 index 000000000..19a3d47f9 --- /dev/null +++ b/codex-rs/artifact-presentation/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "codex-artifact-presentation" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +name = "codex_artifact_presentation" +path = "src/lib.rs" + +[lints] +workspace = true + +[dependencies] +base64 = { workspace = true } +image = { workspace = true, features = ["jpeg", "png"] } +ppt-rs = { workspace = true } +reqwest = { workspace = true, features = ["blocking"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +thiserror = { workspace = true } +uuid = { workspace = true, features = ["v4"] } + +[dev-dependencies] +pretty_assertions = { workspace = true } +tempfile = { workspace = true } +tiny_http = { workspace = true } diff --git a/codex-rs/artifact-presentation/src/lib.rs b/codex-rs/artifact-presentation/src/lib.rs new file mode 100644 index 000000000..34bca50a9 --- /dev/null +++ b/codex-rs/artifact-presentation/src/lib.rs @@ -0,0 +1,6 @@ +mod presentation_artifact; + +#[cfg(test)] +mod tests; + +pub use presentation_artifact::*; diff --git a/codex-rs/artifact-presentation/src/presentation_artifact.rs b/codex-rs/artifact-presentation/src/presentation_artifact.rs new file mode 100644 index 000000000..3a8c8ae8d --- /dev/null +++ b/codex-rs/artifact-presentation/src/presentation_artifact.rs @@ -0,0 +1,4701 @@ +use base64::Engine; +use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; +use image::GenericImageView; +use image::ImageFormat; +use image::codecs::jpeg::JpegEncoder; +use image::imageops::FilterType; +use ppt_rs::Chart; +use ppt_rs::ChartSeries; +use ppt_rs::ChartType; +use ppt_rs::Image; +use ppt_rs::Presentation; +use ppt_rs::Shape; +use ppt_rs::ShapeFill; +use ppt_rs::ShapeLine; +use ppt_rs::ShapeType; +use ppt_rs::SlideContent; +use ppt_rs::SlideLayout; +use ppt_rs::TableBuilder; +use ppt_rs::TableCell; +use ppt_rs::TableRow; +use ppt_rs::generator::ArrowSize; +use ppt_rs::generator::ArrowType; +use ppt_rs::generator::CellAlign; +use ppt_rs::generator::Connector; +use ppt_rs::generator::ConnectorLine; +use ppt_rs::generator::ConnectorType; +use ppt_rs::generator::LineDash; +use serde::Deserialize; +use serde::Serialize; +use serde_json::Value; +use std::collections::HashMap; +use std::collections::HashSet; +use std::path::Path; +use std::path::PathBuf; +use thiserror::Error; +use uuid::Uuid; + +const POINT_TO_EMU: u32 = 12_700; +const DEFAULT_SLIDE_WIDTH_POINTS: u32 = 720; +const DEFAULT_SLIDE_HEIGHT_POINTS: u32 = 540; +const DEFAULT_IMPORTED_TITLE_LEFT: u32 = 36; +const DEFAULT_IMPORTED_TITLE_TOP: u32 = 24; +const DEFAULT_IMPORTED_TITLE_WIDTH: u32 = 648; +const DEFAULT_IMPORTED_TITLE_HEIGHT: u32 = 48; +const DEFAULT_IMPORTED_CONTENT_LEFT: u32 = 48; +const DEFAULT_IMPORTED_CONTENT_TOP: u32 = 96; +const DEFAULT_IMPORTED_CONTENT_WIDTH: u32 = 624; +const DEFAULT_IMPORTED_CONTENT_HEIGHT: u32 = 324; + +#[derive(Debug, Error)] +pub enum PresentationArtifactError { + #[error("missing `artifact_id` for action `{action}`")] + MissingArtifactId { action: String }, + #[error("unknown artifact id `{artifact_id}` for action `{action}`")] + UnknownArtifactId { action: String, artifact_id: String }, + #[error("unknown action `{0}`")] + UnknownAction(String), + #[error("invalid args for action `{action}`: {message}")] + InvalidArgs { action: String, message: String }, + #[error("unsupported feature for action `{action}`: {message}")] + UnsupportedFeature { action: String, message: String }, + #[error("failed to import PPTX `{path}`: {message}")] + ImportFailed { path: PathBuf, message: String }, + #[error("failed to export PPTX `{path}`: {message}")] + ExportFailed { path: PathBuf, message: String }, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct PresentationArtifactRequest { + pub artifact_id: Option, + pub action: String, + #[serde(default)] + pub args: Value, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PathAccessKind { + Read, + Write, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PathAccessRequirement { + pub action: String, + pub kind: PathAccessKind, + pub path: PathBuf, +} + +impl PresentationArtifactRequest { + pub fn required_path_accesses( + &self, + cwd: &Path, + ) -> Result, PresentationArtifactError> { + let access = match self.action.as_str() { + "import_pptx" => { + let args: ImportPptxArgs = parse_args(&self.action, &self.args)?; + vec![PathAccessRequirement { + action: self.action.clone(), + kind: PathAccessKind::Read, + path: resolve_path(cwd, &args.path), + }] + } + "export_pptx" => { + let args: ExportPptxArgs = parse_args(&self.action, &self.args)?; + vec![PathAccessRequirement { + action: self.action.clone(), + kind: PathAccessKind::Write, + path: resolve_path(cwd, &args.path), + }] + } + "export_preview" => { + let args: ExportPreviewArgs = parse_args(&self.action, &self.args)?; + vec![PathAccessRequirement { + action: self.action.clone(), + kind: PathAccessKind::Write, + path: resolve_path(cwd, &args.path), + }] + } + "add_image" => { + let args: AddImageArgs = parse_args(&self.action, &self.args)?; + match args.image_source()? { + ImageInputSource::Path(path) => vec![PathAccessRequirement { + action: self.action.clone(), + kind: PathAccessKind::Read, + path: resolve_path(cwd, &path), + }], + ImageInputSource::DataUrl(_) + | ImageInputSource::Uri(_) + | ImageInputSource::Placeholder => Vec::new(), + } + } + "replace_image" => { + let args: ReplaceImageArgs = parse_args(&self.action, &self.args)?; + match (&args.path, &args.data_url, &args.uri, &args.prompt) { + (Some(path), None, None, None) => vec![PathAccessRequirement { + action: self.action.clone(), + kind: PathAccessKind::Read, + path: resolve_path(cwd, path), + }], + (None, Some(_), None, None) + | (None, None, Some(_), None) + | (None, None, None, Some(_)) => Vec::new(), + _ => { + return Err(PresentationArtifactError::InvalidArgs { + action: self.action.clone(), + message: + "provide exactly one of `path`, `data_url`, or `uri`, or provide `prompt` for a placeholder image" + .to_string(), + }); + } + } + } + _ => Vec::new(), + }; + Ok(access) + } +} + +#[derive(Debug, Default)] +pub struct PresentationArtifactManager { + documents: HashMap, +} + +impl PresentationArtifactManager { + pub fn execute( + &mut self, + request: PresentationArtifactRequest, + cwd: &Path, + ) -> Result { + match request.action.as_str() { + "create" => self.create(request), + "import_pptx" => self.import_pptx(request, cwd), + "export_pptx" => self.export_pptx(request, cwd), + "export_preview" => self.export_preview(request, cwd), + "get_summary" => self.get_summary(request), + "list_slides" => self.list_slides(request), + "list_layouts" => self.list_layouts(request), + "list_layout_placeholders" => self.list_layout_placeholders(request), + "list_slide_placeholders" => self.list_slide_placeholders(request), + "inspect" => self.inspect(request), + "resolve" => self.resolve(request), + "add_slide" => self.add_slide(request), + "insert_slide" => self.insert_slide(request), + "duplicate_slide" => self.duplicate_slide(request), + "move_slide" => self.move_slide(request), + "delete_slide" => self.delete_slide(request), + "create_layout" => self.create_layout(request), + "add_layout_placeholder" => self.add_layout_placeholder(request), + "set_slide_layout" => self.set_slide_layout(request), + "update_placeholder_text" => self.update_placeholder_text(request), + "set_theme" => self.set_theme(request), + "set_notes" => self.set_notes(request), + "append_notes" => self.append_notes(request), + "clear_notes" => self.clear_notes(request), + "set_notes_visibility" => self.set_notes_visibility(request), + "set_slide_background" => self.set_slide_background(request), + "add_text_shape" => self.add_text_shape(request), + "add_shape" => self.add_shape(request), + "add_connector" => self.add_connector(request), + "add_image" => self.add_image(request, cwd), + "replace_image" => self.replace_image(request, cwd), + "add_table" => self.add_table(request), + "update_table_cell" => self.update_table_cell(request), + "merge_table_cells" => self.merge_table_cells(request), + "add_chart" => self.add_chart(request), + "update_text" => self.update_text(request), + "update_shape_style" => self.update_shape_style(request), + "bring_to_front" => self.bring_to_front(request), + "send_to_back" => self.send_to_back(request), + "delete_element" => self.delete_element(request), + "delete_artifact" => self.delete_artifact(request), + other => Err(PresentationArtifactError::UnknownAction(other.to_string())), + } + } + + fn create( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: CreateArgs = parse_args(&request.action, &request.args)?; + let mut document = PresentationDocument::new(args.name); + if let Some(slide_size) = args.slide_size { + document.slide_size = parse_slide_size(&slide_size, &request.action)?; + } + if let Some(theme) = args.theme { + document.theme = normalize_theme(theme, &request.action)?; + } + let artifact_id = document.artifact_id.clone(); + let summary = format!( + "Created presentation artifact `{artifact_id}` with {} slides", + document.slides.len() + ); + let snapshot = snapshot_for_document(&document); + let mut response = + PresentationArtifactResponse::new(artifact_id, request.action, summary, snapshot); + response.theme = Some(document.theme_snapshot()); + self.documents + .insert(response.artifact_id.clone(), document); + Ok(response) + } + + fn import_pptx( + &mut self, + request: PresentationArtifactRequest, + cwd: &Path, + ) -> Result { + let args: ImportPptxArgs = parse_args(&request.action, &request.args)?; + let path = resolve_path(cwd, &args.path); + let imported = Presentation::from_path(&path).map_err(|error| { + PresentationArtifactError::ImportFailed { + path: path.clone(), + message: error.to_string(), + } + })?; + let document = PresentationDocument::from_ppt_rs(imported); + let artifact_id = document.artifact_id.clone(); + let slide_count = document.slides.len(); + let snapshot = snapshot_for_document(&document); + self.documents.insert(artifact_id.clone(), document); + let summary = format!( + "Imported `{}` as artifact `{artifact_id}` with {slide_count} slides", + path.display() + ); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + summary, + snapshot, + )) + } + + fn export_pptx( + &mut self, + request: PresentationArtifactRequest, + cwd: &Path, + ) -> Result { + let args: ExportPptxArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let path = resolve_path(cwd, &args.path); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).map_err(|error| { + PresentationArtifactError::ExportFailed { + path: path.clone(), + message: error.to_string(), + } + })?; + } + + document.to_ppt_rs().save(&path).map_err(|error| { + PresentationArtifactError::ExportFailed { + path: path.clone(), + message: error.to_string(), + } + })?; + + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Exported presentation to `{}`", path.display()), + snapshot_for_document(document), + ); + response.exported_paths.push(path); + Ok(response) + } + + fn export_preview( + &mut self, + request: PresentationArtifactRequest, + cwd: &Path, + ) -> Result { + let args: ExportPreviewArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let output_path = resolve_path(cwd, &args.path); + let preview_format = + parse_preview_output_format(args.format.as_deref(), &output_path, &request.action)?; + let scale = normalize_preview_scale(args.scale, &request.action)?; + let quality = normalize_preview_quality(args.quality, &request.action)?; + if let Some(parent) = output_path.parent() { + std::fs::create_dir_all(parent).map_err(|error| { + PresentationArtifactError::ExportFailed { + path: output_path.clone(), + message: error.to_string(), + } + })?; + } + let temp_dir = + std::env::temp_dir().join(format!("presentation_preview_{}", Uuid::new_v4().simple())); + std::fs::create_dir_all(&temp_dir).map_err(|error| { + PresentationArtifactError::ExportFailed { + path: output_path.clone(), + message: error.to_string(), + } + })?; + let preview_document = if let Some(slide_index) = args.slide_index { + let slide = document + .slides + .get(slide_index as usize) + .cloned() + .ok_or_else(|| { + index_out_of_range(&request.action, slide_index as usize, document.slides.len()) + })?; + PresentationDocument { + artifact_id: document.artifact_id.clone(), + name: document.name.clone(), + slide_size: document.slide_size, + theme: document.theme.clone(), + layouts: Vec::new(), + slides: vec![slide], + next_slide_seq: 1, + next_element_seq: 1, + next_layout_seq: 1, + } + } else { + document.clone() + }; + write_preview_images(&preview_document, &temp_dir, &request.action)?; + let mut exported_paths = collect_pngs(&temp_dir)?; + if args.slide_index.is_some() { + let rendered = + exported_paths + .pop() + .ok_or_else(|| PresentationArtifactError::ExportFailed { + path: output_path.clone(), + message: "preview renderer produced no images".to_string(), + })?; + write_preview_image( + &rendered, + &output_path, + preview_format, + scale, + quality, + &request.action, + )?; + exported_paths = vec![output_path]; + } else { + std::fs::create_dir_all(&output_path).map_err(|error| { + PresentationArtifactError::ExportFailed { + path: output_path.clone(), + message: error.to_string(), + } + })?; + let mut relocated = Vec::new(); + for rendered in exported_paths { + let filename = rendered.file_name().ok_or_else(|| { + PresentationArtifactError::ExportFailed { + path: output_path.clone(), + message: "rendered preview had no filename".to_string(), + } + })?; + let stem = Path::new(filename) + .file_stem() + .and_then(|value| value.to_str()) + .unwrap_or("preview"); + let target = output_path.join(format!("{stem}.{}", preview_format.extension())); + write_preview_image( + &rendered, + &target, + preview_format, + scale, + quality, + &request.action, + )?; + relocated.push(target); + } + exported_paths = relocated; + } + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + "Exported slide preview".to_string(), + snapshot_for_document(document), + ); + response.exported_paths = exported_paths; + Ok(response) + } + + fn get_summary( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!( + "Presentation `{}` has {} slides, {} elements, and {} layouts", + document.name.as_deref().unwrap_or("Untitled"), + document.slides.len(), + document.total_element_count(), + document.layouts.len() + ), + snapshot_for_document(document), + ); + response.slide_list = Some(slide_list(document)); + response.layout_list = Some(layout_list(document)); + response.theme = Some(document.theme_snapshot()); + Ok(response) + } + + fn list_slides( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Listed {} slides", document.slides.len()), + snapshot_for_document(document), + ); + response.slide_list = Some(slide_list(document)); + response.theme = Some(document.theme_snapshot()); + Ok(response) + } + + fn list_layouts( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Listed {} layouts", document.layouts.len()), + snapshot_for_document(document), + ); + response.layout_list = Some(layout_list(document)); + response.theme = Some(document.theme_snapshot()); + Ok(response) + } + + fn list_layout_placeholders( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: LayoutIdArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let placeholders = layout_placeholder_list(document, &args.layout_id, &request.action)?; + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!( + "Listed {} placeholders for layout `{}`", + placeholders.len(), + args.layout_id + ), + snapshot_for_document(document), + ); + response.placeholder_list = Some(placeholders); + response.layout_list = Some(layout_list(document)); + Ok(response) + } + + fn list_slide_placeholders( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: SlideIndexArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let slide_index = args.slide_index as usize; + let slide = document.slides.get(slide_index).ok_or_else(|| { + index_out_of_range(&request.action, slide_index, document.slides.len()) + })?; + let placeholders = slide_placeholder_list(slide, slide_index); + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!( + "Listed {} placeholders for slide {}", + placeholders.len(), + args.slide_index + ), + snapshot_for_document(document), + ); + response.placeholder_list = Some(placeholders); + response.slide_list = Some(slide_list(document)); + Ok(response) + } + + fn inspect( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: InspectArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let inspect_ndjson = inspect_document( + document, + args.kind.as_deref(), + args.target_id.as_deref(), + args.max_chars, + ); + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + "Generated inspection snapshot".to_string(), + snapshot_for_document(document), + ); + response.inspect_ndjson = Some(inspect_ndjson); + response.theme = Some(document.theme_snapshot()); + Ok(response) + } + + fn resolve( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: ResolveArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document(&artifact_id, &request.action)?; + let resolved_record = resolve_anchor(document, &args.id, &request.action)?; + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Resolved `{}`", args.id), + snapshot_for_document(document), + ); + response.resolved_record = Some(resolved_record); + Ok(response) + } + + fn create_layout( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: CreateLayoutArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let layout_id = document.next_layout_id(); + let kind = match args.kind.as_deref() { + Some("master") => LayoutKind::Master, + Some("layout") | None => LayoutKind::Layout, + Some(other) => { + return Err(PresentationArtifactError::InvalidArgs { + action: request.action, + message: format!("unsupported layout kind `{other}`"), + }); + } + }; + document.layouts.push(LayoutDocument { + layout_id: layout_id.clone(), + name: args.name, + kind, + parent_layout_id: args.parent_layout_id, + placeholders: Vec::new(), + }); + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Created layout `{layout_id}`"), + snapshot_for_document(document), + ); + response.layout_list = Some(layout_list(document)); + Ok(response) + } + + fn add_layout_placeholder( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: AddLayoutPlaceholderArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let geometry = args + .geometry + .as_deref() + .map(|value| parse_shape_geometry(value, &request.action)) + .transpose()? + .unwrap_or(ShapeGeometry::Rectangle); + let frame = args.position.unwrap_or(PositionArgs { + left: 48, + top: 72, + width: 624, + height: 96, + }); + let layout = document + .layouts + .iter_mut() + .find(|layout| layout.layout_id == args.layout_id) + .ok_or_else(|| PresentationArtifactError::UnsupportedFeature { + action: request.action.clone(), + message: format!("unknown layout id `{}`", args.layout_id), + })?; + layout.placeholders.push(PlaceholderDefinition { + name: args.name, + placeholder_type: args.placeholder_type, + index: args.index, + text: args.text, + geometry, + frame: frame.into(), + }); + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Added placeholder to layout `{}`", layout.layout_id), + snapshot_for_document(document), + ); + response.layout_list = Some(layout_list(document)); + Ok(response) + } + + fn set_slide_layout( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: SetSlideLayoutArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let layout = document + .get_layout(&args.layout_id, &request.action)? + .clone(); + let mut placeholder_elements = Vec::new(); + for placeholder in layout.placeholders { + let element_id = document.next_element_id(); + let placeholder_ref = Some(PlaceholderRef { + name: placeholder.name.clone(), + placeholder_type: placeholder.placeholder_type.clone(), + index: placeholder.index, + }); + if placeholder.geometry == ShapeGeometry::Rectangle { + placeholder_elements.push(PresentationElement::Text(TextElement { + element_id, + text: placeholder.text.unwrap_or_default(), + frame: placeholder.frame, + fill: None, + style: TextStyle::default(), + placeholder: placeholder_ref, + z_order: placeholder_elements.len(), + })); + } else { + placeholder_elements.push(PresentationElement::Shape(ShapeElement { + element_id, + geometry: placeholder.geometry, + frame: placeholder.frame, + fill: None, + stroke: None, + text: placeholder.text, + text_style: TextStyle::default(), + placeholder: placeholder_ref, + rotation_degrees: None, + z_order: placeholder_elements.len(), + })); + } + } + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + slide.elements.retain(|element| match element { + PresentationElement::Text(text) => text.placeholder.is_none(), + PresentationElement::Shape(shape) => shape.placeholder.is_none(), + _ => true, + }); + slide.layout_id = Some(args.layout_id); + slide.elements.extend(placeholder_elements); + resequence_z_order(slide); + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Applied layout to slide {}", args.slide_index), + snapshot_for_document(document), + ); + response.slide_list = Some(slide_list(document)); + response.layout_list = Some(layout_list(document)); + Ok(response) + } + + fn update_placeholder_text( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: UpdatePlaceholderTextArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + let target_name = args.name.to_ascii_lowercase(); + let element = slide + .elements + .iter_mut() + .find(|element| match element { + PresentationElement::Text(text) => text + .placeholder + .as_ref() + .map(|placeholder| placeholder.name.eq_ignore_ascii_case(&target_name)) + .unwrap_or(false), + PresentationElement::Shape(shape) => shape + .placeholder + .as_ref() + .map(|placeholder| placeholder.name.eq_ignore_ascii_case(&target_name)) + .unwrap_or(false), + _ => false, + }) + .ok_or_else(|| PresentationArtifactError::UnsupportedFeature { + action: request.action.clone(), + message: format!( + "placeholder `{}` was not found on slide {}", + args.name, args.slide_index + ), + })?; + match element { + PresentationElement::Text(text) => text.text = args.text, + PresentationElement::Shape(shape) => shape.text = Some(args.text), + PresentationElement::Connector(_) + | PresentationElement::Image(_) + | PresentationElement::Table(_) + | PresentationElement::Chart(_) => {} + } + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!( + "Updated placeholder `{}` on slide {}", + args.name, args.slide_index + ), + snapshot_for_document(document), + )) + } + + fn set_theme( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: ThemeArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + document.theme = normalize_theme(args, &request.action)?; + let mut response = PresentationArtifactResponse::new( + artifact_id, + request.action, + "Updated theme".to_string(), + snapshot_for_document(document), + ); + response.theme = Some(document.theme_snapshot()); + Ok(response) + } + + fn set_notes( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: NotesArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + slide.notes.text = args.text.unwrap_or_default(); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Updated notes for slide {}", args.slide_index), + snapshot_for_document(document), + )) + } + + fn append_notes( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: NotesArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + let text = args.text.unwrap_or_default(); + if slide.notes.text.is_empty() { + slide.notes.text = text; + } else { + slide.notes.text = format!("{}\n{text}", slide.notes.text); + } + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Appended notes for slide {}", args.slide_index), + snapshot_for_document(document), + )) + } + + fn clear_notes( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: NotesArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + slide.notes.text.clear(); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Cleared notes for slide {}", args.slide_index), + snapshot_for_document(document), + )) + } + + fn set_notes_visibility( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: NotesVisibilityArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + slide.notes.visible = args.visible; + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Updated notes visibility for slide {}", args.slide_index), + snapshot_for_document(document), + )) + } + + fn add_slide( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: AddSlideArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let mut slide = document.new_slide(args.notes, args.background_fill, &request.action)?; + if let Some(layout_id) = args.layout { + apply_layout_to_slide(document, &mut slide, &layout_id, &request.action)?; + } + let index = document.append_slide(slide); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Added slide at index {index}"), + snapshot_for_document(document), + )) + } + + fn insert_slide( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: InsertSlideArgs = parse_args(&request.action, &request.args)?; + if args.index.is_some() == args.after_slide_index.is_some() { + return Err(PresentationArtifactError::InvalidArgs { + action: request.action, + message: "provide exactly one of `index` or `after_slide_index`".to_string(), + }); + } + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let index = args.index.map(to_index).transpose()?.unwrap_or_else(|| { + args.after_slide_index + .map(|value| value as usize + 1) + .unwrap_or(0) + }); + if index > document.slides.len() { + return Err(index_out_of_range( + &request.action, + index, + document.slides.len(), + )); + } + let mut slide = document.new_slide(args.notes, args.background_fill, &request.action)?; + if let Some(layout_id) = args.layout { + apply_layout_to_slide(document, &mut slide, &layout_id, &request.action)?; + } + document.slides.insert(index, slide); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Inserted slide at index {index}"), + snapshot_for_document(document), + )) + } + + fn duplicate_slide( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: SlideIndexArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let source = document + .slides + .get(args.slide_index as usize) + .cloned() + .ok_or_else(|| { + index_out_of_range( + &request.action, + args.slide_index as usize, + document.slides.len(), + ) + })?; + let duplicated = document.clone_slide(source); + let insert_at = args.slide_index as usize + 1; + document.slides.insert(insert_at, duplicated); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Duplicated slide {} to index {insert_at}", args.slide_index), + snapshot_for_document(document), + )) + } + + fn move_slide( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: MoveSlideArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let from = args.from_index as usize; + let to = args.to_index as usize; + if from >= document.slides.len() { + return Err(index_out_of_range( + &request.action, + from, + document.slides.len(), + )); + } + if to >= document.slides.len() { + return Err(index_out_of_range( + &request.action, + to, + document.slides.len(), + )); + } + let slide = document.slides.remove(from); + document.slides.insert(to, slide); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Moved slide from index {from} to {to}"), + snapshot_for_document(document), + )) + } + + fn delete_slide( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: SlideIndexArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let index = args.slide_index as usize; + if index >= document.slides.len() { + return Err(index_out_of_range( + &request.action, + index, + document.slides.len(), + )); + } + document.slides.remove(index); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Deleted slide at index {index}"), + snapshot_for_document(document), + )) + } + + fn set_slide_background( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: SetSlideBackgroundArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let fill = normalize_color_with_document(document, &args.fill, &request.action, "fill")?; + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + slide.background_fill = Some(fill); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Updated background for slide {}", args.slide_index), + snapshot_for_document(document), + )) + } + + fn add_text_shape( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: AddTextShapeArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let style = normalize_text_style_with_document(document, &args.styling, &request.action)?; + let fill = args + .styling + .fill + .as_deref() + .map(|value| normalize_color_with_document(document, value, &request.action, "fill")) + .transpose()?; + let element_id = document.next_element_id(); + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + slide.elements.push(PresentationElement::Text(TextElement { + element_id: element_id.clone(), + text: args.text, + frame: args.position.into(), + fill, + style, + placeholder: None, + z_order: slide.elements.len(), + })); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!( + "Added text element `{element_id}` to slide {}", + args.slide_index + ), + snapshot_for_document(document), + )) + } + + fn add_shape( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: AddShapeArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let text_style = + normalize_text_style_with_document(document, &args.text_style, &request.action)?; + let fill = args + .fill + .as_deref() + .map(|value| normalize_color_with_document(document, value, &request.action, "fill")) + .transpose()?; + let stroke = parse_stroke(document, args.stroke, &request.action)?; + let element_id = document.next_element_id(); + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + slide + .elements + .push(PresentationElement::Shape(ShapeElement { + element_id: element_id.clone(), + geometry: parse_shape_geometry(&args.geometry, &request.action)?, + frame: args.position.into(), + fill, + stroke, + text: args.text, + text_style, + placeholder: None, + rotation_degrees: None, + z_order: slide.elements.len(), + })); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!( + "Added shape element `{element_id}` to slide {}", + args.slide_index + ), + snapshot_for_document(document), + )) + } + + fn add_connector( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: AddConnectorArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let element_id = document.next_element_id(); + let line = parse_connector_line(document, args.line, &request.action)?; + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + slide + .elements + .push(PresentationElement::Connector(ConnectorElement { + element_id: element_id.clone(), + connector_type: parse_connector_kind(&args.connector_type, &request.action)?, + start: args.start, + end: args.end, + line: StrokeStyle { + color: line.color, + width: line.width, + }, + line_style: line.style, + start_arrow: args + .start_arrow + .as_deref() + .map(|value| parse_connector_arrow(value, &request.action)) + .transpose()? + .unwrap_or(ConnectorArrowKind::None), + end_arrow: args + .end_arrow + .as_deref() + .map(|value| parse_connector_arrow(value, &request.action)) + .transpose()? + .unwrap_or(ConnectorArrowKind::None), + arrow_size: args + .arrow_size + .as_deref() + .map(|value| parse_connector_arrow_size(value, &request.action)) + .transpose()? + .unwrap_or(ConnectorArrowScale::Medium), + label: args.label, + z_order: slide.elements.len(), + })); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!( + "Added connector element `{element_id}` to slide {}", + args.slide_index + ), + snapshot_for_document(document), + )) + } + + fn add_image( + &mut self, + request: PresentationArtifactRequest, + cwd: &Path, + ) -> Result { + let args: AddImageArgs = parse_args(&request.action, &request.args)?; + let image_source = args.image_source()?; + let is_placeholder = matches!(image_source, ImageInputSource::Placeholder); + let image_payload = match image_source { + ImageInputSource::Path(path) => Some(load_image_payload_from_path( + &resolve_path(cwd, &path), + &request.action, + )?), + ImageInputSource::DataUrl(data_url) => Some(load_image_payload_from_data_url( + &data_url, + &request.action, + )?), + ImageInputSource::Uri(uri) => Some(load_image_payload_from_uri(&uri, &request.action)?), + ImageInputSource::Placeholder => None, + }; + let fit_mode = args.fit.unwrap_or(ImageFitMode::Stretch); + let lock_aspect_ratio = args + .lock_aspect_ratio + .unwrap_or(fit_mode != ImageFitMode::Stretch); + let crop = args + .crop + .map(|crop| normalize_image_crop(crop, &request.action)) + .transpose()?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let element_id = document.next_element_id(); + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + slide + .elements + .push(PresentationElement::Image(ImageElement { + element_id: element_id.clone(), + frame: args.position.into(), + payload: image_payload, + fit_mode, + crop, + lock_aspect_ratio, + alt_text: args.alt, + prompt: args.prompt, + is_placeholder, + z_order: slide.elements.len(), + })); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!( + "Added image element `{element_id}` to slide {}", + args.slide_index + ), + snapshot_for_document(document), + )) + } + + fn replace_image( + &mut self, + request: PresentationArtifactRequest, + cwd: &Path, + ) -> Result { + let args: ReplaceImageArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let image_source = match (&args.path, &args.data_url, &args.uri, &args.prompt) { + (Some(path), None, None, None) => ImageInputSource::Path(path.clone()), + (None, Some(data_url), None, None) => ImageInputSource::DataUrl(data_url.clone()), + (None, None, Some(uri), None) => ImageInputSource::Uri(uri.clone()), + (None, None, None, Some(_)) => ImageInputSource::Placeholder, + _ => { + return Err(PresentationArtifactError::InvalidArgs { + action: request.action, + message: + "provide exactly one of `path`, `data_url`, or `uri`, or provide `prompt` for a placeholder image" + .to_string(), + }); + } + }; + let is_placeholder = matches!(image_source, ImageInputSource::Placeholder); + let image_payload = match image_source { + ImageInputSource::Path(path) => Some(load_image_payload_from_path( + &resolve_path(cwd, &path), + "replace_image", + )?), + ImageInputSource::DataUrl(data_url) => Some(load_image_payload_from_data_url( + &data_url, + "replace_image", + )?), + ImageInputSource::Uri(uri) => Some(load_image_payload_from_uri(&uri, "replace_image")?), + ImageInputSource::Placeholder => None, + }; + let fit_mode = args.fit.unwrap_or(ImageFitMode::Stretch); + let lock_aspect_ratio = args + .lock_aspect_ratio + .unwrap_or(fit_mode != ImageFitMode::Stretch); + let crop = args + .crop + .map(|crop| normalize_image_crop(crop, &request.action)) + .transpose()?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let element = document.find_element_mut(&args.element_id, &request.action)?; + let PresentationElement::Image(image) = element else { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: format!("element `{}` is not an image", args.element_id), + }); + }; + image.payload = image_payload; + image.fit_mode = fit_mode; + image.crop = crop; + image.lock_aspect_ratio = lock_aspect_ratio; + image.alt_text = args.alt; + image.prompt = args.prompt; + image.is_placeholder = is_placeholder; + Ok(PresentationArtifactResponse::new( + artifact_id, + "replace_image".to_string(), + format!("Replaced image `{}`", args.element_id), + snapshot_for_document(document), + )) + } + + fn add_table( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: AddTableArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let element_id = document.next_element_id(); + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + slide + .elements + .push(PresentationElement::Table(TableElement { + element_id: element_id.clone(), + frame: args.position.into(), + rows: coerce_table_rows(args.rows, &request.action)?, + style: args.style, + merges: Vec::new(), + z_order: slide.elements.len(), + })); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!( + "Added table element `{element_id}` to slide {}", + args.slide_index + ), + snapshot_for_document(document), + )) + } + + fn update_table_cell( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: UpdateTableCellArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let text_style = + normalize_text_style_with_document(document, &args.styling, &request.action)?; + let background_fill = args + .background_fill + .as_deref() + .map(|fill| { + normalize_color_with_document(document, fill, &request.action, "background_fill") + }) + .transpose()?; + let element = document.find_element_mut(&args.element_id, &request.action)?; + let PresentationElement::Table(table) = element else { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: format!("element `{}` is not a table", args.element_id), + }); + }; + let row = args.row as usize; + let column = args.column as usize; + if row >= table.rows.len() || column >= table.rows[row].len() { + return Err(PresentationArtifactError::InvalidArgs { + action: request.action, + message: format!("cell ({row}, {column}) is out of bounds"), + }); + } + let cell = &mut table.rows[row][column]; + cell.text = cell_value_to_string(args.value); + cell.text_style = text_style; + cell.background_fill = background_fill; + cell.alignment = args + .alignment + .as_deref() + .map(|value| parse_alignment(value, &request.action)) + .transpose()?; + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Updated table cell ({row}, {column})"), + snapshot_for_document(document), + )) + } + + fn merge_table_cells( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: MergeTableCellsArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let element = document.find_element_mut(&args.element_id, &request.action)?; + let PresentationElement::Table(table) = element else { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: format!("element `{}` is not a table", args.element_id), + }); + }; + let region = TableMergeRegion { + start_row: args.start_row as usize, + end_row: args.end_row as usize, + start_column: args.start_column as usize, + end_column: args.end_column as usize, + }; + table.merges.push(region); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Merged table cells in `{}`", args.element_id), + snapshot_for_document(document), + )) + } + + fn add_chart( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: AddChartArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let chart_type = parse_chart_type(&args.chart_type, &request.action)?; + let series = args + .series + .into_iter() + .map(|entry| { + if entry.values.is_empty() { + return Err(PresentationArtifactError::InvalidArgs { + action: request.action.clone(), + message: format!("series `{}` must contain at least one value", entry.name), + }); + } + Ok(ChartSeriesSpec { + name: entry.name, + values: entry.values, + }) + }) + .collect::, _>>()?; + let element_id = document.next_element_id(); + let slide = document.get_slide_mut(args.slide_index, &request.action)?; + slide + .elements + .push(PresentationElement::Chart(ChartElement { + element_id: element_id.clone(), + frame: args.position.into(), + chart_type, + categories: args.categories, + series, + title: args.title, + z_order: slide.elements.len(), + })); + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!( + "Added chart element `{element_id}` to slide {}", + args.slide_index + ), + snapshot_for_document(document), + )) + } + + fn update_text( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: UpdateTextArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let style = normalize_text_style_with_document(document, &args.styling, &request.action)?; + let fill = args + .styling + .fill + .as_deref() + .map(|value| normalize_color_with_document(document, value, &request.action, "fill")) + .transpose()?; + let element = document.find_element_mut(&args.element_id, &request.action)?; + match element { + PresentationElement::Text(text) => { + text.text = args.text; + if let Some(fill) = fill.clone() { + text.fill = Some(fill); + } + text.style = style; + } + PresentationElement::Shape(shape) => { + if shape.text.is_none() { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: format!( + "element `{}` does not contain editable text", + args.element_id + ), + }); + } + shape.text = Some(args.text); + if let Some(fill) = fill { + shape.fill = Some(fill); + } + shape.text_style = style; + } + other => { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: format!( + "element `{}` is `{}`; only text-bearing elements support `update_text`", + args.element_id, + other.kind() + ), + }); + } + } + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Updated text for element `{}`", args.element_id), + snapshot_for_document(document), + )) + } + + fn update_shape_style( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: UpdateShapeStyleArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let fill = args + .fill + .as_deref() + .map(|value| normalize_color_with_document(document, value, &request.action, "fill")) + .transpose()?; + let stroke = args + .stroke + .clone() + .map(|value| parse_required_stroke(document, value, &request.action)) + .transpose()?; + let element = document.find_element_mut(&args.element_id, &request.action)?; + match element { + PresentationElement::Text(text) => { + if let Some(position) = args.position { + text.frame = apply_partial_position(text.frame, position); + } + if let Some(fill) = fill.clone() { + text.fill = Some(fill); + } + if args.stroke.is_some() || args.rotation.is_some() { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: + "text elements support only `position`, `z_order`, and `fill` updates" + .to_string(), + }); + } + } + PresentationElement::Shape(shape) => { + if let Some(position) = args.position { + shape.frame = apply_partial_position(shape.frame, position); + } + if let Some(fill) = fill { + shape.fill = Some(fill); + } + if let Some(stroke) = stroke { + shape.stroke = Some(stroke); + } + if let Some(rotation) = args.rotation { + shape.rotation_degrees = Some(rotation); + } + } + PresentationElement::Connector(connector) => { + if args.fill.is_some() + || args.rotation.is_some() + || args.fit.is_some() + || args.crop.is_some() + || args.lock_aspect_ratio.is_some() + { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: + "connector elements support only `position`, `stroke`, and `z_order` updates" + .to_string(), + }); + } + if let Some(position) = args.position { + let updated = apply_partial_position( + Rect { + left: connector.start.left, + top: connector.start.top, + width: connector.end.left.abs_diff(connector.start.left), + height: connector.end.top.abs_diff(connector.start.top), + }, + position, + ); + connector.start = PointArgs { + left: updated.left, + top: updated.top, + }; + connector.end = PointArgs { + left: updated.left.saturating_add(updated.width), + top: updated.top.saturating_add(updated.height), + }; + } + if let Some(stroke) = stroke { + connector.line = stroke; + } + } + PresentationElement::Image(image) => { + if args.fill.is_some() || args.stroke.is_some() || args.rotation.is_some() { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: + "image elements support only `position`, `fit`, `crop`, `lock_aspect_ratio`, and `z_order` updates" + .to_string(), + }); + } + if let Some(position) = args.position { + image.frame = apply_partial_position_to_image(image, position); + } + if let Some(fit) = args.fit { + image.fit_mode = fit; + if !matches!(fit, ImageFitMode::Stretch) && args.lock_aspect_ratio.is_none() { + image.lock_aspect_ratio = true; + } + } + if let Some(crop) = args.crop { + image.crop = Some(normalize_image_crop(crop, &request.action)?); + } + if let Some(lock_aspect_ratio) = args.lock_aspect_ratio { + image.lock_aspect_ratio = lock_aspect_ratio; + } + } + PresentationElement::Table(table) => { + if args.fill.is_some() || args.stroke.is_some() || args.rotation.is_some() { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: "table elements support only `position` and `z_order` updates" + .to_string(), + }); + } + if let Some(position) = args.position { + table.frame = apply_partial_position(table.frame, position); + } + } + PresentationElement::Chart(chart) => { + if args.fill.is_some() || args.stroke.is_some() || args.rotation.is_some() { + return Err(PresentationArtifactError::UnsupportedFeature { + action: request.action, + message: "chart elements support only `position` and `z_order` updates" + .to_string(), + }); + } + if let Some(position) = args.position { + chart.frame = apply_partial_position(chart.frame, position); + } + } + } + if let Some(z_order) = args.z_order { + document.set_z_order(&args.element_id, z_order as usize, &request.action)?; + } + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Updated style for element `{}`", args.element_id), + snapshot_for_document(document), + )) + } + + fn delete_element( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: ElementIdArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + document.remove_element(&args.element_id, &request.action)?; + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Deleted element `{}`", args.element_id), + snapshot_for_document(document), + )) + } + + fn bring_to_front( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: ElementIdArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + let target_index = document.total_element_count(); + document.set_z_order(&args.element_id, target_index, &request.action)?; + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Brought `{}` to front", args.element_id), + snapshot_for_document(document), + )) + } + + fn send_to_back( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let args: ElementIdArgs = parse_args(&request.action, &request.args)?; + let artifact_id = required_artifact_id(&request)?; + let document = self.get_document_mut(&artifact_id, &request.action)?; + document.set_z_order(&args.element_id, 0, &request.action)?; + Ok(PresentationArtifactResponse::new( + artifact_id, + request.action, + format!("Sent `{}` to back", args.element_id), + snapshot_for_document(document), + )) + } + + fn delete_artifact( + &mut self, + request: PresentationArtifactRequest, + ) -> Result { + let artifact_id = required_artifact_id(&request)?; + let removed = self.documents.remove(&artifact_id).ok_or_else(|| { + PresentationArtifactError::UnknownArtifactId { + action: request.action.clone(), + artifact_id: artifact_id.clone(), + } + })?; + Ok(PresentationArtifactResponse { + artifact_id, + action: request.action, + summary: format!( + "Deleted in-memory artifact `{}` with {} slides", + removed.artifact_id, + removed.slides.len() + ), + exported_paths: Vec::new(), + artifact_snapshot: None, + slide_list: None, + layout_list: None, + placeholder_list: None, + theme: None, + inspect_ndjson: None, + resolved_record: None, + }) + } + + fn get_document( + &self, + artifact_id: &str, + action: &str, + ) -> Result<&PresentationDocument, PresentationArtifactError> { + self.documents.get(artifact_id).ok_or_else(|| { + PresentationArtifactError::UnknownArtifactId { + action: action.to_string(), + artifact_id: artifact_id.to_string(), + } + }) + } + + fn get_document_mut( + &mut self, + artifact_id: &str, + action: &str, + ) -> Result<&mut PresentationDocument, PresentationArtifactError> { + self.documents.get_mut(artifact_id).ok_or_else(|| { + PresentationArtifactError::UnknownArtifactId { + action: action.to_string(), + artifact_id: artifact_id.to_string(), + } + }) + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct PresentationArtifactResponse { + pub artifact_id: String, + pub action: String, + pub summary: String, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub exported_paths: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub artifact_snapshot: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub slide_list: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub layout_list: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub placeholder_list: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub theme: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub inspect_ndjson: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub resolved_record: Option, +} + +impl PresentationArtifactResponse { + fn new( + artifact_id: String, + action: String, + summary: String, + artifact_snapshot: ArtifactSnapshot, + ) -> Self { + Self { + artifact_id, + action, + summary, + exported_paths: Vec::new(), + artifact_snapshot: Some(artifact_snapshot), + slide_list: None, + layout_list: None, + placeholder_list: None, + theme: None, + inspect_ndjson: None, + resolved_record: None, + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct ArtifactSnapshot { + pub slide_count: usize, + pub slides: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SlideSnapshot { + pub slide_id: String, + pub index: usize, + pub element_ids: Vec, + pub element_types: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SlideListEntry { + pub slide_id: String, + pub index: usize, + pub notes: Option, + pub notes_visible: bool, + pub background_fill: Option, + pub layout_id: Option, + pub element_count: usize, +} + +#[derive(Debug, Clone, Serialize)] +pub struct LayoutListEntry { + pub layout_id: String, + pub name: String, + pub kind: String, + pub parent_layout_id: Option, + pub placeholder_count: usize, +} + +#[derive(Debug, Clone, Serialize)] +pub struct PlaceholderListEntry { + pub scope: String, + pub source_layout_id: Option, + pub slide_index: Option, + pub element_id: Option, + pub name: String, + pub placeholder_type: String, + pub index: Option, + pub geometry: Option, + pub text_preview: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ThemeSnapshot { + pub color_scheme: HashMap, + pub major_font: Option, + pub minor_font: Option, +} + +#[derive(Debug, Clone, Default)] +struct ThemeState { + color_scheme: HashMap, + major_font: Option, + minor_font: Option, +} + +impl ThemeState { + fn resolve_color(&self, color: &str) -> Option { + let key = color.trim().to_ascii_lowercase(); + let alias = match key.as_str() { + "background1" => "bg1", + "background2" => "bg2", + "text1" => "tx1", + "text2" => "tx2", + "dark1" => "dk1", + "dark2" => "dk2", + "light1" => "lt1", + "light2" => "lt2", + other => other, + }; + self.color_scheme + .get(alias) + .or_else(|| self.color_scheme.get(&key)) + .cloned() + .map(|value| value.trim_start_matches('#').to_uppercase()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum LayoutKind { + Layout, + Master, +} + +#[derive(Debug, Clone)] +struct LayoutDocument { + layout_id: String, + name: String, + kind: LayoutKind, + parent_layout_id: Option, + placeholders: Vec, +} + +#[derive(Debug, Clone)] +struct PlaceholderDefinition { + name: String, + placeholder_type: String, + index: Option, + text: Option, + geometry: ShapeGeometry, + frame: Rect, +} + +#[derive(Debug, Clone)] +struct ResolvedPlaceholder { + source_layout_id: String, + definition: PlaceholderDefinition, +} + +#[derive(Debug, Clone, Default)] +struct NotesState { + text: String, + visible: bool, +} + +#[derive(Debug, Clone, Default)] +struct TextStyle { + font_size: Option, + font_family: Option, + color: Option, + alignment: Option, + bold: bool, + italic: bool, + underline: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "camelCase")] +enum TextAlignment { + Left, + Center, + Right, + Justify, +} + +#[derive(Debug, Clone)] +struct PlaceholderRef { + name: String, + placeholder_type: String, + index: Option, +} + +#[derive(Debug, Clone)] +struct TableMergeRegion { + start_row: usize, + end_row: usize, + start_column: usize, + end_column: usize, +} + +#[derive(Debug, Clone)] +struct TableCellSpec { + text: String, + text_style: TextStyle, + background_fill: Option, + alignment: Option, +} + +#[derive(Debug, Clone)] +struct PresentationDocument { + artifact_id: String, + name: Option, + slide_size: Rect, + theme: ThemeState, + layouts: Vec, + slides: Vec, + next_slide_seq: u32, + next_element_seq: u32, + next_layout_seq: u32, +} + +impl PresentationDocument { + fn new(name: Option) -> Self { + Self { + artifact_id: format!("presentation_{}", Uuid::new_v4().simple()), + name, + slide_size: Rect { + left: 0, + top: 0, + width: DEFAULT_SLIDE_WIDTH_POINTS, + height: DEFAULT_SLIDE_HEIGHT_POINTS, + }, + theme: ThemeState::default(), + layouts: Vec::new(), + slides: Vec::new(), + next_slide_seq: 1, + next_element_seq: 1, + next_layout_seq: 1, + } + } + + fn from_ppt_rs(presentation: Presentation) -> Self { + let mut document = Self::new( + (!presentation.get_title().is_empty()).then(|| presentation.get_title().to_string()), + ); + for imported_slide in presentation.slides() { + let mut slide = PresentationSlide { + slide_id: format!("slide_{}", document.next_slide_seq), + notes: NotesState { + text: imported_slide.notes.clone().unwrap_or_default(), + visible: true, + }, + background_fill: None, + layout_id: None, + elements: Vec::new(), + }; + document.next_slide_seq += 1; + + if !imported_slide.title.is_empty() { + slide.elements.push(PresentationElement::Text(TextElement { + element_id: document.next_element_id(), + text: imported_slide.title.clone(), + frame: Rect { + left: DEFAULT_IMPORTED_TITLE_LEFT, + top: DEFAULT_IMPORTED_TITLE_TOP, + width: DEFAULT_IMPORTED_TITLE_WIDTH, + height: DEFAULT_IMPORTED_TITLE_HEIGHT, + }, + fill: None, + style: TextStyle::default(), + placeholder: None, + z_order: slide.elements.len(), + })); + } + + if !imported_slide.content.is_empty() { + slide.elements.push(PresentationElement::Text(TextElement { + element_id: document.next_element_id(), + text: imported_slide.content.join("\n"), + frame: Rect { + left: DEFAULT_IMPORTED_CONTENT_LEFT, + top: DEFAULT_IMPORTED_CONTENT_TOP, + width: DEFAULT_IMPORTED_CONTENT_WIDTH, + height: DEFAULT_IMPORTED_CONTENT_HEIGHT, + }, + fill: None, + style: TextStyle::default(), + placeholder: None, + z_order: slide.elements.len(), + })); + } + + for imported_shape in &imported_slide.shapes { + slide + .elements + .push(PresentationElement::Shape(ShapeElement { + element_id: document.next_element_id(), + geometry: ShapeGeometry::from_shape_type(imported_shape.shape_type), + frame: Rect::from_emu( + imported_shape.x, + imported_shape.y, + imported_shape.width, + imported_shape.height, + ), + fill: imported_shape.fill.as_ref().map(|fill| fill.color.clone()), + stroke: imported_shape.line.as_ref().map(|line| StrokeStyle { + color: line.color.clone(), + width: emu_to_points(line.width), + }), + text: imported_shape.text.clone(), + text_style: TextStyle::default(), + placeholder: None, + rotation_degrees: imported_shape.rotation, + z_order: slide.elements.len(), + })); + } + + if let Some(imported_table) = &imported_slide.table { + slide + .elements + .push(PresentationElement::Table(TableElement { + element_id: document.next_element_id(), + frame: Rect::from_emu( + imported_table.x, + imported_table.y, + imported_table.width(), + imported_table.height(), + ), + rows: imported_table + .rows + .iter() + .map(|row| { + row.cells + .iter() + .map(|text| TableCellSpec { + text: text.text.clone(), + text_style: TextStyle::default(), + background_fill: None, + alignment: None, + }) + .collect() + }) + .collect(), + style: None, + merges: Vec::new(), + z_order: slide.elements.len(), + })); + } + + document.slides.push(slide); + } + document + } + + fn new_slide( + &mut self, + notes: Option, + background_fill: Option, + action: &str, + ) -> Result { + let normalized_fill = background_fill + .map(|value| { + normalize_color_with_palette(Some(&self.theme), &value, action, "background_fill") + }) + .transpose()?; + let slide = PresentationSlide { + slide_id: format!("slide_{}", self.next_slide_seq), + notes: NotesState { + text: notes.unwrap_or_default(), + visible: true, + }, + background_fill: normalized_fill, + layout_id: None, + elements: Vec::new(), + }; + self.next_slide_seq += 1; + Ok(slide) + } + + fn append_slide(&mut self, slide: PresentationSlide) -> usize { + let index = self.slides.len(); + self.slides.push(slide); + index + } + + fn clone_slide(&mut self, slide: PresentationSlide) -> PresentationSlide { + let mut clone = slide; + clone.slide_id = format!("slide_{}", self.next_slide_seq); + self.next_slide_seq += 1; + for element in &mut clone.elements { + element.set_element_id(self.next_element_id()); + } + clone + } + + fn next_element_id(&mut self) -> String { + let element_id = format!("element_{}", self.next_element_seq); + self.next_element_seq += 1; + element_id + } + + fn total_element_count(&self) -> usize { + self.slides.iter().map(|slide| slide.elements.len()).sum() + } + + fn next_layout_id(&mut self) -> String { + let layout_id = format!("layout_{}", self.next_layout_seq); + self.next_layout_seq += 1; + layout_id + } + + fn get_layout( + &self, + layout_id: &str, + action: &str, + ) -> Result<&LayoutDocument, PresentationArtifactError> { + self.layouts + .iter() + .find(|layout| layout.layout_id == layout_id) + .ok_or_else(|| PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("unknown layout id `{layout_id}`"), + }) + } + + fn theme_snapshot(&self) -> ThemeSnapshot { + ThemeSnapshot { + color_scheme: self.theme.color_scheme.clone(), + major_font: self.theme.major_font.clone(), + minor_font: self.theme.minor_font.clone(), + } + } + + fn find_element_mut( + &mut self, + element_id: &str, + action: &str, + ) -> Result<&mut PresentationElement, PresentationArtifactError> { + let element_id = normalize_element_lookup_id(element_id); + for slide in &mut self.slides { + if let Some(element) = slide + .elements + .iter_mut() + .find(|element| element.element_id() == element_id) + { + return Ok(element); + } + } + Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("unknown element id `{element_id}`"), + }) + } + + fn get_slide_mut( + &mut self, + slide_index: u32, + action: &str, + ) -> Result<&mut PresentationSlide, PresentationArtifactError> { + let index = slide_index as usize; + if index >= self.slides.len() { + return Err(index_out_of_range(action, index, self.slides.len())); + } + Ok(&mut self.slides[index]) + } + + fn remove_element( + &mut self, + element_id: &str, + action: &str, + ) -> Result<(), PresentationArtifactError> { + let element_id = normalize_element_lookup_id(element_id); + for slide in &mut self.slides { + if let Some(index) = slide + .elements + .iter() + .position(|element| element.element_id() == element_id) + { + slide.elements.remove(index); + resequence_z_order(slide); + return Ok(()); + } + } + Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("unknown element id `{element_id}`"), + }) + } + + fn set_z_order( + &mut self, + element_id: &str, + target_index: usize, + action: &str, + ) -> Result<(), PresentationArtifactError> { + let element_id = normalize_element_lookup_id(element_id); + for slide in &mut self.slides { + if let Some(current_index) = slide + .elements + .iter() + .position(|element| element.element_id() == element_id) + { + let destination = target_index.min(slide.elements.len().saturating_sub(1)); + let element = slide.elements.remove(current_index); + slide.elements.insert(destination, element); + resequence_z_order(slide); + return Ok(()); + } + } + Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("unknown element id `{element_id}`"), + }) + } + + fn to_ppt_rs(&self) -> Presentation { + let mut presentation = self + .name + .as_deref() + .map(Presentation::with_title) + .unwrap_or_default(); + for slide in &self.slides { + presentation = presentation.add_slide(slide.to_ppt_rs(self.slide_size)); + } + presentation + } +} + +#[derive(Debug, Clone)] +struct PresentationSlide { + slide_id: String, + notes: NotesState, + background_fill: Option, + layout_id: Option, + elements: Vec, +} + +impl PresentationSlide { + fn to_ppt_rs(&self, slide_size: Rect) -> SlideContent { + let mut content = SlideContent::new("").layout(SlideLayout::Blank); + if self.notes.visible && !self.notes.text.is_empty() { + content = content.notes(&self.notes.text); + } + + if let Some(background_fill) = &self.background_fill { + content = content.add_shape( + Shape::new( + ShapeType::Rectangle, + 0, + 0, + points_to_emu(slide_size.width), + points_to_emu(slide_size.height), + ) + .with_fill(ShapeFill::new(background_fill)), + ); + } + + let mut ordered = self.elements.clone(); + ordered.sort_by_key(PresentationElement::z_order); + for element in ordered { + match element { + PresentationElement::Text(text) => { + let mut shape = Shape::new( + ShapeType::Rectangle, + points_to_emu(text.frame.left), + points_to_emu(text.frame.top), + points_to_emu(text.frame.width), + points_to_emu(text.frame.height), + ) + .with_text(&text.text); + if let Some(fill) = text.fill { + shape = shape.with_fill(ShapeFill::new(&fill)); + } + content = content.add_shape(shape); + } + PresentationElement::Shape(shape) => { + let mut ppt_shape = Shape::new( + shape.geometry.to_ppt_rs(), + points_to_emu(shape.frame.left), + points_to_emu(shape.frame.top), + points_to_emu(shape.frame.width), + points_to_emu(shape.frame.height), + ); + if let Some(text) = shape.text { + ppt_shape = ppt_shape.with_text(&text); + } + if let Some(fill) = shape.fill { + ppt_shape = ppt_shape.with_fill(ShapeFill::new(&fill)); + } + if let Some(stroke) = shape.stroke { + ppt_shape = ppt_shape + .with_line(ShapeLine::new(&stroke.color, points_to_emu(stroke.width))); + } + if let Some(rotation) = shape.rotation_degrees { + ppt_shape = ppt_shape.with_rotation(rotation); + } + content = content.add_shape(ppt_shape); + } + PresentationElement::Connector(connector) => { + let mut ppt_connector = Connector::new( + connector.connector_type.to_ppt_rs(), + points_to_emu(connector.start.left), + points_to_emu(connector.start.top), + points_to_emu(connector.end.left), + points_to_emu(connector.end.top), + ) + .with_line( + ConnectorLine::new( + &connector.line.color, + points_to_emu(connector.line.width), + ) + .with_dash(connector.line_style.to_ppt_rs()), + ) + .with_arrow_size(connector.arrow_size.to_ppt_rs()) + .with_start_arrow(connector.start_arrow.to_ppt_rs()) + .with_end_arrow(connector.end_arrow.to_ppt_rs()); + if let Some(label) = connector.label { + ppt_connector = ppt_connector.with_label(&label); + } + content = content.add_connector(ppt_connector); + } + PresentationElement::Image(image) => { + if let Some(ref payload) = image.payload { + let mut ppt_image = Image::from_bytes( + payload.bytes.clone(), + points_to_emu(image.frame.width), + points_to_emu(image.frame.height), + &payload.format, + ) + .position( + points_to_emu(image.frame.left), + points_to_emu(image.frame.top), + ); + if image.fit_mode != ImageFitMode::Stretch { + let (x, y, width, height, crop) = fit_image(&image); + ppt_image = Image::from_bytes( + payload.bytes.clone(), + points_to_emu(width), + points_to_emu(height), + &payload.format, + ) + .position(points_to_emu(x), points_to_emu(y)); + if let Some((left, top, right, bottom)) = crop { + ppt_image = ppt_image.with_crop(left, top, right, bottom); + } + } + if let Some((left, top, right, bottom)) = image.crop { + ppt_image = ppt_image.with_crop(left, top, right, bottom); + } + content = content.add_image(ppt_image); + } else { + content = content.add_shape( + Shape::new( + ShapeType::Rectangle, + points_to_emu(image.frame.left), + points_to_emu(image.frame.top), + points_to_emu(image.frame.width), + points_to_emu(image.frame.height), + ) + .with_text(image.prompt.as_deref().unwrap_or("Image placeholder")), + ); + } + } + PresentationElement::Table(table) => { + let row_count = table.rows.len().max(1) as u32; + let column_count = + table.rows.iter().map(std::vec::Vec::len).max().unwrap_or(1) as u32; + let column_width = points_to_emu(table.frame.width / column_count.max(1)); + let mut builder = TableBuilder::new(vec![column_width; column_count as usize]) + .position( + points_to_emu(table.frame.left), + points_to_emu(table.frame.top), + ); + for (row_index, row) in table.rows.into_iter().enumerate() { + let cells = row + .into_iter() + .enumerate() + .map(|(column_index, cell)| { + build_table_cell(cell, &table.merges, row_index, column_index) + }) + .collect::>(); + builder = builder.add_row(TableRow::new(cells)); + } + let _ = row_count; + content = content.table(builder.build()); + } + PresentationElement::Chart(chart) => { + let mut ppt_chart = Chart::new( + chart.title.as_deref().unwrap_or("Chart"), + chart.chart_type.to_ppt_rs(), + chart.categories, + points_to_emu(chart.frame.left), + points_to_emu(chart.frame.top), + points_to_emu(chart.frame.width), + points_to_emu(chart.frame.height), + ); + for series in chart.series { + ppt_chart = + ppt_chart.add_series(ChartSeries::new(&series.name, series.values)); + } + content = content.add_chart(ppt_chart); + } + } + } + content + } +} + +#[derive(Debug, Clone)] +enum PresentationElement { + Text(TextElement), + Shape(ShapeElement), + Connector(ConnectorElement), + Image(ImageElement), + Table(TableElement), + Chart(ChartElement), +} + +impl PresentationElement { + fn element_id(&self) -> &str { + match self { + Self::Text(element) => &element.element_id, + Self::Shape(element) => &element.element_id, + Self::Connector(element) => &element.element_id, + Self::Image(element) => &element.element_id, + Self::Table(element) => &element.element_id, + Self::Chart(element) => &element.element_id, + } + } + + fn set_element_id(&mut self, new_id: String) { + match self { + Self::Text(element) => element.element_id = new_id, + Self::Shape(element) => element.element_id = new_id, + Self::Connector(element) => element.element_id = new_id, + Self::Image(element) => element.element_id = new_id, + Self::Table(element) => element.element_id = new_id, + Self::Chart(element) => element.element_id = new_id, + } + } + + fn kind(&self) -> &'static str { + match self { + Self::Text(_) => "text", + Self::Shape(_) => "shape", + Self::Connector(_) => "connector", + Self::Image(_) => "image", + Self::Table(_) => "table", + Self::Chart(_) => "chart", + } + } + + fn z_order(&self) -> usize { + match self { + Self::Text(element) => element.z_order, + Self::Shape(element) => element.z_order, + Self::Connector(element) => element.z_order, + Self::Image(element) => element.z_order, + Self::Table(element) => element.z_order, + Self::Chart(element) => element.z_order, + } + } + + fn set_z_order(&mut self, z_order: usize) { + match self { + Self::Text(element) => element.z_order = z_order, + Self::Shape(element) => element.z_order = z_order, + Self::Connector(element) => element.z_order = z_order, + Self::Image(element) => element.z_order = z_order, + Self::Table(element) => element.z_order = z_order, + Self::Chart(element) => element.z_order = z_order, + } + } +} + +#[derive(Debug, Clone)] +struct TextElement { + element_id: String, + text: String, + frame: Rect, + fill: Option, + style: TextStyle, + placeholder: Option, + z_order: usize, +} + +#[derive(Debug, Clone)] +struct ShapeElement { + element_id: String, + geometry: ShapeGeometry, + frame: Rect, + fill: Option, + stroke: Option, + text: Option, + text_style: TextStyle, + placeholder: Option, + rotation_degrees: Option, + z_order: usize, +} + +#[derive(Debug, Clone)] +struct ConnectorElement { + element_id: String, + connector_type: ConnectorKind, + start: PointArgs, + end: PointArgs, + line: StrokeStyle, + line_style: LineStyle, + start_arrow: ConnectorArrowKind, + end_arrow: ConnectorArrowKind, + arrow_size: ConnectorArrowScale, + label: Option, + z_order: usize, +} + +#[derive(Debug, Clone)] +pub(crate) struct ImageElement { + pub(crate) element_id: String, + pub(crate) frame: Rect, + pub(crate) payload: Option, + pub(crate) fit_mode: ImageFitMode, + pub(crate) crop: Option, + pub(crate) lock_aspect_ratio: bool, + pub(crate) alt_text: Option, + pub(crate) prompt: Option, + pub(crate) is_placeholder: bool, + pub(crate) z_order: usize, +} + +#[derive(Debug, Clone)] +struct TableElement { + element_id: String, + frame: Rect, + rows: Vec>, + style: Option, + merges: Vec, + z_order: usize, +} + +#[derive(Debug, Clone)] +struct ChartElement { + element_id: String, + frame: Rect, + chart_type: ChartTypeSpec, + categories: Vec, + series: Vec, + title: Option, + z_order: usize, +} + +#[derive(Debug, Clone)] +pub(crate) struct ImagePayload { + pub(crate) bytes: Vec, + pub(crate) format: String, + pub(crate) width_px: u32, + pub(crate) height_px: u32, +} + +#[derive(Debug, Clone)] +struct ChartSeriesSpec { + name: String, + values: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ShapeGeometry { + Rectangle, + RoundedRectangle, + Ellipse, + Triangle, + RightTriangle, + Diamond, + Pentagon, + Hexagon, + Octagon, + Star4, + Star5, + Star6, + Star8, + RightArrow, + LeftArrow, + UpArrow, + DownArrow, + LeftRightArrow, + UpDownArrow, + Chevron, + Heart, + Cloud, + Wave, + FlowChartProcess, + FlowChartDecision, + FlowChartConnector, + Parallelogram, + Trapezoid, +} + +impl ShapeGeometry { + fn from_shape_type(shape_type: ShapeType) -> Self { + match shape_type { + ShapeType::RoundedRectangle => Self::RoundedRectangle, + ShapeType::Ellipse | ShapeType::Circle => Self::Ellipse, + ShapeType::Triangle => Self::Triangle, + ShapeType::RightTriangle => Self::RightTriangle, + ShapeType::Diamond => Self::Diamond, + ShapeType::Pentagon => Self::Pentagon, + ShapeType::Hexagon => Self::Hexagon, + ShapeType::Octagon => Self::Octagon, + ShapeType::Star4 => Self::Star4, + ShapeType::Star5 => Self::Star5, + ShapeType::Star6 => Self::Star6, + ShapeType::Star8 => Self::Star8, + ShapeType::RightArrow => Self::RightArrow, + ShapeType::LeftArrow => Self::LeftArrow, + ShapeType::UpArrow => Self::UpArrow, + ShapeType::DownArrow => Self::DownArrow, + ShapeType::LeftRightArrow => Self::LeftRightArrow, + ShapeType::UpDownArrow => Self::UpDownArrow, + ShapeType::ChevronArrow => Self::Chevron, + ShapeType::Heart => Self::Heart, + ShapeType::Cloud => Self::Cloud, + ShapeType::Wave => Self::Wave, + ShapeType::FlowChartProcess => Self::FlowChartProcess, + ShapeType::FlowChartDecision => Self::FlowChartDecision, + ShapeType::FlowChartConnector => Self::FlowChartConnector, + ShapeType::Parallelogram => Self::Parallelogram, + ShapeType::Trapezoid => Self::Trapezoid, + _ => Self::Rectangle, + } + } + + fn to_ppt_rs(self) -> ShapeType { + match self { + Self::Rectangle => ShapeType::Rectangle, + Self::RoundedRectangle => ShapeType::RoundedRectangle, + Self::Ellipse => ShapeType::Ellipse, + Self::Triangle => ShapeType::Triangle, + Self::RightTriangle => ShapeType::RightTriangle, + Self::Diamond => ShapeType::Diamond, + Self::Pentagon => ShapeType::Pentagon, + Self::Hexagon => ShapeType::Hexagon, + Self::Octagon => ShapeType::Octagon, + Self::Star4 => ShapeType::Star4, + Self::Star5 => ShapeType::Star5, + Self::Star6 => ShapeType::Star6, + Self::Star8 => ShapeType::Star8, + Self::RightArrow => ShapeType::RightArrow, + Self::LeftArrow => ShapeType::LeftArrow, + Self::UpArrow => ShapeType::UpArrow, + Self::DownArrow => ShapeType::DownArrow, + Self::LeftRightArrow => ShapeType::LeftRightArrow, + Self::UpDownArrow => ShapeType::UpDownArrow, + Self::Chevron => ShapeType::ChevronArrow, + Self::Heart => ShapeType::Heart, + Self::Cloud => ShapeType::Cloud, + Self::Wave => ShapeType::Wave, + Self::FlowChartProcess => ShapeType::FlowChartProcess, + Self::FlowChartDecision => ShapeType::FlowChartDecision, + Self::FlowChartConnector => ShapeType::FlowChartConnector, + Self::Parallelogram => ShapeType::Parallelogram, + Self::Trapezoid => ShapeType::Trapezoid, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ChartTypeSpec { + Bar, + BarHorizontal, + BarStacked, + BarStacked100, + Line, + LineMarkers, + LineStacked, + Pie, + Doughnut, + Area, + AreaStacked, + AreaStacked100, + Scatter, + ScatterLines, + ScatterSmooth, + Bubble, + Radar, + RadarFilled, + StockHlc, + StockOhlc, + Combo, +} + +impl ChartTypeSpec { + fn to_ppt_rs(self) -> ChartType { + match self { + Self::Bar => ChartType::Bar, + Self::BarHorizontal => ChartType::BarHorizontal, + Self::BarStacked => ChartType::BarStacked, + Self::BarStacked100 => ChartType::BarStacked100, + Self::Line => ChartType::Line, + Self::LineMarkers => ChartType::LineMarkers, + Self::LineStacked => ChartType::LineStacked, + Self::Pie => ChartType::Pie, + Self::Doughnut => ChartType::Doughnut, + Self::Area => ChartType::Area, + Self::AreaStacked => ChartType::AreaStacked, + Self::AreaStacked100 => ChartType::AreaStacked100, + Self::Scatter => ChartType::Scatter, + Self::ScatterLines => ChartType::ScatterLines, + Self::ScatterSmooth => ChartType::ScatterSmooth, + Self::Bubble => ChartType::Bubble, + Self::Radar => ChartType::Radar, + Self::RadarFilled => ChartType::RadarFilled, + Self::StockHlc => ChartType::StockHLC, + Self::StockOhlc => ChartType::StockOHLC, + Self::Combo => ChartType::Combo, + } + } +} + +impl ConnectorKind { + fn to_ppt_rs(self) -> ConnectorType { + match self { + Self::Straight => ConnectorType::Straight, + Self::Elbow => ConnectorType::Elbow, + Self::Curved => ConnectorType::Curved, + } + } +} + +impl ConnectorArrowKind { + fn to_ppt_rs(self) -> ArrowType { + match self { + Self::None => ArrowType::None, + Self::Triangle => ArrowType::Triangle, + Self::Stealth => ArrowType::Stealth, + Self::Diamond => ArrowType::Diamond, + Self::Oval => ArrowType::Oval, + Self::Open => ArrowType::Open, + } + } +} + +impl ConnectorArrowScale { + fn to_ppt_rs(self) -> ArrowSize { + match self { + Self::Small => ArrowSize::Small, + Self::Medium => ArrowSize::Medium, + Self::Large => ArrowSize::Large, + } + } +} + +impl LineStyle { + fn to_ppt_rs(self) -> LineDash { + match self { + Self::Solid => LineDash::Solid, + Self::Dashed => LineDash::Dash, + Self::Dotted => LineDash::Dot, + Self::DashDot => LineDash::DashDot, + Self::DashDotDot => LineDash::DashDotDot, + Self::LongDash => LineDash::LongDash, + Self::LongDashDot => LineDash::LongDashDot, + } + } +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub(crate) enum ImageFitMode { + Stretch, + Contain, + Cover, +} + +#[derive(Debug, Clone)] +struct StrokeStyle { + color: String, + width: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ConnectorKind { + Straight, + Elbow, + Curved, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ConnectorArrowKind { + None, + Triangle, + Stealth, + Diamond, + Oval, + Open, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ConnectorArrowScale { + Small, + Medium, + Large, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum LineStyle { + Solid, + Dashed, + Dotted, + DashDot, + DashDotDot, + LongDash, + LongDashDot, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct Rect { + pub(crate) left: u32, + pub(crate) top: u32, + pub(crate) width: u32, + pub(crate) height: u32, +} + +impl Rect { + fn from_emu(left: u32, top: u32, width: u32, height: u32) -> Self { + Self { + left: emu_to_points(left), + top: emu_to_points(top), + width: emu_to_points(width), + height: emu_to_points(height), + } + } +} + +impl From for Rect { + fn from(value: PositionArgs) -> Self { + Self { + left: value.left, + top: value.top, + width: value.width, + height: value.height, + } + } +} + +fn apply_partial_position(rect: Rect, position: PartialPositionArgs) -> Rect { + Rect { + left: position.left.unwrap_or(rect.left), + top: position.top.unwrap_or(rect.top), + width: position.width.unwrap_or(rect.width), + height: position.height.unwrap_or(rect.height), + } +} + +fn apply_partial_position_to_image(image: &ImageElement, position: PartialPositionArgs) -> Rect { + let mut frame = apply_partial_position(image.frame, position.clone()); + if image.lock_aspect_ratio { + let base_ratio = image + .payload + .as_ref() + .map(|payload| payload.width_px as f64 / payload.height_px as f64) + .unwrap_or_else(|| image.frame.width as f64 / image.frame.height as f64); + if let Some(width) = position.width + && position.height.is_none() + { + frame.height = (width as f64 / base_ratio).round() as u32; + } else if let Some(height) = position.height + && position.width.is_none() + { + frame.width = (height as f64 * base_ratio).round() as u32; + } + } + frame +} + +#[derive(Debug, Deserialize)] +struct CreateArgs { + name: Option, + slide_size: Option, + theme: Option, +} + +#[derive(Debug, Deserialize)] +struct ImportPptxArgs { + path: PathBuf, +} + +#[derive(Debug, Deserialize)] +struct ExportPptxArgs { + path: PathBuf, +} + +#[derive(Debug, Deserialize)] +struct ExportPreviewArgs { + path: PathBuf, + slide_index: Option, + format: Option, + scale: Option, + quality: Option, +} + +#[derive(Debug, Default, Deserialize)] +struct AddSlideArgs { + layout: Option, + notes: Option, + background_fill: Option, +} + +#[derive(Debug, Deserialize)] +struct CreateLayoutArgs { + name: String, + kind: Option, + parent_layout_id: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum PreviewOutputFormat { + Png, + Jpeg, +} + +impl PreviewOutputFormat { + fn extension(self) -> &'static str { + match self { + Self::Png => "png", + Self::Jpeg => "jpg", + } + } +} + +#[derive(Debug, Deserialize)] +struct AddLayoutPlaceholderArgs { + layout_id: String, + name: String, + placeholder_type: String, + index: Option, + text: Option, + geometry: Option, + position: Option, +} + +#[derive(Debug, Deserialize)] +struct LayoutIdArgs { + layout_id: String, +} + +#[derive(Debug, Deserialize)] +struct SetSlideLayoutArgs { + slide_index: u32, + layout_id: String, +} + +#[derive(Debug, Deserialize)] +struct UpdatePlaceholderTextArgs { + slide_index: u32, + name: String, + text: String, +} + +#[derive(Debug, Deserialize)] +struct NotesArgs { + slide_index: u32, + text: Option, +} + +#[derive(Debug, Deserialize)] +struct NotesVisibilityArgs { + slide_index: u32, + visible: bool, +} + +#[derive(Debug, Deserialize)] +struct ThemeArgs { + color_scheme: HashMap, + major_font: Option, + minor_font: Option, +} + +#[derive(Debug, Deserialize)] +struct InspectArgs { + kind: Option, + target_id: Option, + max_chars: Option, +} + +#[derive(Debug, Deserialize)] +struct ResolveArgs { + id: String, +} + +#[derive(Debug, Default, Deserialize)] +struct InsertSlideArgs { + index: Option, + after_slide_index: Option, + layout: Option, + notes: Option, + background_fill: Option, +} + +#[derive(Debug, Deserialize)] +struct SlideIndexArgs { + slide_index: u32, +} + +#[derive(Debug, Deserialize)] +struct MoveSlideArgs { + from_index: u32, + to_index: u32, +} + +#[derive(Debug, Deserialize)] +struct SetSlideBackgroundArgs { + slide_index: u32, + fill: String, +} + +#[derive(Debug, Clone, Deserialize)] +struct PositionArgs { + left: u32, + top: u32, + width: u32, + height: u32, +} + +#[derive(Debug, Clone, Default, Deserialize)] +struct PartialPositionArgs { + left: Option, + top: Option, + width: Option, + height: Option, +} + +#[derive(Debug, Default, Deserialize)] +struct TextStylingArgs { + font_size: Option, + font_family: Option, + color: Option, + fill: Option, + alignment: Option, + bold: Option, + italic: Option, +} + +#[derive(Debug, Deserialize)] +struct AddTextShapeArgs { + slide_index: u32, + text: String, + position: PositionArgs, + #[serde(flatten)] + styling: TextStylingArgs, +} + +#[derive(Debug, Clone, Default, Deserialize)] +struct StrokeArgs { + color: String, + width: u32, +} + +#[derive(Debug, Deserialize)] +struct AddShapeArgs { + slide_index: u32, + geometry: String, + position: PositionArgs, + fill: Option, + stroke: Option, + text: Option, + #[serde(default)] + text_style: TextStylingArgs, +} + +#[derive(Debug, Clone, Default, Deserialize)] +struct ConnectorLineArgs { + color: Option, + width: Option, + style: Option, +} + +#[derive(Debug, Clone, Deserialize)] +struct PointArgs { + left: u32, + top: u32, +} + +#[derive(Debug, Deserialize)] +struct AddConnectorArgs { + slide_index: u32, + connector_type: String, + start: PointArgs, + end: PointArgs, + line: Option, + start_arrow: Option, + end_arrow: Option, + arrow_size: Option, + label: Option, +} + +#[derive(Debug, Deserialize)] +struct AddImageArgs { + slide_index: u32, + path: Option, + data_url: Option, + uri: Option, + position: PositionArgs, + fit: Option, + crop: Option, + lock_aspect_ratio: Option, + alt: Option, + prompt: Option, +} + +impl AddImageArgs { + fn image_source(&self) -> Result { + match (&self.path, &self.data_url, &self.uri) { + (Some(path), None, None) => Ok(ImageInputSource::Path(path.clone())), + (None, Some(data_url), None) => Ok(ImageInputSource::DataUrl(data_url.clone())), + (None, None, Some(uri)) => Ok(ImageInputSource::Uri(uri.clone())), + (None, None, None) if self.prompt.is_some() => Ok(ImageInputSource::Placeholder), + _ => Err(PresentationArtifactError::InvalidArgs { + action: "add_image".to_string(), + message: + "provide exactly one of `path`, `data_url`, or `uri`, or provide `prompt` for a placeholder image" + .to_string(), + }), + } + } +} + +enum ImageInputSource { + Path(PathBuf), + DataUrl(String), + Uri(String), + Placeholder, +} + +#[derive(Debug, Clone, Deserialize)] +struct ImageCropArgs { + left: f64, + top: f64, + right: f64, + bottom: f64, +} + +#[derive(Debug, Deserialize)] +struct AddTableArgs { + slide_index: u32, + position: PositionArgs, + rows: Vec>, + style: Option, +} + +#[derive(Debug, Deserialize)] +struct AddChartArgs { + slide_index: u32, + position: PositionArgs, + chart_type: String, + categories: Vec, + series: Vec, + title: Option, +} + +#[derive(Debug, Deserialize)] +struct ChartSeriesArgs { + name: String, + values: Vec, +} + +#[derive(Debug, Deserialize)] +struct UpdateTextArgs { + element_id: String, + text: String, + #[serde(default)] + styling: TextStylingArgs, +} + +#[derive(Debug, Deserialize)] +struct UpdateShapeStyleArgs { + element_id: String, + position: Option, + fill: Option, + stroke: Option, + rotation: Option, + fit: Option, + crop: Option, + lock_aspect_ratio: Option, + z_order: Option, +} + +#[derive(Debug, Deserialize)] +struct ElementIdArgs { + element_id: String, +} + +#[derive(Debug, Deserialize)] +struct ReplaceImageArgs { + element_id: String, + path: Option, + data_url: Option, + uri: Option, + fit: Option, + crop: Option, + lock_aspect_ratio: Option, + alt: Option, + prompt: Option, +} + +#[derive(Debug, Deserialize)] +struct UpdateTableCellArgs { + element_id: String, + row: u32, + column: u32, + value: Value, + #[serde(default)] + styling: TextStylingArgs, + background_fill: Option, + alignment: Option, +} + +#[derive(Debug, Deserialize)] +struct MergeTableCellsArgs { + element_id: String, + start_row: u32, + end_row: u32, + start_column: u32, + end_column: u32, +} + +fn parse_args(action: &str, value: &Value) -> Result +where + T: for<'de> Deserialize<'de>, +{ + serde_json::from_value(value.clone()).map_err(|error| PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: error.to_string(), + }) +} + +fn required_artifact_id( + request: &PresentationArtifactRequest, +) -> Result { + request + .artifact_id + .clone() + .ok_or_else(|| PresentationArtifactError::MissingArtifactId { + action: request.action.clone(), + }) +} + +fn resolve_path(cwd: &Path, path: &Path) -> PathBuf { + if path.is_absolute() { + path.to_path_buf() + } else { + cwd.join(path) + } +} + +fn normalize_color( + color: &str, + action: &str, + field: &str, +) -> Result { + normalize_color_with_palette(None, color, action, field) +} + +fn normalize_color_with_document( + document: &PresentationDocument, + color: &str, + action: &str, + field: &str, +) -> Result { + normalize_color_with_palette(Some(&document.theme), color, action, field) +} + +fn normalize_color_with_palette( + theme: Option<&ThemeState>, + color: &str, + action: &str, + field: &str, +) -> Result { + let trimmed = color.trim(); + let normalized = theme + .and_then(|palette| palette.resolve_color(trimmed)) + .unwrap_or_else(|| trimmed.trim_start_matches('#').to_uppercase()); + if normalized.len() != 6 + || !normalized + .chars() + .all(|character| character.is_ascii_hexdigit()) + { + return Err(PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("field `{field}` must be a 6-digit RGB hex color"), + }); + } + Ok(normalized) +} + +fn parse_shape_geometry( + geometry: &str, + action: &str, +) -> Result { + match geometry { + "rectangle" | "rect" => Ok(ShapeGeometry::Rectangle), + "rounded_rectangle" | "roundedRect" => Ok(ShapeGeometry::RoundedRectangle), + "ellipse" | "circle" => Ok(ShapeGeometry::Ellipse), + "triangle" => Ok(ShapeGeometry::Triangle), + "right_triangle" => Ok(ShapeGeometry::RightTriangle), + "diamond" => Ok(ShapeGeometry::Diamond), + "pentagon" => Ok(ShapeGeometry::Pentagon), + "hexagon" => Ok(ShapeGeometry::Hexagon), + "octagon" => Ok(ShapeGeometry::Octagon), + "star4" => Ok(ShapeGeometry::Star4), + "star" | "star5" => Ok(ShapeGeometry::Star5), + "star6" => Ok(ShapeGeometry::Star6), + "star8" => Ok(ShapeGeometry::Star8), + "right_arrow" => Ok(ShapeGeometry::RightArrow), + "left_arrow" => Ok(ShapeGeometry::LeftArrow), + "up_arrow" => Ok(ShapeGeometry::UpArrow), + "down_arrow" => Ok(ShapeGeometry::DownArrow), + "left_right_arrow" | "leftRightArrow" => Ok(ShapeGeometry::LeftRightArrow), + "up_down_arrow" | "upDownArrow" => Ok(ShapeGeometry::UpDownArrow), + "chevron" => Ok(ShapeGeometry::Chevron), + "heart" => Ok(ShapeGeometry::Heart), + "cloud" => Ok(ShapeGeometry::Cloud), + "wave" => Ok(ShapeGeometry::Wave), + "flowChartProcess" | "flow_chart_process" => Ok(ShapeGeometry::FlowChartProcess), + "flowChartDecision" | "flow_chart_decision" => Ok(ShapeGeometry::FlowChartDecision), + "flowChartConnector" | "flow_chart_connector" => Ok(ShapeGeometry::FlowChartConnector), + "parallelogram" => Ok(ShapeGeometry::Parallelogram), + "trapezoid" => Ok(ShapeGeometry::Trapezoid), + _ => Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("geometry `{geometry}` is not supported"), + }), + } +} + +fn parse_chart_type( + chart_type: &str, + action: &str, +) -> Result { + match chart_type { + "bar" => Ok(ChartTypeSpec::Bar), + "bar_horizontal" => Ok(ChartTypeSpec::BarHorizontal), + "bar_stacked" => Ok(ChartTypeSpec::BarStacked), + "bar_stacked_100" => Ok(ChartTypeSpec::BarStacked100), + "line" => Ok(ChartTypeSpec::Line), + "line_markers" => Ok(ChartTypeSpec::LineMarkers), + "line_stacked" => Ok(ChartTypeSpec::LineStacked), + "pie" => Ok(ChartTypeSpec::Pie), + "doughnut" => Ok(ChartTypeSpec::Doughnut), + "area" => Ok(ChartTypeSpec::Area), + "area_stacked" => Ok(ChartTypeSpec::AreaStacked), + "area_stacked_100" => Ok(ChartTypeSpec::AreaStacked100), + "scatter" => Ok(ChartTypeSpec::Scatter), + "scatter_lines" => Ok(ChartTypeSpec::ScatterLines), + "scatter_smooth" => Ok(ChartTypeSpec::ScatterSmooth), + "bubble" => Ok(ChartTypeSpec::Bubble), + "radar" => Ok(ChartTypeSpec::Radar), + "radar_filled" => Ok(ChartTypeSpec::RadarFilled), + "stock_hlc" => Ok(ChartTypeSpec::StockHlc), + "stock_ohlc" => Ok(ChartTypeSpec::StockOhlc), + "combo" => Ok(ChartTypeSpec::Combo), + _ => Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("chart_type `{chart_type}` is not supported"), + }), + } +} + +fn parse_stroke( + document: &PresentationDocument, + stroke: Option, + action: &str, +) -> Result, PresentationArtifactError> { + stroke + .map(|value| parse_required_stroke(document, value, action)) + .transpose() +} + +fn parse_required_stroke( + document: &PresentationDocument, + stroke: StrokeArgs, + action: &str, +) -> Result { + Ok(StrokeStyle { + color: normalize_color_with_document(document, &stroke.color, action, "stroke.color")?, + width: stroke.width, + }) +} + +fn parse_connector_kind( + connector_type: &str, + action: &str, +) -> Result { + match connector_type { + "straight" => Ok(ConnectorKind::Straight), + "elbow" => Ok(ConnectorKind::Elbow), + "curved" => Ok(ConnectorKind::Curved), + _ => Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("connector_type `{connector_type}` is not supported"), + }), + } +} + +fn parse_connector_arrow( + value: &str, + action: &str, +) -> Result { + match value { + "none" => Ok(ConnectorArrowKind::None), + "triangle" => Ok(ConnectorArrowKind::Triangle), + "stealth" => Ok(ConnectorArrowKind::Stealth), + "diamond" => Ok(ConnectorArrowKind::Diamond), + "oval" => Ok(ConnectorArrowKind::Oval), + "open" => Ok(ConnectorArrowKind::Open), + _ => Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("connector arrow `{value}` is not supported"), + }), + } +} + +fn parse_connector_arrow_size( + value: &str, + action: &str, +) -> Result { + match value { + "small" => Ok(ConnectorArrowScale::Small), + "medium" => Ok(ConnectorArrowScale::Medium), + "large" => Ok(ConnectorArrowScale::Large), + _ => Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("connector arrow_size `{value}` is not supported"), + }), + } +} + +fn parse_line_style(value: &str, action: &str) -> Result { + match value { + "solid" => Ok(LineStyle::Solid), + "dashed" => Ok(LineStyle::Dashed), + "dotted" => Ok(LineStyle::Dotted), + "dash-dot" | "dash_dot" => Ok(LineStyle::DashDot), + "dash-dot-dot" | "dash_dot_dot" => Ok(LineStyle::DashDotDot), + "long-dash" | "long_dash" => Ok(LineStyle::LongDash), + "long-dash-dot" | "long_dash_dot" => Ok(LineStyle::LongDashDot), + _ => Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("line style `{value}` is not supported"), + }), + } +} + +fn parse_connector_line( + document: &PresentationDocument, + line: Option, + action: &str, +) -> Result { + let line = line.unwrap_or_default(); + Ok(ParsedConnectorLine { + color: line + .color + .as_deref() + .map(|value| normalize_color_with_document(document, value, action, "line.color")) + .transpose()? + .unwrap_or_else(|| "000000".to_string()), + width: line.width.unwrap_or(1), + style: line + .style + .as_deref() + .map(|value| parse_line_style(value, action)) + .transpose()? + .unwrap_or(LineStyle::Solid), + }) +} + +struct ParsedConnectorLine { + color: String, + width: u32, + style: LineStyle, +} + +fn normalize_text_style_with_document( + document: &PresentationDocument, + styling: &TextStylingArgs, + action: &str, +) -> Result { + normalize_text_style_with_palette(Some(&document.theme), styling, action) +} + +fn normalize_text_style_with_palette( + theme: Option<&ThemeState>, + styling: &TextStylingArgs, + action: &str, +) -> Result { + Ok(TextStyle { + font_size: styling.font_size, + font_family: styling.font_family.clone(), + color: styling + .color + .as_deref() + .map(|value| normalize_color_with_palette(theme, value, action, "color")) + .transpose()?, + alignment: styling + .alignment + .as_deref() + .map(|value| parse_alignment(value, action)) + .transpose()?, + bold: styling.bold.unwrap_or(false), + italic: styling.italic.unwrap_or(false), + underline: false, + }) +} + +fn coerce_table_rows( + rows: Vec>, + action: &str, +) -> Result>, PresentationArtifactError> { + if rows.is_empty() { + return Err(PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: "`rows` must contain at least one row".to_string(), + }); + } + Ok(rows + .into_iter() + .map(|row| { + row.into_iter() + .map(|value| TableCellSpec { + text: cell_value_to_string(value), + text_style: TextStyle::default(), + background_fill: None, + alignment: None, + }) + .collect() + }) + .collect()) +} + +fn parse_alignment(value: &str, action: &str) -> Result { + match value { + "left" => Ok(TextAlignment::Left), + "center" | "middle" => Ok(TextAlignment::Center), + "right" => Ok(TextAlignment::Right), + "justify" => Ok(TextAlignment::Justify), + _ => Err(PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("unsupported alignment `{value}`"), + }), + } +} + +fn normalize_theme(args: ThemeArgs, action: &str) -> Result { + let color_scheme = args + .color_scheme + .into_iter() + .map(|(key, value)| { + normalize_color(&value, action, &key) + .map(|normalized| (key.to_ascii_lowercase(), normalized)) + }) + .collect::, _>>()?; + Ok(ThemeState { + color_scheme, + major_font: args.major_font, + minor_font: args.minor_font, + }) +} + +fn parse_slide_size(value: &Value, action: &str) -> Result { + let position: PositionArgs = serde_json::from_value(value.clone()).map_err(|error| { + PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("invalid slide_size: {error}"), + } + })?; + Ok(position.into()) +} + +fn apply_layout_to_slide( + document: &mut PresentationDocument, + slide: &mut PresentationSlide, + layout_id: &str, + action: &str, +) -> Result<(), PresentationArtifactError> { + let placeholders = resolved_layout_placeholders(document, layout_id, action)?; + slide.layout_id = Some(layout_id.to_string()); + for resolved in placeholders { + let placeholder = resolved.definition; + let placeholder_ref = Some(PlaceholderRef { + name: placeholder.name, + placeholder_type: placeholder.placeholder_type, + index: placeholder.index, + }); + let element_id = document.next_element_id(); + if placeholder.geometry == ShapeGeometry::Rectangle { + slide.elements.push(PresentationElement::Text(TextElement { + element_id, + text: placeholder.text.unwrap_or_default(), + frame: placeholder.frame, + fill: None, + style: TextStyle::default(), + placeholder: placeholder_ref, + z_order: slide.elements.len(), + })); + } else { + slide + .elements + .push(PresentationElement::Shape(ShapeElement { + element_id, + geometry: placeholder.geometry, + frame: placeholder.frame, + fill: None, + stroke: None, + text: placeholder.text, + text_style: TextStyle::default(), + placeholder: placeholder_ref, + rotation_degrees: None, + z_order: slide.elements.len(), + })); + } + } + Ok(()) +} + +fn resolved_layout_placeholders( + document: &PresentationDocument, + layout_id: &str, + action: &str, +) -> Result, PresentationArtifactError> { + let mut lineage = Vec::new(); + collect_layout_lineage( + document, + layout_id, + action, + &mut HashSet::new(), + &mut lineage, + )?; + let mut resolved: Vec = Vec::new(); + for layout in lineage { + for placeholder in &layout.placeholders { + if let Some(index) = resolved.iter().position(|entry| { + placeholder_key(&entry.definition) == placeholder_key(placeholder) + }) { + resolved[index] = ResolvedPlaceholder { + source_layout_id: layout.layout_id.clone(), + definition: placeholder.clone(), + }; + } else { + resolved.push(ResolvedPlaceholder { + source_layout_id: layout.layout_id.clone(), + definition: placeholder.clone(), + }); + } + } + } + Ok(resolved) +} + +fn collect_layout_lineage<'a>( + document: &'a PresentationDocument, + layout_id: &str, + action: &str, + seen: &mut HashSet, + lineage: &mut Vec<&'a LayoutDocument>, +) -> Result<(), PresentationArtifactError> { + if !seen.insert(layout_id.to_string()) { + return Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("layout inheritance cycle detected at `{layout_id}`"), + }); + } + let layout = document.get_layout(layout_id, action)?; + if let Some(parent_layout_id) = &layout.parent_layout_id { + collect_layout_lineage(document, parent_layout_id, action, seen, lineage)?; + } + lineage.push(layout); + Ok(()) +} + +fn placeholder_key(placeholder: &PlaceholderDefinition) -> (String, String, Option) { + ( + placeholder.name.to_ascii_lowercase(), + placeholder.placeholder_type.to_ascii_lowercase(), + placeholder.index, + ) +} + +fn layout_placeholder_list( + document: &PresentationDocument, + layout_id: &str, + action: &str, +) -> Result, PresentationArtifactError> { + resolved_layout_placeholders(document, layout_id, action).map(|placeholders| { + placeholders + .into_iter() + .map(|placeholder| PlaceholderListEntry { + scope: "layout".to_string(), + source_layout_id: Some(placeholder.source_layout_id), + slide_index: None, + element_id: None, + name: placeholder.definition.name, + placeholder_type: placeholder.definition.placeholder_type, + index: placeholder.definition.index, + geometry: Some(format!("{:?}", placeholder.definition.geometry)), + text_preview: placeholder.definition.text, + }) + .collect() + }) +} + +fn slide_placeholder_list( + slide: &PresentationSlide, + slide_index: usize, +) -> Vec { + slide + .elements + .iter() + .filter_map(|element| match element { + PresentationElement::Text(text) => { + text.placeholder + .as_ref() + .map(|placeholder| PlaceholderListEntry { + scope: "slide".to_string(), + source_layout_id: slide.layout_id.clone(), + slide_index: Some(slide_index), + element_id: Some(text.element_id.clone()), + name: placeholder.name.clone(), + placeholder_type: placeholder.placeholder_type.clone(), + index: placeholder.index, + geometry: Some("Rectangle".to_string()), + text_preview: Some(text.text.clone()), + }) + } + PresentationElement::Shape(shape) => { + shape + .placeholder + .as_ref() + .map(|placeholder| PlaceholderListEntry { + scope: "slide".to_string(), + source_layout_id: slide.layout_id.clone(), + slide_index: Some(slide_index), + element_id: Some(shape.element_id.clone()), + name: placeholder.name.clone(), + placeholder_type: placeholder.placeholder_type.clone(), + index: placeholder.index, + geometry: Some(format!("{:?}", shape.geometry)), + text_preview: shape.text.clone(), + }) + } + PresentationElement::Connector(_) + | PresentationElement::Image(_) + | PresentationElement::Table(_) + | PresentationElement::Chart(_) => None, + }) + .collect() +} + +fn build_table_cell( + cell: TableCellSpec, + merges: &[TableMergeRegion], + row_index: usize, + column_index: usize, +) -> TableCell { + let mut table_cell = TableCell::new(&cell.text); + if cell.text_style.bold { + table_cell = table_cell.bold(); + } + if cell.text_style.italic { + table_cell = table_cell.italic(); + } + if cell.text_style.underline { + table_cell = table_cell.underline(); + } + if let Some(color) = cell.text_style.color { + table_cell = table_cell.text_color(&color); + } + if let Some(fill) = cell.background_fill { + table_cell = table_cell.background_color(&fill); + } + if let Some(size) = cell.text_style.font_size { + table_cell = table_cell.font_size(size); + } + if let Some(font_family) = cell.text_style.font_family { + table_cell = table_cell.font_family(&font_family); + } + if let Some(alignment) = cell.alignment.or(cell.text_style.alignment) { + table_cell = match alignment { + TextAlignment::Left => table_cell.align_left(), + TextAlignment::Center => table_cell.align_center(), + TextAlignment::Right => table_cell.align_right(), + TextAlignment::Justify => table_cell.align(CellAlign::Justify), + }; + } + for merge in merges { + if row_index == merge.start_row && column_index == merge.start_column { + table_cell = table_cell + .grid_span((merge.end_column - merge.start_column + 1) as u32) + .row_span((merge.end_row - merge.start_row + 1) as u32); + } else if row_index >= merge.start_row + && row_index <= merge.end_row + && column_index >= merge.start_column + && column_index <= merge.end_column + { + if row_index == merge.start_row { + table_cell = table_cell.h_merge(); + } else { + table_cell = table_cell.v_merge(); + } + } + } + table_cell +} + +fn inspect_document( + document: &PresentationDocument, + kind: Option<&str>, + target_id: Option<&str>, + max_chars: Option, +) -> String { + let kinds = + kind.unwrap_or("deck,slide,textbox,shape,connector,table,chart,image,notes,layoutList"); + let include = |name: &str| kinds.split(',').map(str::trim).any(|entry| entry == name); + let mut lines = Vec::new(); + if include("deck") { + let record = serde_json::json!({ + "kind": "deck", + "id": format!("pr/{}", document.artifact_id), + "name": document.name, + "slides": document.slides.len(), + }); + if target_matches(target_id, &record) { + lines.push(record); + } + } + if include("layoutList") { + for layout in &document.layouts { + let placeholders = resolved_layout_placeholders(document, &layout.layout_id, "inspect") + .unwrap_or_default() + .into_iter() + .map(|placeholder| { + serde_json::json!({ + "name": placeholder.definition.name, + "type": placeholder.definition.placeholder_type, + "sourceLayoutId": placeholder.source_layout_id, + "textPreview": placeholder.definition.text, + }) + }) + .collect::>(); + let record = serde_json::json!({ + "kind": "layout", + "id": format!("ly/{}", layout.layout_id), + "layoutId": layout.layout_id, + "name": layout.name, + "type": match layout.kind { LayoutKind::Layout => "layout", LayoutKind::Master => "master" }, + "parentLayoutId": layout.parent_layout_id, + "placeholders": placeholders, + }); + if target_matches(target_id, &record) { + lines.push(record); + } + } + } + for (index, slide) in document.slides.iter().enumerate() { + let slide_id = format!("sl/{}", slide.slide_id); + if include("slide") { + let record = serde_json::json!({ + "kind": "slide", + "id": slide_id, + "slide": index + 1, + "layoutId": slide.layout_id, + "elements": slide.elements.len(), + }); + if target_matches(target_id, &record) { + lines.push(record); + } + } + if include("notes") && !slide.notes.text.is_empty() { + let record = serde_json::json!({ + "kind": "notes", + "id": format!("nt/{}", slide.slide_id), + "slide": index + 1, + "visible": slide.notes.visible, + "text": slide.notes.text, + }); + if target_matches(target_id, &record) || target_id == Some(slide_id.as_str()) { + lines.push(record); + } + } + for element in &slide.elements { + let mut record = match element { + PresentationElement::Text(text) => { + if !include("textbox") { + continue; + } + serde_json::json!({ + "kind": "textbox", + "id": format!("sh/{}", text.element_id), + "slide": index + 1, + "text": text.text, + "textPreview": text.text.replace('\n', " | "), + "textChars": text.text.chars().count(), + "textLines": text.text.lines().count(), + "bbox": [text.frame.left, text.frame.top, text.frame.width, text.frame.height], + "bboxUnit": "points", + }) + } + PresentationElement::Shape(shape) => { + if !(include("shape") || include("textbox") && shape.text.is_some()) { + continue; + } + let kind = if shape.text.is_some() && include("textbox") { + "textbox" + } else { + "shape" + }; + serde_json::json!({ + "kind": kind, + "id": format!("sh/{}", shape.element_id), + "slide": index + 1, + "geometry": format!("{:?}", shape.geometry), + "text": shape.text, + "bbox": [shape.frame.left, shape.frame.top, shape.frame.width, shape.frame.height], + "bboxUnit": "points", + }) + } + PresentationElement::Connector(connector) => { + if !include("shape") && !include("connector") { + continue; + } + serde_json::json!({ + "kind": "connector", + "id": format!("cn/{}", connector.element_id), + "slide": index + 1, + "connectorType": format!("{:?}", connector.connector_type), + "start": [connector.start.left, connector.start.top], + "end": [connector.end.left, connector.end.top], + "lineStyle": format!("{:?}", connector.line_style), + "label": connector.label, + }) + } + PresentationElement::Table(table) => { + if !include("table") { + continue; + } + serde_json::json!({ + "kind": "table", + "id": format!("tb/{}", table.element_id), + "slide": index + 1, + "rows": table.rows.len(), + "cols": table.rows.iter().map(std::vec::Vec::len).max().unwrap_or(0), + "preview": table.rows.first().map(|row| row.iter().map(|cell| cell.text.clone()).collect::>().join(" | ")), + "style": table.style, + "bbox": [table.frame.left, table.frame.top, table.frame.width, table.frame.height], + "bboxUnit": "points", + }) + } + PresentationElement::Chart(chart) => { + if !include("chart") { + continue; + } + serde_json::json!({ + "kind": "chart", + "id": format!("ch/{}", chart.element_id), + "slide": index + 1, + "chartType": format!("{:?}", chart.chart_type), + "title": chart.title, + "bbox": [chart.frame.left, chart.frame.top, chart.frame.width, chart.frame.height], + "bboxUnit": "points", + }) + } + PresentationElement::Image(image) => { + if !include("image") { + continue; + } + serde_json::json!({ + "kind": "image", + "id": format!("im/{}", image.element_id), + "slide": index + 1, + "alt": image.alt_text, + "prompt": image.prompt, + "fit": format!("{:?}", image.fit_mode), + "crop": image.crop.map(|(left, top, right, bottom)| serde_json::json!({ + "left": left, + "top": top, + "right": right, + "bottom": bottom, + })), + "isPlaceholder": image.is_placeholder, + "lockAspectRatio": image.lock_aspect_ratio, + "bbox": [image.frame.left, image.frame.top, image.frame.width, image.frame.height], + "bboxUnit": "points", + }) + } + }; + if !target_matches(target_id, &record) && target_id != Some(slide_id.as_str()) { + continue; + } + if let Some(placeholder) = match element { + PresentationElement::Text(text) => text.placeholder.as_ref(), + PresentationElement::Shape(shape) => shape.placeholder.as_ref(), + PresentationElement::Connector(_) + | PresentationElement::Image(_) + | PresentationElement::Table(_) + | PresentationElement::Chart(_) => None, + } { + record["placeholder"] = + serde_json::Value::String(placeholder.placeholder_type.clone()); + record["placeholderName"] = serde_json::Value::String(placeholder.name.clone()); + record["placeholderIndex"] = placeholder + .index + .map(serde_json::Value::from) + .unwrap_or(serde_json::Value::Null); + } + lines.push(record); + } + } + let mut ndjson = lines + .into_iter() + .map(|line| line.to_string()) + .collect::>() + .join("\n"); + if let Some(max_chars) = max_chars + && ndjson.len() > max_chars + { + let omitted_lines = ndjson[max_chars..].lines().count(); + ndjson.truncate(max_chars); + ndjson.push('\n'); + ndjson.push_str( + &serde_json::json!({ + "kind": "notice", + "message": format!( + "Truncated: omitted {omitted_lines} lines. Increase maxChars or narrow target." + ), + }) + .to_string(), + ); + } + ndjson +} + +fn target_matches(target_id: Option<&str>, record: &Value) -> bool { + match target_id { + None => true, + Some(target_id) => record.get("id").and_then(Value::as_str) == Some(target_id), + } +} + +fn normalize_element_lookup_id(element_id: &str) -> &str { + element_id + .split_once('/') + .map(|(_, normalized)| normalized) + .unwrap_or(element_id) +} + +fn resolve_anchor( + document: &PresentationDocument, + id: &str, + action: &str, +) -> Result { + if id == format!("pr/{}", document.artifact_id) { + return Ok(serde_json::json!({ + "kind": "deck", + "id": id, + "artifactId": document.artifact_id, + "name": document.name, + "slideCount": document.slides.len(), + })); + } + + for (slide_index, slide) in document.slides.iter().enumerate() { + let slide_id = format!("sl/{}", slide.slide_id); + if id == slide_id { + return Ok(serde_json::json!({ + "kind": "slide", + "id": slide_id, + "slide": slide_index + 1, + "slideIndex": slide_index, + "layoutId": slide.layout_id, + "notesId": (!slide.notes.text.is_empty()).then(|| format!("nt/{}", slide.slide_id)), + "elementIds": slide.elements.iter().map(|element| { + let prefix = match element { + PresentationElement::Text(_) | PresentationElement::Shape(_) => "sh", + PresentationElement::Connector(_) => "cn", + PresentationElement::Image(_) => "im", + PresentationElement::Table(_) => "tb", + PresentationElement::Chart(_) => "ch", + }; + format!("{prefix}/{}", element.element_id()) + }).collect::>(), + })); + } + let notes_id = format!("nt/{}", slide.slide_id); + if id == notes_id { + return Ok(serde_json::json!({ + "kind": "notes", + "id": notes_id, + "slide": slide_index + 1, + "slideIndex": slide_index, + "visible": slide.notes.visible, + "text": slide.notes.text, + })); + } + for element in &slide.elements { + let record = match element { + PresentationElement::Text(text) => serde_json::json!({ + "kind": "textbox", + "id": format!("sh/{}", text.element_id), + "elementId": text.element_id, + "slide": slide_index + 1, + "slideIndex": slide_index, + "text": text.text, + "bbox": [text.frame.left, text.frame.top, text.frame.width, text.frame.height], + "bboxUnit": "points", + }), + PresentationElement::Shape(shape) => serde_json::json!({ + "kind": if shape.text.is_some() { "textbox" } else { "shape" }, + "id": format!("sh/{}", shape.element_id), + "elementId": shape.element_id, + "slide": slide_index + 1, + "slideIndex": slide_index, + "geometry": format!("{:?}", shape.geometry), + "text": shape.text, + "bbox": [shape.frame.left, shape.frame.top, shape.frame.width, shape.frame.height], + "bboxUnit": "points", + }), + PresentationElement::Connector(connector) => serde_json::json!({ + "kind": "connector", + "id": format!("cn/{}", connector.element_id), + "elementId": connector.element_id, + "slide": slide_index + 1, + "slideIndex": slide_index, + "connectorType": format!("{:?}", connector.connector_type), + "start": [connector.start.left, connector.start.top], + "end": [connector.end.left, connector.end.top], + "lineStyle": format!("{:?}", connector.line_style), + "label": connector.label, + }), + PresentationElement::Image(image) => serde_json::json!({ + "kind": "image", + "id": format!("im/{}", image.element_id), + "elementId": image.element_id, + "slide": slide_index + 1, + "slideIndex": slide_index, + "alt": image.alt_text, + "prompt": image.prompt, + "fit": format!("{:?}", image.fit_mode), + "crop": image.crop.map(|(left, top, right, bottom)| serde_json::json!({ + "left": left, + "top": top, + "right": right, + "bottom": bottom, + })), + "isPlaceholder": image.is_placeholder, + "lockAspectRatio": image.lock_aspect_ratio, + "bbox": [image.frame.left, image.frame.top, image.frame.width, image.frame.height], + "bboxUnit": "points", + }), + PresentationElement::Table(table) => serde_json::json!({ + "kind": "table", + "id": format!("tb/{}", table.element_id), + "elementId": table.element_id, + "slide": slide_index + 1, + "slideIndex": slide_index, + "rows": table.rows.len(), + "cols": table.rows.iter().map(std::vec::Vec::len).max().unwrap_or(0), + "bbox": [table.frame.left, table.frame.top, table.frame.width, table.frame.height], + "bboxUnit": "points", + }), + PresentationElement::Chart(chart) => serde_json::json!({ + "kind": "chart", + "id": format!("ch/{}", chart.element_id), + "elementId": chart.element_id, + "slide": slide_index + 1, + "slideIndex": slide_index, + "chartType": format!("{:?}", chart.chart_type), + "title": chart.title, + "bbox": [chart.frame.left, chart.frame.top, chart.frame.width, chart.frame.height], + "bboxUnit": "points", + }), + }; + if record.get("id").and_then(Value::as_str) == Some(id) { + return Ok(record); + } + } + } + + for layout in &document.layouts { + let layout_id = format!("ly/{}", layout.layout_id); + if id == layout_id { + return Ok(serde_json::json!({ + "kind": "layout", + "id": layout_id, + "layoutId": layout.layout_id, + "name": layout.name, + "type": match layout.kind { + LayoutKind::Layout => "layout", + LayoutKind::Master => "master", + }, + "parentLayoutId": layout.parent_layout_id, + "placeholders": layout_placeholder_list(document, &layout.layout_id, action)?, + })); + } + } + + Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: format!("unknown resolve id `{id}`"), + }) +} + +fn write_preview_images( + document: &PresentationDocument, + output_dir: &Path, + action: &str, +) -> Result<(), PresentationArtifactError> { + let pptx_path = output_dir.join("preview.pptx"); + document.to_ppt_rs().save(&pptx_path).map_err(|error| { + PresentationArtifactError::ExportFailed { + path: pptx_path.clone(), + message: error.to_string(), + } + })?; + document + .to_ppt_rs() + .save_as_png(output_dir) + .map_err(|error| PresentationArtifactError::ExportFailed { + path: output_dir.to_path_buf(), + message: format!("{action}: {error}"), + }) +} + +pub(crate) fn write_preview_image( + source_path: &Path, + target_path: &Path, + format: PreviewOutputFormat, + scale: f32, + quality: u8, + action: &str, +) -> Result<(), PresentationArtifactError> { + if matches!(format, PreviewOutputFormat::Png) && scale == 1.0 { + std::fs::rename(source_path, target_path).map_err(|error| { + PresentationArtifactError::ExportFailed { + path: target_path.to_path_buf(), + message: error.to_string(), + } + })?; + return Ok(()); + } + let mut preview = + image::open(source_path).map_err(|error| PresentationArtifactError::ExportFailed { + path: source_path.to_path_buf(), + message: format!("{action}: {error}"), + })?; + if scale != 1.0 { + let width = (preview.width() as f32 * scale).round().max(1.0) as u32; + let height = (preview.height() as f32 * scale).round().max(1.0) as u32; + preview = preview.resize_exact(width, height, FilterType::Lanczos3); + } + let file = std::fs::File::create(target_path).map_err(|error| { + PresentationArtifactError::ExportFailed { + path: target_path.to_path_buf(), + message: error.to_string(), + } + })?; + let mut writer = std::io::BufWriter::new(file); + match format { + PreviewOutputFormat::Png => { + preview + .write_to(&mut writer, ImageFormat::Png) + .map_err(|error| PresentationArtifactError::ExportFailed { + path: target_path.to_path_buf(), + message: format!("{action}: {error}"), + })? + } + PreviewOutputFormat::Jpeg => { + let rgb = preview.to_rgb8(); + let mut encoder = JpegEncoder::new_with_quality(&mut writer, quality); + encoder.encode_image(&rgb).map_err(|error| { + PresentationArtifactError::ExportFailed { + path: target_path.to_path_buf(), + message: format!("{action}: {error}"), + } + })?; + } + } + std::fs::remove_file(source_path).ok(); + Ok(()) +} + +fn collect_pngs(output_dir: &Path) -> Result, PresentationArtifactError> { + let mut files = std::fs::read_dir(output_dir) + .map_err(|error| PresentationArtifactError::ExportFailed { + path: output_dir.to_path_buf(), + message: error.to_string(), + })? + .filter_map(Result::ok) + .map(|entry| entry.path()) + .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("png")) + .collect::>(); + files.sort(); + Ok(files) +} + +fn parse_preview_output_format( + format: Option<&str>, + path: &Path, + action: &str, +) -> Result { + let value = format + .map(str::to_owned) + .or_else(|| { + path.extension() + .and_then(|extension| extension.to_str()) + .map(str::to_owned) + }) + .unwrap_or_else(|| "png".to_string()); + match value.to_ascii_lowercase().as_str() { + "png" => Ok(PreviewOutputFormat::Png), + "jpg" | "jpeg" => Ok(PreviewOutputFormat::Jpeg), + "svg" => Err(PresentationArtifactError::UnsupportedFeature { + action: action.to_string(), + message: "preview format `svg` is not supported".to_string(), + }), + other => Err(PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("preview format `{other}` is not supported"), + }), + } +} + +fn normalize_preview_scale( + scale: Option, + action: &str, +) -> Result { + let scale = scale.unwrap_or(1.0); + if !scale.is_finite() || scale <= 0.0 { + return Err(PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: "`scale` must be a positive number".to_string(), + }); + } + Ok(scale) +} + +fn normalize_preview_quality( + quality: Option, + action: &str, +) -> Result { + let quality = quality.unwrap_or(90); + if quality == 0 || quality > 100 { + return Err(PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: "`quality` must be between 1 and 100".to_string(), + }); + } + Ok(quality) +} + +fn cell_value_to_string(value: Value) -> String { + match value { + Value::Null => String::new(), + Value::String(text) => text, + Value::Bool(boolean) => boolean.to_string(), + Value::Number(number) => number.to_string(), + other => other.to_string(), + } +} + +fn snapshot_for_document(document: &PresentationDocument) -> ArtifactSnapshot { + ArtifactSnapshot { + slide_count: document.slides.len(), + slides: document + .slides + .iter() + .enumerate() + .map(|(index, slide)| SlideSnapshot { + slide_id: slide.slide_id.clone(), + index, + element_ids: slide + .elements + .iter() + .map(|element| element.element_id().to_string()) + .collect(), + element_types: slide + .elements + .iter() + .map(|element| element.kind().to_string()) + .collect(), + }) + .collect(), + } +} + +fn slide_list(document: &PresentationDocument) -> Vec { + document + .slides + .iter() + .enumerate() + .map(|(index, slide)| SlideListEntry { + slide_id: slide.slide_id.clone(), + index, + notes: (!slide.notes.text.is_empty()).then(|| slide.notes.text.clone()), + notes_visible: slide.notes.visible, + background_fill: slide.background_fill.clone(), + layout_id: slide.layout_id.clone(), + element_count: slide.elements.len(), + }) + .collect() +} + +fn layout_list(document: &PresentationDocument) -> Vec { + document + .layouts + .iter() + .map(|layout| LayoutListEntry { + layout_id: layout.layout_id.clone(), + name: layout.name.clone(), + kind: match layout.kind { + LayoutKind::Layout => "layout".to_string(), + LayoutKind::Master => "master".to_string(), + }, + parent_layout_id: layout.parent_layout_id.clone(), + placeholder_count: layout.placeholders.len(), + }) + .collect() +} + +fn points_to_emu(points: u32) -> u32 { + points.saturating_mul(POINT_TO_EMU) +} + +fn emu_to_points(emu: u32) -> u32 { + emu / POINT_TO_EMU +} + +type ImageCrop = (f64, f64, f64, f64); +type FittedImage = (u32, u32, u32, u32, Option); + +pub(crate) fn fit_image(image: &ImageElement) -> FittedImage { + let Some(payload) = image.payload.as_ref() else { + return ( + image.frame.left, + image.frame.top, + image.frame.width, + image.frame.height, + None, + ); + }; + let frame = image.frame; + let source_width = payload.width_px as f64; + let source_height = payload.height_px as f64; + let target_width = frame.width as f64; + let target_height = frame.height as f64; + let source_ratio = source_width / source_height; + let target_ratio = target_width / target_height; + + match image.fit_mode { + ImageFitMode::Stretch => (frame.left, frame.top, frame.width, frame.height, None), + ImageFitMode::Contain => { + let scale = if source_ratio > target_ratio { + target_width / source_width + } else { + target_height / source_height + }; + let width = (source_width * scale).round() as u32; + let height = (source_height * scale).round() as u32; + let left = frame.left + frame.width.saturating_sub(width) / 2; + let top = frame.top + frame.height.saturating_sub(height) / 2; + (left, top, width, height, None) + } + ImageFitMode::Cover => { + let scale = if source_ratio > target_ratio { + target_height / source_height + } else { + target_width / source_width + }; + let width = source_width * scale; + let height = source_height * scale; + let crop_x = ((width - target_width).max(0.0) / width) / 2.0; + let crop_y = ((height - target_height).max(0.0) / height) / 2.0; + ( + frame.left, + frame.top, + frame.width, + frame.height, + Some((crop_x, crop_y, crop_x, crop_y)), + ) + } + } +} + +fn normalize_image_crop( + crop: ImageCropArgs, + action: &str, +) -> Result { + for (name, value) in [ + ("left", crop.left), + ("top", crop.top), + ("right", crop.right), + ("bottom", crop.bottom), + ] { + if !(0.0..=1.0).contains(&value) { + return Err(PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("image crop `{name}` must be between 0.0 and 1.0"), + }); + } + } + Ok((crop.left, crop.top, crop.right, crop.bottom)) +} + +fn load_image_payload_from_path( + path: &Path, + action: &str, +) -> Result { + let bytes = std::fs::read(path).map_err(|error| PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("failed to read image `{}`: {error}", path.display()), + })?; + build_image_payload( + bytes, + path.file_name() + .and_then(|name| name.to_str()) + .unwrap_or("image") + .to_string(), + action, + ) +} + +fn load_image_payload_from_data_url( + data_url: &str, + action: &str, +) -> Result { + let (header, payload) = + data_url + .split_once(',') + .ok_or_else(|| PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: "data_url must include a MIME header and base64 payload".to_string(), + })?; + let mime = header + .strip_prefix("data:") + .and_then(|prefix| prefix.strip_suffix(";base64")) + .ok_or_else(|| PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: "data_url must be base64-encoded".to_string(), + })?; + let bytes = BASE64_STANDARD.decode(payload).map_err(|error| { + PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("failed to decode image data_url: {error}"), + } + })?; + build_image_payload( + bytes, + format!("image.{}", image_extension_from_mime(mime)), + action, + ) +} + +fn load_image_payload_from_uri( + uri: &str, + action: &str, +) -> Result { + let response = + reqwest::blocking::get(uri).map_err(|error| PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("failed to fetch image `{uri}`: {error}"), + })?; + let status = response.status(); + if !status.is_success() { + return Err(PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("failed to fetch image `{uri}`: HTTP {status}"), + }); + } + let content_type = response + .headers() + .get(reqwest::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .map(|value| value.split(';').next().unwrap_or(value).trim().to_string()); + let bytes = response + .bytes() + .map_err(|error| PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("failed to read image `{uri}`: {error}"), + })?; + build_image_payload( + bytes.to_vec(), + infer_remote_image_filename(uri, content_type.as_deref()), + action, + ) +} + +fn infer_remote_image_filename(uri: &str, content_type: Option<&str>) -> String { + let path_name = reqwest::Url::parse(uri) + .ok() + .and_then(|url| { + url.path_segments() + .and_then(Iterator::last) + .map(str::to_owned) + }) + .filter(|segment| !segment.is_empty()); + match (path_name, content_type) { + (Some(path_name), _) if Path::new(&path_name).extension().is_some() => path_name, + (Some(path_name), Some(content_type)) => { + format!("{path_name}.{}", image_extension_from_mime(content_type)) + } + (Some(path_name), None) => path_name, + (None, Some(content_type)) => format!("image.{}", image_extension_from_mime(content_type)), + (None, None) => "image.png".to_string(), + } +} + +fn build_image_payload( + bytes: Vec, + filename: String, + action: &str, +) -> Result { + let image = image::load_from_memory(&bytes).map_err(|error| { + PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("failed to decode image bytes: {error}"), + } + })?; + let (width_px, height_px) = image.dimensions(); + let format = Path::new(&filename) + .extension() + .and_then(|extension| extension.to_str()) + .unwrap_or("png") + .to_uppercase(); + Ok(ImagePayload { + bytes, + format, + width_px, + height_px, + }) +} + +fn image_extension_from_mime(mime: &str) -> &'static str { + match mime { + "image/jpeg" => "jpg", + "image/gif" => "gif", + "image/webp" => "webp", + _ => "png", + } +} + +fn index_out_of_range(action: &str, index: usize, len: usize) -> PresentationArtifactError { + PresentationArtifactError::InvalidArgs { + action: action.to_string(), + message: format!("slide index {index} is out of range for {len} slides"), + } +} + +fn to_index(value: u32) -> Result { + usize::try_from(value).map_err(|_| PresentationArtifactError::InvalidArgs { + action: "insert_slide".to_string(), + message: "index does not fit in usize".to_string(), + }) +} + +fn resequence_z_order(slide: &mut PresentationSlide) { + for (index, element) in slide.elements.iter_mut().enumerate() { + element.set_z_order(index); + } +} diff --git a/codex-rs/artifact-presentation/src/tests.rs b/codex-rs/artifact-presentation/src/tests.rs new file mode 100644 index 000000000..e39275c11 --- /dev/null +++ b/codex-rs/artifact-presentation/src/tests.rs @@ -0,0 +1,952 @@ +use super::presentation_artifact::*; +use pretty_assertions::assert_eq; + +#[test] +fn manager_can_create_add_text_and_export() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let create_response = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Demo" }), + }, + temp_dir.path(), + )?; + let artifact_id = create_response.artifact_id; + + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + + let add_text = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_text_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "text": "hello", + "position": { "left": 40, "top": 40, "width": 200, "height": 80 } + }), + }, + temp_dir.path(), + )?; + assert_eq!( + add_text + .artifact_snapshot + .as_ref() + .map(|snapshot| snapshot.slide_count), + Some(1) + ); + + let export_path = temp_dir.path().join("deck.pptx"); + let export = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "export_pptx".to_string(), + args: serde_json::json!({ "path": export_path }), + }, + temp_dir.path(), + )?; + assert_eq!(export.exported_paths.len(), 1); + assert!(export.exported_paths[0].exists()); + Ok(()) +} + +#[test] +fn manager_can_import_exported_presentation() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Roundtrip" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id.clone(); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "add_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "geometry": "rectangle", + "position": { "left": 24, "top": 24, "width": 180, "height": 120 }, + "text": "shape" + }), + }, + temp_dir.path(), + )?; + let export_path = temp_dir.path().join("roundtrip.pptx"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id), + action: "export_pptx".to_string(), + args: serde_json::json!({ "path": export_path }), + }, + temp_dir.path(), + )?; + + let imported = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "import_pptx".to_string(), + args: serde_json::json!({ "path": "roundtrip.pptx" }), + }, + temp_dir.path(), + )?; + assert_eq!( + imported + .artifact_snapshot + .as_ref() + .map(|snapshot| snapshot.slide_count), + Some(1) + ); + Ok(()) +} + +#[test] +fn image_fit_contain_preserves_aspect_ratio() { + let image = ImageElement { + element_id: "element_1".to_string(), + frame: Rect { + left: 10, + top: 10, + width: 200, + height: 200, + }, + payload: Some(ImagePayload { + bytes: Vec::new(), + format: "PNG".to_string(), + width_px: 400, + height_px: 200, + }), + fit_mode: ImageFitMode::Contain, + crop: None, + lock_aspect_ratio: true, + alt_text: None, + prompt: None, + is_placeholder: false, + z_order: 0, + }; + + let (left, top, width, height, crop) = fit_image(&image); + assert_eq!((left, top, width, height), (10, 60, 200, 100)); + assert_eq!(crop, None); +} + +#[test] +fn preview_image_writer_supports_jpeg_and_scale() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let source_path = temp_dir.path().join("preview.png"); + image::RgbaImage::from_pixel(80, 40, image::Rgba([0x22, 0x66, 0xAA, 0xFF])) + .save(&source_path)?; + let target_path = temp_dir.path().join("preview.jpg"); + write_preview_image( + &source_path, + &target_path, + PreviewOutputFormat::Jpeg, + 0.5, + 82, + "test", + )?; + let rendered = image::open(&target_path)?; + assert_eq!((rendered.width(), rendered.height()), (40, 20)); + assert_eq!( + image::ImageFormat::from_path(&target_path)?, + image::ImageFormat::Jpeg + ); + assert!(!source_path.exists()); + Ok(()) +} + +#[test] +fn image_uris_can_add_and_replace_images() -> Result<(), Box> { + let mut image_bytes = std::io::Cursor::new(Vec::new()); + image::DynamicImage::ImageRgba8(image::RgbaImage::from_pixel( + 16, + 8, + image::Rgba([0x11, 0x88, 0xCC, 0xFF]), + )) + .write_to(&mut image_bytes, image::ImageFormat::Png)?; + let png = image_bytes.into_inner(); + + let server = tiny_http::Server::http("127.0.0.1:0").expect("server"); + let port = server.server_addr().to_ip().expect("ip addr").port(); + let server_thread = std::thread::spawn(move || { + for request in server.incoming_requests().take(2) { + let response = tiny_http::Response::from_data(png.clone()).with_header( + tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"image/png"[..]) + .expect("header"), + ); + request.respond(response).expect("respond"); + } + }); + + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Remote Images" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let remote_uri = format!("http://127.0.0.1:{port}/image.png"); + let added = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_image".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "uri": remote_uri, + "position": { "left": 32, "top": 48, "width": 120, "height": 60 } + }), + }, + temp_dir.path(), + )?; + let element_id = added + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.last()) + .cloned() + .expect("image id"); + assert_eq!( + added + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .map(|slide| slide.element_types.clone()), + Some(vec!["image".to_string()]) + ); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "replace_image".to_string(), + args: serde_json::json!({ + "element_id": format!("im/{element_id}"), + "uri": format!("http://127.0.0.1:{port}/updated.png"), + "fit": "contain" + }), + }, + temp_dir.path(), + )?; + let inspect = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "inspect".to_string(), + args: serde_json::json!({ "kind": "image" }), + }, + temp_dir.path(), + )?; + assert!( + inspect + .inspect_ndjson + .expect("image inspect") + .contains("\"fit\":\"Contain\"") + ); + server_thread.join().expect("server thread"); + Ok(()) +} + +#[test] +fn manager_supports_layout_theme_notes_and_inspect() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ + "name": "Deck", + "theme": { + "color_scheme": { + "accent1": "#123456", + "bg1": "#FFFFFF", + "tx1": "#111111" + }, + "major_font": "Aptos" + } + }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id.clone(); + + let master_layouts = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "create_layout".to_string(), + args: serde_json::json!({ "name": "Brand Master", "kind": "master" }), + }, + temp_dir.path(), + )?; + assert_eq!(master_layouts.layout_list.as_ref().map(Vec::len), Some(1)); + let master_id = master_layouts.layout_list.unwrap()[0].layout_id.clone(); + + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_layout_placeholder".to_string(), + args: serde_json::json!({ + "layout_id": master_id, + "name": "title", + "placeholder_type": "title", + "text": "Placeholder title", + "position": { "left": 48, "top": 48, "width": 500, "height": 60 } + }), + }, + temp_dir.path(), + )?; + + let child_layouts = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "create_layout".to_string(), + args: serde_json::json!({ + "name": "Title Slide", + "kind": "layout", + "parent_layout_id": master_id + }), + }, + temp_dir.path(), + )?; + assert_eq!(child_layouts.layout_list.as_ref().map(Vec::len), Some(2)); + let layout_id = child_layouts + .layout_list + .as_ref() + .and_then(|layouts| layouts.iter().find(|layout| layout.kind == "layout")) + .map(|layout| layout.layout_id.clone()) + .expect("child layout id"); + + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_layout_placeholder".to_string(), + args: serde_json::json!({ + "layout_id": layout_id, + "name": "subtitle", + "placeholder_type": "subtitle", + "text": "Placeholder subtitle", + "position": { "left": 48, "top": 128, "width": 500, "height": 48 } + }), + }, + temp_dir.path(), + )?; + + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({ "layout": layout_id }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "set_notes".to_string(), + args: serde_json::json!({ "slide_index": 0, "text": "Speaker notes" }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "append_notes".to_string(), + args: serde_json::json!({ "slide_index": 0, "text": "More context" }), + }, + temp_dir.path(), + )?; + let layout_placeholders = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "list_layout_placeholders".to_string(), + args: serde_json::json!({ "layout_id": layout_id }), + }, + temp_dir.path(), + )?; + assert_eq!( + layout_placeholders.placeholder_list.as_ref().map(Vec::len), + Some(2) + ); + + let slide_placeholders = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "list_slide_placeholders".to_string(), + args: serde_json::json!({ "slide_index": 0 }), + }, + temp_dir.path(), + )?; + assert_eq!( + slide_placeholders.placeholder_list.as_ref().map(Vec::len), + Some(2) + ); + + let resolved_layout = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "resolve".to_string(), + args: serde_json::json!({ "id": format!("ly/{layout_id}") }), + }, + temp_dir.path(), + )?; + assert_eq!( + resolved_layout + .resolved_record + .as_ref() + .and_then(|record| record.get("kind")) + .and_then(serde_json::Value::as_str), + Some("layout") + ); + + let inspect = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "inspect".to_string(), + args: serde_json::json!({ "kind": "deck,slide,textbox,notes,layoutList" }), + }, + temp_dir.path(), + )?; + let inspect_ndjson = inspect.inspect_ndjson.expect("inspect output"); + assert!(inspect_ndjson.contains("\"kind\":\"layout\"")); + assert!(inspect_ndjson.contains("\"kind\":\"notes\"")); + assert!(inspect_ndjson.contains("\"placeholder\":\"title\"")); + + let truncated = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(created.artifact_id), + action: "inspect".to_string(), + args: serde_json::json!({ + "kind": "deck,slide,textbox,notes,layoutList", + "max_chars": 250 + }), + }, + temp_dir.path(), + )?; + assert!( + truncated + .inspect_ndjson + .expect("truncated inspect") + .contains("\"kind\":\"notice\"") + ); + Ok(()) +} + +#[test] +fn notes_visibility_controls_exported_notes() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Notes" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({ "notes": "Hidden notes" }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "set_notes_visibility".to_string(), + args: serde_json::json!({ "slide_index": 0, "visible": false }), + }, + temp_dir.path(), + )?; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "export_pptx".to_string(), + args: serde_json::json!({ "path": "notes-hidden.pptx" }), + }, + temp_dir.path(), + )?; + + let imported = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "import_pptx".to_string(), + args: serde_json::json!({ "path": "notes-hidden.pptx" }), + }, + temp_dir.path(), + )?; + let summary = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(imported.artifact_id), + action: "list_slides".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + assert_eq!( + summary + .slide_list + .as_ref() + .and_then(|slides| slides.first()) + .and_then(|slide| slide.notes.clone()), + None + ); + Ok(()) +} + +#[test] +fn image_placeholders_and_anchor_updates_work() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Images" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let placeholder = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_image".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "position": { "left": 24, "top": 24, "width": 200, "height": 120 }, + "fit": "contain", + "prompt": "Generate a hero illustration", + "alt": "Hero placeholder" + }), + }, + temp_dir.path(), + )?; + let image_anchor = placeholder + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .map(|id| format!("im/{id}")) + .expect("image anchor"); + + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": image_anchor, + "fit": "cover", + "crop": { "left": 0.1, "top": 0.0, "right": 0.1, "bottom": 0.0 }, + "lock_aspect_ratio": true + }), + }, + temp_dir.path(), + )?; + + let resolved = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "resolve".to_string(), + args: serde_json::json!({ "id": image_anchor }), + }, + temp_dir.path(), + )?; + let record = resolved.resolved_record.expect("resolved image"); + assert_eq!( + record.get("kind").and_then(serde_json::Value::as_str), + Some("image") + ); + assert_eq!( + record + .get("isPlaceholder") + .and_then(serde_json::Value::as_bool), + Some(true) + ); + assert_eq!( + record + .get("lockAspectRatio") + .and_then(serde_json::Value::as_bool), + Some(true) + ); + Ok(()) +} + +#[test] +fn image_partial_resize_respects_lock_aspect_ratio() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Image Resize" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let added = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_image".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "position": { "left": 10, "top": 10, "width": 200, "height": 100 }, + "prompt": "Placeholder image", + "lock_aspect_ratio": true + }), + }, + temp_dir.path(), + )?; + let image_anchor = added + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .map(|id| format!("im/{id}")) + .expect("image anchor"); + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_shape_style".to_string(), + args: serde_json::json!({ + "element_id": image_anchor, + "position": { "width": 120 } + }), + }, + temp_dir.path(), + )?; + let resolved = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "resolve".to_string(), + args: serde_json::json!({ "id": image_anchor }), + }, + temp_dir.path(), + )?; + let bbox = resolved + .resolved_record + .as_ref() + .and_then(|record| record.get("bbox")) + .and_then(serde_json::Value::as_array) + .expect("bbox"); + assert_eq!(bbox[2].as_u64(), Some(120)); + assert_eq!(bbox[3].as_u64(), Some(60)); + Ok(()) +} + +#[test] +fn connectors_support_arrows_and_inspect() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Connectors" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let added = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_connector".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "connector_type": "elbow", + "start": { "left": 20, "top": 20 }, + "end": { "left": 180, "top": 160 }, + "line": { "color": "#ff0000", "width": 2, "style": "dash-dot" }, + "start_arrow": "none", + "end_arrow": "triangle", + "arrow_size": "large", + "label": "flow" + }), + }, + temp_dir.path(), + )?; + let connector_id = added + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .map(|id| format!("cn/{id}")) + .expect("connector id"); + let resolved = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "resolve".to_string(), + args: serde_json::json!({ "id": connector_id }), + }, + temp_dir.path(), + )?; + assert_eq!( + resolved + .resolved_record + .as_ref() + .and_then(|record| record.get("kind")) + .and_then(serde_json::Value::as_str), + Some("connector") + ); + let inspect = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "inspect".to_string(), + args: serde_json::json!({ "kind": "connector" }), + }, + temp_dir.path(), + )?; + assert!( + inspect + .inspect_ndjson + .expect("connector inspect") + .contains("\"kind\":\"connector\"") + ); + Ok(()) +} + +#[test] +fn z_order_helpers_resequence_elements() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Z Order" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let first = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_text_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "text": "A", + "position": { "left": 10, "top": 10, "width": 100, "height": 40 } + }), + }, + temp_dir.path(), + )?; + let second = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_text_shape".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "text": "B", + "position": { "left": 20, "top": 20, "width": 100, "height": 40 } + }), + }, + temp_dir.path(), + )?; + let first_id = first + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .cloned() + .expect("first id"); + let second_id = second + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.last()) + .cloned() + .expect("second id"); + let sent_back = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "send_to_back".to_string(), + args: serde_json::json!({ "element_id": format!("sh/{second_id}") }), + }, + temp_dir.path(), + )?; + assert_eq!( + sent_back + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .map(|slide| slide.element_ids.clone()), + Some(vec![second_id.clone(), first_id.clone()]) + ); + let brought_front = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "bring_to_front".to_string(), + args: serde_json::json!({ "element_id": format!("sh/{second_id}") }), + }, + temp_dir.path(), + )?; + assert_eq!( + brought_front + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .map(|slide| slide.element_ids.clone()), + Some(vec![first_id, second_id]) + ); + Ok(()) +} + +#[test] +fn manager_supports_table_cell_updates_and_merges() -> Result<(), Box> { + let temp_dir = tempfile::tempdir()?; + let mut manager = PresentationArtifactManager::default(); + let created = manager.execute( + PresentationArtifactRequest { + artifact_id: None, + action: "create".to_string(), + args: serde_json::json!({ "name": "Tables" }), + }, + temp_dir.path(), + )?; + let artifact_id = created.artifact_id; + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_slide".to_string(), + args: serde_json::json!({}), + }, + temp_dir.path(), + )?; + let table = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "add_table".to_string(), + args: serde_json::json!({ + "slide_index": 0, + "position": { "left": 24, "top": 24, "width": 240, "height": 120 }, + "rows": [["A", "B"], ["C", "D"]], + "style": "TableStyleMedium9" + }), + }, + temp_dir.path(), + )?; + let table_id = table + .artifact_snapshot + .as_ref() + .and_then(|snapshot| snapshot.slides.first()) + .and_then(|slide| slide.element_ids.first()) + .cloned() + .expect("table id"); + + manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "update_table_cell".to_string(), + args: serde_json::json!({ + "element_id": table_id, + "row": 0, + "column": 1, + "value": "Updated", + "background_fill": "#eeeeee", + "alignment": "right", + "styling": { "bold": true } + }), + }, + temp_dir.path(), + )?; + let inspect = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id.clone()), + action: "inspect".to_string(), + args: serde_json::json!({ "kind": "table" }), + }, + temp_dir.path(), + )?; + assert!( + inspect + .inspect_ndjson + .expect("inspect") + .contains("\"kind\":\"table\"") + ); + + let merged = manager.execute( + PresentationArtifactRequest { + artifact_id: Some(artifact_id), + action: "merge_table_cells".to_string(), + args: serde_json::json!({ + "element_id": table_id, + "start_row": 0, + "end_row": 0, + "start_column": 0, + "end_column": 1 + }), + }, + temp_dir.path(), + )?; + assert_eq!( + merged + .artifact_snapshot + .as_ref() + .map(|snapshot| snapshot.slide_count), + Some(1) + ); + Ok(()) +} diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 87f18eb80..9eb88c66c 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -42,6 +42,7 @@ codex-hooks = { workspace = true } codex-keyring-store = { workspace = true } codex-network-proxy = { workspace = true } codex-otel = { workspace = true } +codex-artifact-presentation = { workspace = true } codex-protocol = { workspace = true } codex-rmcp-client = { workspace = true } codex-state = { workspace = true } diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index af1253374..3cd700091 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -313,6 +313,9 @@ "apps_mcp_gateway": { "type": "boolean" }, + "artifact": { + "type": "boolean" + }, "child_agents_md": { "type": "boolean" }, @@ -1700,6 +1703,9 @@ "apps_mcp_gateway": { "type": "boolean" }, + "artifact": { + "type": "boolean" + }, "child_agents_md": { "type": "boolean" }, diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 6db1c995e..23f658cf2 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -55,6 +55,9 @@ use async_channel::Receiver; use async_channel::Sender; use chrono::Local; use chrono::Utc; +use codex_artifact_presentation::PresentationArtifactError; +use codex_artifact_presentation::PresentationArtifactRequest; +use codex_artifact_presentation::PresentationArtifactResponse; use codex_hooks::HookEvent; use codex_hooks::HookEventAfterAgent; use codex_hooks::HookPayload; @@ -1777,6 +1780,15 @@ impl Session { state.clear_connector_selection(); } + pub(crate) async fn execute_presentation_artifact( + &self, + request: PresentationArtifactRequest, + cwd: &Path, + ) -> Result { + let mut state = self.state.lock().await; + state.presentation_artifacts.execute(request, cwd) + } + async fn record_initial_history(&self, conversation_history: InitialHistory) { let turn_context = self.new_default_turn().await; self.clear_mcp_tool_selection().await; diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 3462a45a4..d76c2dfc4 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -145,6 +145,8 @@ pub enum Feature { CollaborationModes, /// Enable personality selection in the TUI. Personality, + /// Enable native artifact tools. + Artifact, /// Enable Fast mode selection in the TUI and request layer. FastMode, /// Enable voice transcription in the TUI composer. @@ -662,6 +664,12 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::Stable, default_enabled: true, }, + FeatureSpec { + id: Feature::Artifact, + key: "artifact", + stage: Stage::UnderDevelopment, + default_enabled: false, + }, FeatureSpec { id: Feature::FastMode, key: "fast_mode", diff --git a/codex-rs/core/src/state/session.rs b/codex-rs/core/src/state/session.rs index 576db7cf3..822af421b 100644 --- a/codex-rs/core/src/state/session.rs +++ b/codex-rs/core/src/state/session.rs @@ -1,5 +1,6 @@ //! Session-wide mutable state. +use codex_artifact_presentation::PresentationArtifactManager; use codex_protocol::models::ResponseItem; use std::collections::HashMap; use std::collections::HashSet; @@ -32,6 +33,7 @@ pub(crate) struct SessionState { pub(crate) startup_regular_task: Option>>, pub(crate) active_mcp_tool_selection: Option>, pub(crate) active_connector_selection: HashSet, + pub(crate) presentation_artifacts: PresentationArtifactManager, } impl SessionState { @@ -49,6 +51,7 @@ impl SessionState { startup_regular_task: None, active_mcp_tool_selection: None, active_connector_selection: HashSet::new(), + presentation_artifacts: PresentationArtifactManager::default(), } } diff --git a/codex-rs/core/src/tools/handlers/mod.rs b/codex-rs/core/src/tools/handlers/mod.rs index 89e35fa22..49127df10 100644 --- a/codex-rs/core/src/tools/handlers/mod.rs +++ b/codex-rs/core/src/tools/handlers/mod.rs @@ -8,6 +8,7 @@ mod mcp; mod mcp_resource; pub(crate) mod multi_agents; mod plan; +mod presentation_artifact; mod read_file; mod request_user_input; mod search_tool_bm25; @@ -38,6 +39,7 @@ pub use mcp::McpHandler; pub use mcp_resource::McpResourceHandler; pub use multi_agents::MultiAgentHandler; pub use plan::PlanHandler; +pub use presentation_artifact::PresentationArtifactHandler; pub use read_file::ReadFileHandler; pub use request_user_input::RequestUserInputHandler; pub(crate) use request_user_input::request_user_input_tool_description; diff --git a/codex-rs/core/src/tools/handlers/presentation_artifact.rs b/codex-rs/core/src/tools/handlers/presentation_artifact.rs new file mode 100644 index 000000000..866d90de8 --- /dev/null +++ b/codex-rs/core/src/tools/handlers/presentation_artifact.rs @@ -0,0 +1,254 @@ +use async_trait::async_trait; +use codex_artifact_presentation::PathAccessKind; +use codex_artifact_presentation::PathAccessRequirement; +use codex_artifact_presentation::PresentationArtifactError; +use codex_artifact_presentation::PresentationArtifactRequest; +use codex_protocol::protocol::AskForApproval; +use codex_protocol::protocol::ReviewDecision; +use serde_json::to_string; +use std::path::Component; +use std::path::Path; +use std::path::PathBuf; + +use crate::codex::Session; +use crate::codex::TurnContext; +use crate::features::Feature; +use crate::function_tool::FunctionCallError; +use crate::path_utils::normalize_for_path_comparison; +use crate::path_utils::resolve_symlink_write_paths; +use crate::tools::context::ToolInvocation; +use crate::tools::context::ToolOutput; +use crate::tools::context::ToolPayload; +use crate::tools::handlers::parse_arguments; +use crate::tools::registry::ToolHandler; +use crate::tools::registry::ToolKind; +use crate::tools::sandboxing::with_cached_approval; +use codex_protocol::models::FunctionCallOutputBody; + +pub struct PresentationArtifactHandler; + +#[async_trait] +impl ToolHandler for PresentationArtifactHandler { + fn kind(&self) -> ToolKind { + ToolKind::Function + } + + async fn is_mutating(&self, invocation: &ToolInvocation) -> bool { + let ToolPayload::Function { arguments } = &invocation.payload else { + return true; + }; + let Ok(request) = parse_arguments::(arguments) else { + return true; + }; + !matches!( + request.action.as_str(), + "get_summary" + | "list_slides" + | "list_layouts" + | "list_layout_placeholders" + | "list_slide_placeholders" + | "inspect" + | "resolve" + ) + } + + async fn handle(&self, invocation: ToolInvocation) -> Result { + let ToolInvocation { + session, + turn, + payload, + call_id, + .. + } = invocation; + + if !session.enabled(Feature::Artifact) { + return Err(FunctionCallError::RespondToModel( + "presentation_artifact is disabled by feature flag".to_string(), + )); + } + + let arguments = match payload { + ToolPayload::Function { arguments } => arguments, + _ => { + return Err(FunctionCallError::RespondToModel( + "presentation_artifact handler received unsupported payload".to_string(), + )); + } + }; + + let request: PresentationArtifactRequest = parse_arguments(&arguments)?; + for access in request + .required_path_accesses(&turn.cwd) + .map_err(presentation_error)? + { + authorize_path_access(session.as_ref(), turn.as_ref(), &call_id, &access).await?; + } + + let response = session + .execute_presentation_artifact(request, &turn.cwd) + .await + .map_err(presentation_error)?; + + Ok(ToolOutput::Function { + body: FunctionCallOutputBody::Text(to_string(&response).map_err(|error| { + FunctionCallError::RespondToModel(format!( + "failed to serialize presentation_artifact response: {error}" + )) + })?), + success: Some(true), + }) + } +} + +fn presentation_error(error: PresentationArtifactError) -> FunctionCallError { + FunctionCallError::RespondToModel(error.to_string()) +} + +async fn authorize_path_access( + session: &Session, + turn: &TurnContext, + call_id: &str, + access: &PathAccessRequirement, +) -> Result<(), FunctionCallError> { + let effective_path = match access.kind { + PathAccessKind::Read => effective_read_path(&access.path), + PathAccessKind::Write => effective_write_path(&access.path), + }; + let allowed = match access.kind { + PathAccessKind::Read => path_is_readable(turn, &effective_path), + PathAccessKind::Write => path_is_writable(turn, &effective_path), + }; + if allowed { + return Ok(()); + } + + let approval_policy = turn.approval_policy.value(); + if !matches!( + approval_policy, + AskForApproval::OnRequest | AskForApproval::UnlessTrusted + ) { + return Err(FunctionCallError::RespondToModel(format!( + "{} path `{}` is outside the current sandbox policy", + access_kind_label(access.kind), + access.path.display() + ))); + } + + let key = format!( + "presentation_artifact:{:?}:{}", + access.kind, + effective_path.display() + ); + let path = access.path.clone(); + let action = access.action.clone(); + let decision = with_cached_approval( + &session.services, + "presentation_artifact", + vec![key], + || { + let path = path.clone(); + let action = action.clone(); + async move { + session + .request_command_approval( + turn, + call_id.to_string(), + None, + vec![ + "presentation_artifact".to_string(), + action, + path.display().to_string(), + ], + turn.cwd.clone(), + Some(format!( + "Allow presentation_artifact to {} `{}`?", + access_kind_verb(access.kind), + path.display() + )), + None, + None, + None, + None, + ) + .await + } + }, + ) + .await; + + if matches!( + decision, + ReviewDecision::Approved + | ReviewDecision::ApprovedForSession + | ReviewDecision::ApprovedExecpolicyAmendment { .. } + ) { + return Ok(()); + } + + Err(FunctionCallError::RespondToModel(format!( + "{} path `{}` was not approved", + access_kind_label(access.kind), + access.path.display() + ))) +} + +fn path_is_readable(turn: &TurnContext, path: &Path) -> bool { + if turn.sandbox_policy.has_full_disk_read_access() { + return true; + } + + turn.sandbox_policy + .get_readable_roots_with_cwd(&turn.cwd) + .iter() + .any(|root| path.starts_with(root.as_path())) +} + +fn path_is_writable(turn: &TurnContext, path: &Path) -> bool { + if turn.sandbox_policy.has_full_disk_write_access() { + return true; + } + + turn.sandbox_policy + .get_writable_roots_with_cwd(&turn.cwd) + .iter() + .any(|root| root.is_path_writable(path)) +} + +fn effective_read_path(path: &Path) -> PathBuf { + normalize_for_path_comparison(path).unwrap_or_else(|_| normalize_without_fs(path)) +} + +fn effective_write_path(path: &Path) -> PathBuf { + let write_path = resolve_symlink_write_paths(path) + .map(|paths| paths.write_path) + .unwrap_or_else(|_| path.to_path_buf()); + normalize_for_path_comparison(&write_path).unwrap_or_else(|_| normalize_without_fs(&write_path)) +} + +fn normalize_without_fs(path: &Path) -> PathBuf { + let mut normalized = PathBuf::new(); + for component in path.components() { + match component { + Component::ParentDir => { + normalized.pop(); + } + Component::CurDir => {} + other => normalized.push(other.as_os_str()), + } + } + normalized +} + +fn access_kind_label(kind: PathAccessKind) -> &'static str { + match kind { + PathAccessKind::Read => "read", + PathAccessKind::Write => "write", + } +} + +fn access_kind_verb(kind: PathAccessKind) -> &'static str { + match kind { + PathAccessKind::Read => "read from", + PathAccessKind::Write => "write to", + } +} diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 76730d089..6b36cdca4 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -35,6 +35,8 @@ use std::collections::HashMap; const SEARCH_TOOL_BM25_DESCRIPTION_TEMPLATE: &str = include_str!("../../templates/search_tool/tool_description.md"); +const PRESENTATION_ARTIFACT_DESCRIPTION_TEMPLATE: &str = + include_str!("../../templates/tools/presentation_artifact.md"); #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum ShellCommandBackendConfig { @@ -55,6 +57,7 @@ pub(crate) struct ToolsConfig { pub js_repl_enabled: bool, pub js_repl_tools_only: bool, pub collab_tools: bool, + pub presentation_artifact: bool, pub default_mode_request_user_input: bool, pub experimental_supported_tools: Vec, pub agent_jobs_tools: bool, @@ -84,6 +87,7 @@ impl ToolsConfig { let include_default_mode_request_user_input = features.enabled(Feature::DefaultModeRequestUserInput); let include_search_tool = features.enabled(Feature::Apps); + let include_presentation_artifact = features.enabled(Feature::Artifact); let include_agent_jobs = include_collab_tools && features.enabled(Feature::Sqlite); let request_permission_enabled = features.enabled(Feature::RequestPermissions); let shell_command_backend = @@ -139,6 +143,7 @@ impl ToolsConfig { js_repl_enabled: include_js_repl, js_repl_tools_only: include_js_repl_tools_only, collab_tools: include_collab_tools, + presentation_artifact: include_presentation_artifact, default_mode_request_user_input: include_default_mode_request_user_input, experimental_supported_tools: model_info.experimental_supported_tools.clone(), agent_jobs_tools: include_agent_jobs, @@ -561,6 +566,44 @@ fn create_view_image_tool() -> ToolSpec { }) } +fn create_presentation_artifact_tool() -> ToolSpec { + let properties = BTreeMap::from([ + ( + "artifact_id".to_string(), + JsonSchema::String { + description: Some( + "Artifact id returned by an earlier presentation_artifact call.".to_string(), + ), + }, + ), + ( + "action".to_string(), + JsonSchema::String { + description: Some("Action name to run against the artifact.".to_string()), + }, + ), + ( + "args".to_string(), + JsonSchema::Object { + properties: BTreeMap::new(), + required: None, + additional_properties: Some(true.into()), + }, + ), + ]); + + ToolSpec::Function(ResponsesApiTool { + name: "presentation_artifact".to_string(), + description: PRESENTATION_ARTIFACT_DESCRIPTION_TEMPLATE.to_string(), + strict: false, + parameters: JsonSchema::Object { + properties, + required: Some(vec!["action".to_string(), "args".to_string()]), + additional_properties: Some(false.into()), + }, + }) +} + fn create_collab_input_items_schema() -> JsonSchema { let properties = BTreeMap::from([ ( @@ -1658,6 +1701,7 @@ pub(crate) fn build_specs( use crate::tools::handlers::McpResourceHandler; use crate::tools::handlers::MultiAgentHandler; use crate::tools::handlers::PlanHandler; + use crate::tools::handlers::PresentationArtifactHandler; use crate::tools::handlers::ReadFileHandler; use crate::tools::handlers::RequestUserInputHandler; use crate::tools::handlers::SearchToolBm25Handler; @@ -1685,6 +1729,7 @@ pub(crate) fn build_specs( let search_tool_handler = Arc::new(SearchToolBm25Handler); let js_repl_handler = Arc::new(JsReplHandler); let js_repl_reset_handler = Arc::new(JsReplResetHandler); + let presentation_artifact_handler = Arc::new(PresentationArtifactHandler); let request_permission_enabled = config.request_permission_enabled; match &config.shell_type { @@ -1822,6 +1867,11 @@ pub(crate) fn build_specs( builder.push_spec_with_parallel_support(create_view_image_tool(), true); builder.register_handler("view_image", view_image_handler); + if config.presentation_artifact { + builder.push_spec(create_presentation_artifact_tool()); + builder.register_handler("presentation_artifact", presentation_artifact_handler); + } + if config.collab_tools { let multi_agent_handler = Arc::new(MultiAgentHandler); builder.push_spec(create_spawn_agent_tool(config)); @@ -2122,6 +2172,23 @@ mod tests { ); } + #[test] + fn test_build_specs_artifact_tool_enabled() { + let config = test_config(); + let model_info = + ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config); + let mut features = Features::with_defaults(); + features.enable(Feature::Artifact); + let tools_config = ToolsConfig::new(&ToolsConfigParams { + model_info: &model_info, + features: &features, + web_search_mode: Some(WebSearchMode::Cached), + session_source: SessionSource::Cli, + }); + let (tools, _) = build_specs(&tools_config, None, None, &[]).build(); + assert_contains_tool_names(&tools, &["presentation_artifact"]); + } + #[test] fn test_build_specs_agent_job_worker_tools_enabled() { let config = test_config(); diff --git a/codex-rs/core/templates/tools/presentation_artifact.md b/codex-rs/core/templates/tools/presentation_artifact.md new file mode 100644 index 000000000..b3a3673fb --- /dev/null +++ b/codex-rs/core/templates/tools/presentation_artifact.md @@ -0,0 +1,95 @@ +Create and edit PowerPoint presentation artifacts inside the current thread. + +- This is a stateful built-in tool. `artifact_id` values are returned by earlier calls and persist only for the current thread. +- Resume and fork do not restore live artifact state. Export files if you need a durable handoff. +- Relative paths resolve from the current working directory. +- Position and size values are in slide points. + +Supported actions: +- `create` +- `import_pptx` +- `export_pptx` +- `export_preview` +- `get_summary` +- `list_slides` +- `list_layouts` +- `list_layout_placeholders` +- `list_slide_placeholders` +- `inspect` +- `resolve` +- `create_layout` +- `add_layout_placeholder` +- `set_slide_layout` +- `update_placeholder_text` +- `set_theme` +- `set_notes` +- `append_notes` +- `clear_notes` +- `set_notes_visibility` +- `add_slide` +- `insert_slide` +- `duplicate_slide` +- `move_slide` +- `delete_slide` +- `set_slide_background` +- `add_text_shape` +- `add_shape` +- `add_connector` +- `add_image` +- `replace_image` +- `add_table` +- `update_table_cell` +- `merge_table_cells` +- `add_chart` +- `update_text` +- `update_shape_style` +- `bring_to_front` +- `send_to_back` +- `delete_element` +- `delete_artifact` + +Example create: +`{"action":"create","args":{"name":"Quarterly Update"}}` + +Example edit: +`{"artifact_id":"presentation_x","action":"add_text_shape","args":{"slide_index":0,"text":"Revenue up 24%","position":{"left":48,"top":72,"width":260,"height":80}}}` + +Example export: +`{"artifact_id":"presentation_x","action":"export_pptx","args":{"path":"artifacts/q2-update.pptx"}}` + +Example layout flow: +`{"artifact_id":"presentation_x","action":"create_layout","args":{"name":"Title Slide"}}` + +`{"artifact_id":"presentation_x","action":"add_layout_placeholder","args":{"layout_id":"layout_1","name":"title","placeholder_type":"title","text":"Click to add title","position":{"left":48,"top":48,"width":624,"height":72}}}` + +`{"artifact_id":"presentation_x","action":"set_slide_layout","args":{"slide_index":0,"layout_id":"layout_1"}}` + +`{"artifact_id":"presentation_x","action":"list_layout_placeholders","args":{"layout_id":"layout_1"}}` + +`{"artifact_id":"presentation_x","action":"list_slide_placeholders","args":{"slide_index":0}}` + +Example inspect: +`{"artifact_id":"presentation_x","action":"inspect","args":{"kind":"deck,slide,textbox,shape,table,chart,image,notes,layoutList","max_chars":12000}}` + +Example resolve: +`{"artifact_id":"presentation_x","action":"resolve","args":{"id":"sh/element_3"}}` + +Notes visibility is honored on export: `set_notes_visibility` controls whether speaker notes are emitted into exported PPTX output. + +Image placeholders can be prompt-only. `add_image` accepts `prompt` without `path`/`data_url`, and unresolved placeholders export as a visible placeholder box instead of failing. + +Remote images are supported. `add_image` and `replace_image` accept `uri` in addition to local `path` and `data_url`. + +Image edits can target inspect/resolve anchors like `im/element_3`, and `update_shape_style` now accepts image `fit`, `crop`, and `lock_aspect_ratio` updates. + +`update_shape_style.position` accepts partial updates, so you can move or resize an element without resending the full rect. + +Connectors are supported via `add_connector`, with straight/elbow/curved types plus dash styles and arrow heads. + +Example preview: +`{"artifact_id":"presentation_x","action":"export_preview","args":{"slide_index":0,"path":"artifacts/q2-update-slide1.png"}}` + +`export_preview` also accepts `format`, `scale`, and `quality` for rendered previews. `format` currently supports `png` and `jpeg`. + +Example JPEG preview: +`{"artifact_id":"presentation_x","action":"export_preview","args":{"slide_index":0,"path":"artifacts/q2-update-slide1.jpg","format":"jpeg","scale":0.75,"quality":85}}` diff --git a/codex-rs/deny.toml b/codex-rs/deny.toml index 29bf6c588..d2de50249 100644 --- a/codex-rs/deny.toml +++ b/codex-rs/deny.toml @@ -78,6 +78,7 @@ ignore = [ # TODO(fcoury): remove this exception when syntect drops yaml-rust and bincode, or updates to versions that have fixed the vulnerabilities. { id = "RUSTSEC-2024-0320", reason = "yaml-rust is unmaintained; pulled in via syntect v5.3.0 used by codex-tui for syntax highlighting; no fixed release yet" }, { id = "RUSTSEC-2025-0141", reason = "bincode is unmaintained; pulled in via syntect v5.3.0 used by codex-tui for syntax highlighting; no fixed release yet" }, + { id = "RUSTSEC-2025-0134", reason = "rustls-pemfile is unmaintained; pulled in via reqwest -> ppt-rs -> codex-artifact-presentation; no safe upgrade available yet" }, ] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library.