diff --git a/Cargo.lock b/Cargo.lock index 54ef9f0c1..ca29f4b18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,26 +90,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" - -[[package]] -name = "async-lock" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", @@ -202,9 +191,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecount" @@ -220,9 +209,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.90" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" [[package]] name = "cfg-if" @@ -276,15 +265,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "concurrent-queue" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "const-random" version = "0.1.18" @@ -330,12 +310,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" - [[package]] name = "crunchy" version = "0.2.2" @@ -419,9 +393,9 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -432,27 +406,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener", - "pin-project-lite", -] - [[package]] name = "fancy-regex" version = "0.11.0" @@ -581,9 +534,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "js-sys", @@ -646,9 +599,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -834,9 +787,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cdbb7cb6f3ba28f5b212dd250ab4483105efc3e381f5c8bb90340f14f0a2cc3" +checksum = "c4b0e68d9af1f066c06d6e2397583795b912d78537d7d907c561e82c13d69fa1" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -848,9 +801,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab2e14e727d2faf388c99d9ca5210566ed3b044f07d92c29c3611718d178380" +checksum = "92f254f56af1ae84815b9b1325094743dcf05b92abb5e94da2e81a35cff0cada" dependencies = [ "futures-channel", "futures-util", @@ -872,12 +825,11 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71962a1c49af43adf81d337e4ebc93f3c915faf6eccaa14d74e255107dfd7723" +checksum = "274d68152c24aa78977243bb56f28d7946e6aa309945b37d33174a3f92d89a3a" dependencies = [ "anyhow", - "async-lock", "async-trait", "beef", "futures-timer", @@ -897,9 +849,9 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c13987da51270bda2c1c9b40c19be0fe9b225c7a0553963d8f17e683a50ce84" +checksum = "ac13bc1e44cd00448a5ff485824a128629c945f02077804cb659c07a0ba41395" dependencies = [ "async-trait", "hyper", @@ -917,9 +869,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e53c72de6cd2ad6ac1aa6e848206ef8b736f92ed02354959130373dfa5b3cbd" +checksum = "3dc828e537868d6b12bbb07ec20324909a22ced6efca0057c825c3e1126b2c6d" dependencies = [ "anyhow", "beef", @@ -930,9 +882,9 @@ dependencies = [ [[package]] name = "jsonrpsee-wasm-client" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae2c3f2411052b4a831cb7a34cd1498e0d8b9309bd49fca67567634ff64023d" +checksum = "7cf8dcee48f383e24957e238240f997ec317ba358b4e6d2e8be3f745bcdabdb5" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -941,9 +893,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8a07ab8da9a283b906f6735ddd17d3680158bb72259e853441d1dd0167079ec" +checksum = "32f00abe918bf34b785f87459b9205790e5361a3f7437adb50e928dc243f27eb" dependencies = [ "http", "jsonrpsee-client-transport", @@ -996,13 +948,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.5.0", "libc", - "redox_syscall", ] [[package]] @@ -1033,15 +984,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "minidom" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe549115a674f5ec64c754d85e37d6f42664bd0ef4ffb62b619489ad99c6cb1a" -dependencies = [ - "quick-xml", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1080,9 +1022,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41" dependencies = [ "num-bigint", "num-complex", @@ -1204,20 +1146,14 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "ordered-multimap" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d6a8c22fc714f0c2373e6091bf6f5e9b37b1bc0b1184874b7e0a4e303d318f" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ "dlv-list", "hashbrown 0.14.3", ] -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - [[package]] name = "parking_lot" version = "0.12.1" @@ -1289,9 +1225,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1336,31 +1272,18 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.17.2" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", ] -[[package]] -name = "quickxml_to_serde" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "286b05c7a00b356ff6ac5218e10d628e0a3be02e777d067ca7286d353c3c407e" -dependencies = [ - "minidom", - "regex", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1406,9 +1329,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -1563,7 +1486,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.1", + "rustls-pemfile 2.1.2", "rustls-pki-types", "schannel", "security-framework", @@ -1580,11 +1503,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "rustls-pki-types", ] @@ -1617,9 +1540,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "ryu" @@ -1654,9 +1577,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1667,9 +1590,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -1785,9 +1708,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -1797,9 +1720,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.55" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -1866,9 +1789,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "num-conv", @@ -1886,9 +1809,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -2153,7 +2076,7 @@ dependencies = [ "petgraph", "petgraph-graphml", "prettytable-rs", - "quickxml_to_serde", + "quick-xml", "rand", "rust-ini", "serde", @@ -2290,7 +2213,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -2310,17 +2233,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -2331,9 +2255,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -2343,9 +2267,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -2355,9 +2279,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -2367,9 +2297,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -2379,9 +2309,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -2391,9 +2321,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -2403,9 +2333,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winreg" @@ -2419,9 +2349,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" [[package]] name = "xmltree" diff --git a/Cargo.toml b/Cargo.toml index 84a569c1b..2b4ed0a06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ jsonschema = "0.17.1" petgraph = "0.6.4" petgraph-graphml = "3.0.0" prettytable-rs = "0.10.0" -quickxml_to_serde = "0.6.0" +quick-xml = "0.31.0" rand = "0.8.5" rust-ini = "0.21.0" serde = { version = "1.0.197", features = ["derive"] } @@ -22,6 +22,7 @@ serde_json = "1.0.115" tokio = { version = "1.36.0", features = ["rt-multi-thread"] } xmltree = { version = "0.10.3", features = ["indexmap"] } + [[bin]] name = "warcli" path = "src/rust-cli/main.rs" diff --git a/src/rust-cli/graph.rs b/src/rust-cli/graph.rs index 70dbf88c8..3e985078b 100644 --- a/src/rust-cli/graph.rs +++ b/src/rust-cli/graph.rs @@ -2,16 +2,14 @@ use rand::seq::SliceRandom; use rand::thread_rng; use std::borrow::Cow; use std::fs::File; -use std::io::{BufReader, Cursor, Read}; +use std::io::Cursor; use std::path::{Path, PathBuf}; +use crate::graphml::GraphML; use crate::util::{dump_bitcoin_conf, parse_bitcoin_conf}; use anyhow::Context; use clap::Subcommand; -use jsonschema::JSONSchema; use petgraph::graph::{DiGraph, NodeIndex}; -use petgraph_graphml::GraphMl; -use quickxml_to_serde::{xml_string_to_json, Config}; use xmltree::{Element, EmitterConfig, XMLNode}; #[derive(Subcommand, Debug)] @@ -96,7 +94,7 @@ fn handle_bitcoin_conf(bitcoin_conf: Option<&Path>) -> String { } fn convert_to_graphml(graph: &petgraph::graph::DiGraph<(), ()>) -> anyhow::Result> { - let graphml = GraphMl::new(graph).pretty_print(true); + let graphml = petgraph_graphml::GraphMl::new(graph).pretty_print(true); let mut buf = Vec::new(); graphml .to_writer(&mut buf) @@ -173,36 +171,10 @@ fn add_custom_attributes( } fn validate_schema(graph: &PathBuf) -> anyhow::Result<()> { - let f = - File::open("src/schema/graph_schema.json").context("Read schema file from source dir")?; - let reader = BufReader::new(f); - let schema: serde_json::Value = - serde_json::from_reader(reader).context("Read schema into serde_json Value")?; - // TODO: this hack needing a static lifetime seems wrong. Try and fix it - let schema_static: &'static serde_json::Value = Box::leak(Box::new(schema)); - let compiled_schema = - JSONSchema::compile(schema_static).context("compile schema into JSONSchema")?; - - // Parse graph into serde_json::Value - let f = File::open(graph).context("Read xml graph from disk")?; - let mut reader = BufReader::new(f); - let mut xml_string = String::new(); - reader.read_to_string(&mut xml_string)?; - let conf = Config::new_with_defaults(); - let json = xml_string_to_json(xml_string, &conf).context("Convert xml string to JSON")?; - println!("{}", json); - - // Validate schema - // TODO: THIS IS NOT WORKING - // We need to iterate over nodes and edges and call validate on each one, I think? - let result = compiled_schema.validate(&json); - if let Err(errors) = result { - for error in errors { - println!("Validation error: {}", error); - println!("Instance path: {}", error.instance_path); - } - } - println!("Schema validated successfully!"); + // Our graphml files should include a "key" section describing kinds of values available in each + // node. The parsing logic in GraphML does similar validation work as the existing python + // validation approach. + let _ = GraphML::from_file(graph).context("Could not load and parse graphml file")?; Ok(()) } @@ -268,3 +240,91 @@ pub async fn handle_graph_command(command: &GraphCommand) -> anyhow::Result<()> } Ok(()) } + +#[cfg(test)] +mod tests { + use std::{io::Write, str::FromStr}; + + use super::*; + + static GRAPHML_FILE: &str = r#" + + + + + + + + + + + 26.0 + + + + False + False + + + bitcoindevproject/bitcoin:26.0 + + + + False + False + + + 26.0 + + + + False + False + + + 26.0 + + + + False + False + + + 26.0 + + + + False + False + + + + + + + + + + + + + +"#; + + #[test] + fn validate_graphml() { + let path = "temp_graphml_file_for_validate_graphml_test.xml"; + let mut file = std::fs::File::create(path).unwrap(); + file.write_all(GRAPHML_FILE.as_bytes()).unwrap(); + let schema_is_valid = match validate_schema(&PathBuf::from_str(path).unwrap()) { + Ok(_) => true, + Err(e) => { + println!("{:?}", e); + false + } + }; + assert!(schema_is_valid); + std::fs::remove_file(path).unwrap(); + } +} diff --git a/src/rust-cli/graphml.rs b/src/rust-cli/graphml.rs new file mode 100644 index 000000000..c902c8c04 --- /dev/null +++ b/src/rust-cli/graphml.rs @@ -0,0 +1,744 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// Originally from: +// https://github.com/Qiskit/rustworkx/blob/d5521e3ac8f1332c2caf0b172fc24b19a9352699/src/graphml.rs#L12 + +#![allow(clippy::borrow_as_ptr)] + +use std::collections::{BTreeMap, HashMap}; +use std::convert::From; +use std::fmt::Display; +use std::iter::FromIterator; +use std::num::{ParseFloatError, ParseIntError}; +use std::path::Path; +use std::str::ParseBoolError; + +use quick_xml::events::{BytesStart, Event}; +use quick_xml::name::QName; +use quick_xml::Error as XmlError; +use quick_xml::Reader; + +use petgraph::algo; +use petgraph::{Directed, Undirected}; + +// use pyo3::exceptions::PyException; +// use pyo3::prelude::*; +// use pyo3::PyErr; + +// use crate::{digraph::PyDiGraph, graph::PyGraph, StablePyGraph}; + +#[derive(Debug)] +pub enum Error { + Xml(String), + ParseValue(String), + NotFound(String), + UnSupported(String), + InvalidDoc(String), +} +impl std::error::Error for Error {} +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Xml(_) => write!(f, "XML Error"), + Error::ParseValue(value) => write!(f, "ParseValue Error: {value}"), + Error::NotFound(_) => write!(f, "NotFound Error"), + Error::UnSupported(_) => write!(f, "UnSupported Error"), + Error::InvalidDoc(_) => write!(f, "InvalidDoc Error"), + } + } +} + +impl From for Error { + #[inline] + fn from(e: XmlError) -> Error { + Error::Xml(format!("Xml document not well-formed: {}", e)) + } +} + +impl From for Error { + #[inline] + fn from(e: ParseBoolError) -> Error { + Error::ParseValue(format!("Failed conversion to 'bool': {}", e)) + } +} + +impl From for Error { + #[inline] + fn from(e: ParseIntError) -> Error { + Error::ParseValue(format!("Failed conversion to 'int' or 'long': {}", e)) + } +} + +impl From for Error { + #[inline] + fn from(e: ParseFloatError) -> Error { + Error::ParseValue(format!("Failed conversion to 'float' or 'double': {}", e)) + } +} + +// impl From for PyErr { +// #[inline] +// fn from(error: Error) -> PyErr { +// match error { +// Error::Xml(msg) +// | Error::ParseValue(msg) +// | Error::NotFound(msg) +// | Error::UnSupported(msg) +// | Error::InvalidDoc(msg) => PyException::new_err(msg), +// } +// } +// } + +fn xml_attribute<'a>(element: &'a BytesStart<'a>, key: &[u8]) -> Result { + element + .attributes() + .find_map(|a| { + if let Ok(a) = a { + if a.key == QName(key) { + let decoded = a + .unescape_value() + .map_err(Error::from) + .map(|cow_str| cow_str.into_owned()); + return Some(decoded); + } + } + None + }) + .unwrap_or_else(|| { + Err(Error::NotFound(format!( + "Attribute '{}' not found.", + String::from_utf8_lossy(key) + ))) + }) +} + +#[derive(Clone, Copy)] +enum Domain { + Node, + Edge, + Graph, + All, +} + +enum Type { + Boolean, + Int, + Float, + Double, + String, + Long, +} + +#[derive(Clone)] +enum Value { + Boolean(bool), + Int(isize), + Float(f32), + Double(f64), + String(String), + Long(isize), + UnDefined, +} + +// impl IntoPy for Value { +// fn into_py(self, py: Python) -> PyObject { +// match self { +// Value::Boolean(val) => val.into_py(py), +// Value::Int(val) => val.into_py(py), +// Value::Float(val) => val.into_py(py), +// Value::Double(val) => val.into_py(py), +// Value::String(val) => val.into_py(py), +// Value::Long(val) => val.into_py(py), +// Value::UnDefined => py.None(), +// } +// } +// } + +struct Key { + name: String, + ty: Type, + default: Value, +} + +impl Key { + fn parse(&self, val: String) -> Result { + Ok(match self.ty { + Type::Boolean => { + let val = val.to_lowercase(); + Value::Boolean(val.parse()?) + } + Type::Int => Value::Int(val.parse()?), + Type::Float => Value::Float(val.parse()?), + Type::Double => Value::Double(val.parse()?), + Type::String => Value::String(val), + Type::Long => Value::Long(val.parse()?), + }) + } + + fn set_value(&mut self, val: String) -> Result<(), Error> { + self.default = self.parse(val)?; + Ok(()) + } +} + +struct Node { + id: String, + data: HashMap, +} + +struct Edge { + id: Option, + source: String, + target: String, + data: HashMap, +} + +enum Direction { + Directed, + UnDirected, +} + +pub struct Graph { + dir: Direction, + nodes: Vec, + edges: Vec, + attributes: HashMap, +} + +impl Graph { + fn new<'a, I>(dir: Direction, default_attrs: I) -> Self + where + I: Iterator, + { + Self { + dir, + nodes: Vec::new(), + edges: Vec::new(), + attributes: HashMap::from_iter( + default_attrs.map(|key| (key.name.clone(), key.default.clone())), + ), + } + } + + fn add_node<'a, I>(&mut self, element: &'a BytesStart<'a>, default_data: I) -> Result<(), Error> + where + I: Iterator, + { + self.nodes.push(Node { + id: xml_attribute(element, b"id")?, + data: HashMap::from_iter( + default_data.map(|key| (key.name.clone(), key.default.clone())), + ), + }); + + Ok(()) + } + + fn add_edge<'a, I>(&mut self, element: &'a BytesStart<'a>, default_data: I) -> Result<(), Error> + where + I: Iterator, + { + self.edges.push(Edge { + id: xml_attribute(element, b"id").ok(), + source: xml_attribute(element, b"source")?, + target: xml_attribute(element, b"target")?, + data: HashMap::from_iter( + default_data.map(|key| (key.name.clone(), key.default.clone())), + ), + }); + + Ok(()) + } + + fn last_node_set_data(&mut self, key: &Key, val: String) -> Result<(), Error> { + if let Some(node) = self.nodes.last_mut() { + node.data.insert(key.name.clone(), key.parse(val)?); + } + + Ok(()) + } + + fn last_edge_set_data(&mut self, key: &Key, val: String) -> Result<(), Error> { + if let Some(edge) = self.edges.last_mut() { + edge.data.insert(key.name.clone(), key.parse(val)?); + } + + Ok(()) + } +} + +// impl IntoPy for Graph { +// fn into_py(self, py: Python) -> PyObject { +// macro_rules! make_graph { +// ($graph:ident) => { +// let mut mapping = HashMap::with_capacity(self.nodes.len()); +// for mut node in self.nodes { +// // Write the node id from GraphML doc into the node data payload +// // since in rustworkx nodes are indexed by an unsigned integer and +// // not by a hashable String. +// node.data +// .insert(String::from("id"), Value::String(node.id.clone())); +// mapping.insert(node.id, $graph.add_node(node.data.into_py(py))); +// } + +// for mut edge in self.edges { +// match (mapping.get(&edge.source), mapping.get(&edge.target)) { +// (Some(&source), Some(&target)) => { +// // Write the edge id from GraphML doc into the edge data payload +// // since in rustworkx edges are indexed by an unsigned integer and +// // not by a hashable String. +// if let Some(id) = edge.id { +// edge.data.insert(String::from("id"), Value::String(id)); +// } +// $graph.add_edge(source, target, edge.data.into_py(py)); +// } +// _ => { +// // We skip an edge if one of its endpoints was not added earlier in the graph. +// } +// } +// } +// }; +// } + +// match self.dir { +// Direction::UnDirected => { +// let mut graph = +// StablePyGraph::::with_capacity(self.nodes.len(), self.edges.len()); +// make_graph!(graph); + +// let out = PyGraph { +// graph, +// node_removed: false, +// multigraph: true, +// attrs: self.attributes.into_py(py), +// }; + +// out.into_py(py) +// } +// Direction::Directed => { +// let mut graph = +// StablePyGraph::::with_capacity(self.nodes.len(), self.edges.len()); +// make_graph!(graph); + +// let out = PyDiGraph { +// graph, +// cycle_state: algo::DfsSpace::default(), +// check_cycle: false, +// node_removed: false, +// multigraph: true, +// attrs: self.attributes.into_py(py), +// }; + +// out.into_py(py) +// } +// } +// } +// } + +#[derive(Debug)] +enum State { + Start, + Graph, + Node, + Edge, + DataForNode, + DataForEdge, + DataForGraph, + Key, + DefaultForKey, +} + +macro_rules! matches { + ($expression:expr, $( $pattern:pat_param )|+) => { + match $expression { + $( $pattern )|+ => {}, + _ => { + return Err(Error::InvalidDoc(String::from( + "The input xml document doesn't follow the syntax of GraphML language \ + (or it has features that are not supported by the current version of the parser)." + ))); + } + } + } +} + +pub struct GraphML { + graphs: Vec, + key_for_nodes: BTreeMap, + key_for_edges: BTreeMap, + key_for_graph: BTreeMap, + key_for_all: BTreeMap, +} + +impl Default for GraphML { + fn default() -> Self { + Self { + graphs: Vec::new(), + key_for_nodes: BTreeMap::new(), + key_for_edges: BTreeMap::new(), + key_for_graph: BTreeMap::new(), + key_for_all: BTreeMap::new(), + } + } +} + +impl GraphML { + pub fn create_graph<'a>(&mut self, element: &'a BytesStart<'a>) -> Result<(), Error> { + let dir = match xml_attribute(element, b"edgedefault")?.as_bytes() { + b"directed" => Direction::Directed, + b"undirected" => Direction::UnDirected, + _ => { + return Err(Error::InvalidDoc(String::from( + "Invalid 'edgedefault' attribute.", + ))); + } + }; + + self.graphs.push(Graph::new( + dir, + self.key_for_graph.values().chain(self.key_for_all.values()), + )); + + Ok(()) + } + + pub fn add_node<'a>(&mut self, element: &'a BytesStart<'a>) -> Result<(), Error> { + if let Some(graph) = self.graphs.last_mut() { + graph.add_node( + element, + self.key_for_nodes.values().chain(self.key_for_all.values()), + )?; + } + + Ok(()) + } + + pub fn add_edge<'a>(&mut self, element: &'a BytesStart<'a>) -> Result<(), Error> { + if let Some(graph) = self.graphs.last_mut() { + graph.add_edge( + element, + self.key_for_edges.values().chain(self.key_for_all.values()), + )?; + } + + Ok(()) + } + + pub fn add_graphml_key<'a>(&mut self, element: &'a BytesStart<'a>) -> Result { + let id = xml_attribute(element, b"id")?; + let ty = match xml_attribute(element, b"attr.type")?.as_bytes() { + b"boolean" => Type::Boolean, + b"int" => Type::Int, + b"float" => Type::Float, + b"double" => Type::Double, + b"string" => Type::String, + b"long" => Type::Long, + _ => { + return Err(Error::InvalidDoc(format!( + "Invalid 'attr.type' attribute in key with id={}.", + id, + ))); + } + }; + + let key = Key { + name: xml_attribute(element, b"attr.name")?, + ty, + default: Value::UnDefined, + }; + + match xml_attribute(element, b"for")?.as_bytes() { + b"node" => { + self.key_for_nodes.insert(id, key); + Ok(Domain::Node) + } + b"edge" => { + self.key_for_edges.insert(id, key); + Ok(Domain::Edge) + } + b"graph" => { + self.key_for_graph.insert(id, key); + Ok(Domain::Graph) + } + b"all" => { + self.key_for_all.insert(id, key); + Ok(Domain::All) + } + _ => Err(Error::InvalidDoc(format!( + "Invalid 'for' attribute in key with id={}.", + id, + ))), + } + } + + pub fn last_key_set_value(&mut self, val: String, domain: Domain) -> Result<(), Error> { + let elem = match domain { + Domain::Node => self.key_for_nodes.last_entry(), + Domain::Edge => self.key_for_edges.last_entry(), + Domain::Graph => self.key_for_graph.last_entry(), + Domain::All => self.key_for_all.last_entry(), + }; + + if let Some(mut entry) = elem { + entry.get_mut().set_value(val)? + } + + Ok(()) + } + + pub fn last_node_set_data(&mut self, key: &str, val: String) -> Result<(), Error> { + let key = match self.key_for_all.get(key) { + Some(key) => key, + None => self + .key_for_nodes + .get(key) + .ok_or_else(|| Error::NotFound(format!("Key '{}' for nodes not found.", key)))?, + }; + + if let Some(graph) = self.graphs.last_mut() { + graph.last_node_set_data(key, val)?; + } + + Ok(()) + } + + pub fn last_edge_set_data(&mut self, key: &str, val: String) -> Result<(), Error> { + let key = match self.key_for_all.get(key) { + Some(key) => key, + None => self + .key_for_edges + .get(key) + .ok_or_else(|| Error::NotFound(format!("Key '{}' for edges not found.", key)))?, + }; + + if let Some(graph) = self.graphs.last_mut() { + graph.last_edge_set_data(key, val)?; + } + + Ok(()) + } + + pub fn last_graph_set_attribute(&mut self, key: &str, val: String) -> Result<(), Error> { + let key = match self.key_for_all.get(key) { + Some(key) => key, + None => self + .key_for_graph + .get(key) + .ok_or_else(|| Error::NotFound(format!("Key '{}' for graph not found.", key)))?, + }; + + if let Some(graph) = self.graphs.last_mut() { + graph.attributes.insert(key.name.clone(), key.parse(val)?); + } + + Ok(()) + } + + /// Parse a file written in GraphML format. + /// + /// The implementation is based on a state machine in order to + /// accept only valid GraphML syntax (e.g a `` element should + /// be nested inside a `` element) where the internal state changes + /// after handling each quick_xml event. + pub fn from_file>(path: P) -> Result { + let mut graphml = GraphML::default(); + + let mut buf = Vec::new(); + let mut reader = Reader::from_file(path)?; + + let mut state = State::Start; + let mut domain_of_last_key = Domain::Node; + let mut last_data_key = String::new(); + + loop { + match reader.read_event_into(&mut buf)? { + Event::Start(ref e) => match e.name() { + QName(b"key") => { + matches!(state, State::Start); + domain_of_last_key = graphml.add_graphml_key(e)?; + state = State::Key; + } + QName(b"default") => { + matches!(state, State::Key); + state = State::DefaultForKey; + } + QName(b"graph") => { + matches!(state, State::Start); + graphml.create_graph(e)?; + state = State::Graph; + } + QName(b"node") => { + matches!(state, State::Graph); + graphml.add_node(e)?; + state = State::Node; + } + QName(b"edge") => { + matches!(state, State::Graph); + graphml.add_edge(e)?; + state = State::Edge; + } + QName(b"data") => { + matches!(state, State::Node | State::Edge | State::Graph); + last_data_key = xml_attribute(e, b"key")?; + match state { + State::Node => state = State::DataForNode, + State::Edge => state = State::DataForEdge, + State::Graph => state = State::DataForGraph, + _ => { + // in all other cases we have already bailed out in `matches`. + unreachable!() + } + } + } + QName(b"hyperedge") => { + return Err(Error::UnSupported(String::from( + "Hyperedges are not supported.", + ))); + } + QName(b"port") => { + return Err(Error::UnSupported(String::from("Ports are not supported."))); + } + _ => {} + }, + Event::Empty(ref e) => match e.name() { + QName(b"key") => { + matches!(state, State::Start); + graphml.add_graphml_key(e)?; + } + QName(b"node") => { + matches!(state, State::Graph); + graphml.add_node(e)?; + } + QName(b"edge") => { + matches!(state, State::Graph); + graphml.add_edge(e)?; + } + QName(b"port") => { + return Err(Error::UnSupported(String::from("Ports are not supported."))); + } + _ => {} + }, + Event::End(ref e) => match e.name() { + QName(b"key") => { + matches!(state, State::Key); + state = State::Start; + } + QName(b"default") => { + matches!(state, State::DefaultForKey); + state = State::Key; + } + QName(b"graph") => { + matches!(state, State::Graph); + state = State::Start; + } + QName(b"node") => { + matches!(state, State::Node); + state = State::Graph; + } + QName(b"edge") => { + matches!(state, State::Edge); + state = State::Graph; + } + QName(b"data") => { + matches!( + state, + State::DataForNode | State::DataForEdge | State::DataForGraph + ); + match state { + State::DataForNode => state = State::Node, + State::DataForEdge => state = State::Edge, + State::DataForGraph => state = State::Graph, + _ => { + // in all other cases we have already bailed out in `matches`. + unreachable!() + } + } + } + _ => {} + }, + Event::Text(ref e) => match state { + State::DefaultForKey => { + graphml + .last_key_set_value((e.unescape()?).to_string(), domain_of_last_key)?; + } + State::DataForNode => { + graphml.last_node_set_data(&last_data_key, (e.unescape()?).to_string())?; + } + State::DataForEdge => { + graphml.last_edge_set_data(&last_data_key, (e.unescape()?).to_string())?; + } + State::DataForGraph => { + graphml.last_graph_set_attribute( + &last_data_key, + (e.unescape()?).to_string(), + )?; + } + _ => {} + }, + Event::Eof => { + break; + } + _ => {} + } + buf.clear(); + } + + Ok(graphml) + } +} + +/// Read a list of graphs from a file in GraphML format. +/// +/// GraphML is a comprehensive and easy-to-use file format for graphs. It consists +/// of a language core to describe the structural properties of a graph and a flexible +/// extension mechanism to add application-specific data. +/// +/// For more information see: +/// http://graphml.graphdrawing.org/ +/// +/// .. note:: +/// +/// This implementation does not support mixed graphs (directed and unidirected edges together), +/// hyperedges, nested graphs, or ports. +/// +/// .. note:: +/// +/// GraphML attributes with `graph` domain are stored in :attr:`~.PyGraph.attrs` field. +/// +/// :param str path: The path of the input file to read. +/// +/// :return: A list of graphs parsed from GraphML file. +/// :rtype: list[Union[PyGraph, PyDiGraph]] +/// :raises RuntimeError: when an error is encountered while parsing the GraphML file. +// #[pyfunction] +// #[pyo3(text_signature = "(path, /)")] +// pub fn read_graphml(py: Python, path: &str) -> PyResult> { +// let graphml = GraphML::from_file(path)?; + +// let mut out = Vec::new(); +// for graph in graphml.graphs { +// out.push(graph.into_py(py)) +// } + +// Ok(out) +// } +pub fn read_graphml(path: &str) -> Result, Error> { + let graphml = GraphML::from_file(path)?; + + let mut out = Vec::new(); + for graph in graphml.graphs { + out.push(graph) + } + + Ok(out) +} diff --git a/src/rust-cli/main.rs b/src/rust-cli/main.rs index 548ded168..c6b57cc41 100644 --- a/src/rust-cli/main.rs +++ b/src/rust-cli/main.rs @@ -4,6 +4,7 @@ use clap::{Parser, Subcommand}; mod debug; mod general; mod graph; +mod graphml; mod image; mod network; mod rpc_call;