├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── rust-toolchain.toml └── src ├── client.rs ├── commands ├── call.rs ├── get.rs ├── get_all.rs ├── introspect.rs ├── list.rs ├── main.rs ├── mod.rs └── set.rs ├── config.rs ├── convert.rs ├── dbus_type.rs ├── introspection.rs ├── main.rs ├── pattern.rs └── test_introspection_doc.xml /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Install dependencies 20 | run: sudo apt-get install libdbus-1-dev 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 10 | 11 | [[package]] 12 | name = "ahash" 13 | version = "0.7.8" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" 16 | dependencies = [ 17 | "getrandom", 18 | "once_cell", 19 | "version_check", 20 | ] 21 | 22 | [[package]] 23 | name = "aho-corasick" 24 | version = "1.1.3" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 27 | dependencies = [ 28 | "memchr", 29 | ] 30 | 31 | [[package]] 32 | name = "alloc-no-stdlib" 33 | version = "2.0.4" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 36 | 37 | [[package]] 38 | name = "alloc-stdlib" 39 | version = "0.2.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 42 | dependencies = [ 43 | "alloc-no-stdlib", 44 | ] 45 | 46 | [[package]] 47 | name = "allocator-api2" 48 | version = "0.2.18" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 51 | 52 | [[package]] 53 | name = "android-tzdata" 54 | version = "0.1.1" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 57 | 58 | [[package]] 59 | name = "android_system_properties" 60 | version = "0.1.5" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 63 | dependencies = [ 64 | "libc", 65 | ] 66 | 67 | [[package]] 68 | name = "arrayvec" 69 | version = "0.7.6" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 72 | 73 | [[package]] 74 | name = "autocfg" 75 | version = "1.4.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 78 | 79 | [[package]] 80 | name = "bindgen" 81 | version = "0.70.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" 84 | dependencies = [ 85 | "bitflags", 86 | "cexpr", 87 | "clang-sys", 88 | "itertools", 89 | "proc-macro2", 90 | "quote", 91 | "regex", 92 | "rustc-hash", 93 | "shlex", 94 | "syn 2.0.91", 95 | ] 96 | 97 | [[package]] 98 | name = "bit-set" 99 | version = "0.8.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" 102 | dependencies = [ 103 | "bit-vec", 104 | ] 105 | 106 | [[package]] 107 | name = "bit-vec" 108 | version = "0.8.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" 111 | 112 | [[package]] 113 | name = "bitflags" 114 | version = "2.6.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 117 | 118 | [[package]] 119 | name = "bitvec" 120 | version = "1.0.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" 123 | dependencies = [ 124 | "funty", 125 | "radium", 126 | "tap", 127 | "wyz", 128 | ] 129 | 130 | [[package]] 131 | name = "borsh" 132 | version = "1.5.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" 135 | dependencies = [ 136 | "borsh-derive", 137 | "cfg_aliases", 138 | ] 139 | 140 | [[package]] 141 | name = "borsh-derive" 142 | version = "1.5.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" 145 | dependencies = [ 146 | "once_cell", 147 | "proc-macro-crate", 148 | "proc-macro2", 149 | "quote", 150 | "syn 2.0.91", 151 | "syn_derive", 152 | ] 153 | 154 | [[package]] 155 | name = "brotli" 156 | version = "6.0.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" 159 | dependencies = [ 160 | "alloc-no-stdlib", 161 | "alloc-stdlib", 162 | "brotli-decompressor", 163 | ] 164 | 165 | [[package]] 166 | name = "brotli-decompressor" 167 | version = "4.0.1" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" 170 | dependencies = [ 171 | "alloc-no-stdlib", 172 | "alloc-stdlib", 173 | ] 174 | 175 | [[package]] 176 | name = "bumpalo" 177 | version = "3.16.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 180 | 181 | [[package]] 182 | name = "byte-unit" 183 | version = "5.1.4" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "33ac19bdf0b2665407c39d82dbc937e951e7e2001609f0fb32edd0af45a2d63e" 186 | dependencies = [ 187 | "rust_decimal", 188 | "serde", 189 | "utf8-width", 190 | ] 191 | 192 | [[package]] 193 | name = "bytecheck" 194 | version = "0.6.12" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" 197 | dependencies = [ 198 | "bytecheck_derive", 199 | "ptr_meta", 200 | "simdutf8", 201 | ] 202 | 203 | [[package]] 204 | name = "bytecheck_derive" 205 | version = "0.6.12" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" 208 | dependencies = [ 209 | "proc-macro2", 210 | "quote", 211 | "syn 1.0.109", 212 | ] 213 | 214 | [[package]] 215 | name = "byteorder" 216 | version = "1.5.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 219 | 220 | [[package]] 221 | name = "bytes" 222 | version = "1.7.2" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" 225 | 226 | [[package]] 227 | name = "cc" 228 | version = "1.1.30" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" 231 | dependencies = [ 232 | "shlex", 233 | ] 234 | 235 | [[package]] 236 | name = "cexpr" 237 | version = "0.6.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 240 | dependencies = [ 241 | "nom", 242 | ] 243 | 244 | [[package]] 245 | name = "cfg-if" 246 | version = "1.0.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 249 | 250 | [[package]] 251 | name = "cfg_aliases" 252 | version = "0.2.1" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 255 | 256 | [[package]] 257 | name = "chrono" 258 | version = "0.4.38" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 261 | dependencies = [ 262 | "android-tzdata", 263 | "iana-time-zone", 264 | "num-traits", 265 | "pure-rust-locales", 266 | "serde", 267 | "windows-targets 0.52.6", 268 | ] 269 | 270 | [[package]] 271 | name = "chrono-humanize" 272 | version = "0.2.3" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "799627e6b4d27827a814e837b9d8a504832086081806d45b1afa34dc982b023b" 275 | dependencies = [ 276 | "chrono", 277 | ] 278 | 279 | [[package]] 280 | name = "clang-sys" 281 | version = "1.8.1" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 284 | dependencies = [ 285 | "glob", 286 | "libc", 287 | "libloading", 288 | ] 289 | 290 | [[package]] 291 | name = "core-foundation-sys" 292 | version = "0.8.7" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 295 | 296 | [[package]] 297 | name = "crc32fast" 298 | version = "1.4.2" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 301 | dependencies = [ 302 | "cfg-if", 303 | ] 304 | 305 | [[package]] 306 | name = "crossbeam-deque" 307 | version = "0.8.5" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 310 | dependencies = [ 311 | "crossbeam-epoch", 312 | "crossbeam-utils", 313 | ] 314 | 315 | [[package]] 316 | name = "crossbeam-epoch" 317 | version = "0.9.18" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 320 | dependencies = [ 321 | "crossbeam-utils", 322 | ] 323 | 324 | [[package]] 325 | name = "crossbeam-utils" 326 | version = "0.8.20" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 329 | 330 | [[package]] 331 | name = "crossterm" 332 | version = "0.28.1" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 335 | dependencies = [ 336 | "bitflags", 337 | "crossterm_winapi", 338 | "mio", 339 | "parking_lot", 340 | "rustix", 341 | "signal-hook", 342 | "signal-hook-mio", 343 | "winapi", 344 | ] 345 | 346 | [[package]] 347 | name = "crossterm_winapi" 348 | version = "0.9.1" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 351 | dependencies = [ 352 | "winapi", 353 | ] 354 | 355 | [[package]] 356 | name = "dbus" 357 | version = "0.9.7" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" 360 | dependencies = [ 361 | "libc", 362 | "libdbus-sys", 363 | "winapi", 364 | ] 365 | 366 | [[package]] 367 | name = "dirs" 368 | version = "5.0.1" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" 371 | dependencies = [ 372 | "dirs-sys", 373 | ] 374 | 375 | [[package]] 376 | name = "dirs-sys" 377 | version = "0.4.1" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 380 | dependencies = [ 381 | "libc", 382 | "option-ext", 383 | "redox_users", 384 | "windows-sys 0.48.0", 385 | ] 386 | 387 | [[package]] 388 | name = "doctest-file" 389 | version = "1.0.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" 392 | 393 | [[package]] 394 | name = "either" 395 | version = "1.13.0" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 398 | 399 | [[package]] 400 | name = "equivalent" 401 | version = "1.0.1" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 404 | 405 | [[package]] 406 | name = "erased-serde" 407 | version = "0.4.5" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" 410 | dependencies = [ 411 | "serde", 412 | "typeid", 413 | ] 414 | 415 | [[package]] 416 | name = "errno" 417 | version = "0.3.9" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 420 | dependencies = [ 421 | "libc", 422 | "windows-sys 0.52.0", 423 | ] 424 | 425 | [[package]] 426 | name = "fancy-regex" 427 | version = "0.14.0" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" 430 | dependencies = [ 431 | "bit-set", 432 | "regex-automata", 433 | "regex-syntax", 434 | ] 435 | 436 | [[package]] 437 | name = "flate2" 438 | version = "1.0.34" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" 441 | dependencies = [ 442 | "crc32fast", 443 | "miniz_oxide", 444 | ] 445 | 446 | [[package]] 447 | name = "foldhash" 448 | version = "0.1.3" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" 451 | 452 | [[package]] 453 | name = "funty" 454 | version = "2.0.0" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 457 | 458 | [[package]] 459 | name = "getrandom" 460 | version = "0.2.15" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 463 | dependencies = [ 464 | "cfg-if", 465 | "libc", 466 | "wasi", 467 | ] 468 | 469 | [[package]] 470 | name = "glob" 471 | version = "0.3.1" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 474 | 475 | [[package]] 476 | name = "hashbrown" 477 | version = "0.12.3" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 480 | dependencies = [ 481 | "ahash", 482 | ] 483 | 484 | [[package]] 485 | name = "hashbrown" 486 | version = "0.15.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 489 | dependencies = [ 490 | "allocator-api2", 491 | "equivalent", 492 | "foldhash", 493 | ] 494 | 495 | [[package]] 496 | name = "heck" 497 | version = "0.5.0" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 500 | 501 | [[package]] 502 | name = "hex" 503 | version = "0.4.3" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 506 | 507 | [[package]] 508 | name = "iana-time-zone" 509 | version = "0.1.61" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 512 | dependencies = [ 513 | "android_system_properties", 514 | "core-foundation-sys", 515 | "iana-time-zone-haiku", 516 | "js-sys", 517 | "wasm-bindgen", 518 | "windows-core 0.52.0", 519 | ] 520 | 521 | [[package]] 522 | name = "iana-time-zone-haiku" 523 | version = "0.1.2" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 526 | dependencies = [ 527 | "cc", 528 | ] 529 | 530 | [[package]] 531 | name = "indexmap" 532 | version = "2.7.0" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 535 | dependencies = [ 536 | "equivalent", 537 | "hashbrown 0.15.0", 538 | ] 539 | 540 | [[package]] 541 | name = "interprocess" 542 | version = "2.2.1" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "d2f4e4a06d42fab3e85ab1b419ad32b09eab58b901d40c57935ff92db3287a13" 545 | dependencies = [ 546 | "doctest-file", 547 | "libc", 548 | "recvmsg", 549 | "widestring", 550 | "windows-sys 0.52.0", 551 | ] 552 | 553 | [[package]] 554 | name = "inventory" 555 | version = "0.3.15" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" 558 | 559 | [[package]] 560 | name = "is_ci" 561 | version = "1.2.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" 564 | 565 | [[package]] 566 | name = "itertools" 567 | version = "0.13.0" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 570 | dependencies = [ 571 | "either", 572 | ] 573 | 574 | [[package]] 575 | name = "itoa" 576 | version = "1.0.11" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 579 | 580 | [[package]] 581 | name = "js-sys" 582 | version = "0.3.72" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 585 | dependencies = [ 586 | "wasm-bindgen", 587 | ] 588 | 589 | [[package]] 590 | name = "libc" 591 | version = "0.2.159" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 594 | 595 | [[package]] 596 | name = "libdbus-sys" 597 | version = "0.2.5" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" 600 | dependencies = [ 601 | "pkg-config", 602 | ] 603 | 604 | [[package]] 605 | name = "libloading" 606 | version = "0.8.5" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" 609 | dependencies = [ 610 | "cfg-if", 611 | "windows-targets 0.52.6", 612 | ] 613 | 614 | [[package]] 615 | name = "libproc" 616 | version = "0.14.10" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "e78a09b56be5adbcad5aa1197371688dc6bb249a26da3bca2011ee2fb987ebfb" 619 | dependencies = [ 620 | "bindgen", 621 | "errno", 622 | "libc", 623 | ] 624 | 625 | [[package]] 626 | name = "libredox" 627 | version = "0.1.3" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 630 | dependencies = [ 631 | "bitflags", 632 | "libc", 633 | ] 634 | 635 | [[package]] 636 | name = "linux-raw-sys" 637 | version = "0.4.14" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 640 | 641 | [[package]] 642 | name = "lock_api" 643 | version = "0.4.12" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 646 | dependencies = [ 647 | "autocfg", 648 | "scopeguard", 649 | ] 650 | 651 | [[package]] 652 | name = "log" 653 | version = "0.4.22" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 656 | 657 | [[package]] 658 | name = "lru" 659 | version = "0.12.5" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 662 | dependencies = [ 663 | "hashbrown 0.15.0", 664 | ] 665 | 666 | [[package]] 667 | name = "lscolors" 668 | version = "0.17.0" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "53304fff6ab1e597661eee37e42ea8c47a146fca280af902bb76bff8a896e523" 671 | dependencies = [ 672 | "nu-ansi-term", 673 | ] 674 | 675 | [[package]] 676 | name = "mach2" 677 | version = "0.4.2" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" 680 | dependencies = [ 681 | "libc", 682 | ] 683 | 684 | [[package]] 685 | name = "memchr" 686 | version = "2.7.4" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 689 | 690 | [[package]] 691 | name = "miette" 692 | version = "7.4.0" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64" 695 | dependencies = [ 696 | "cfg-if", 697 | "miette-derive", 698 | "owo-colors", 699 | "supports-color", 700 | "supports-hyperlinks", 701 | "supports-unicode", 702 | "terminal_size", 703 | "textwrap", 704 | "thiserror 1.0.64", 705 | "unicode-width", 706 | ] 707 | 708 | [[package]] 709 | name = "miette-derive" 710 | version = "7.4.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67" 713 | dependencies = [ 714 | "proc-macro2", 715 | "quote", 716 | "syn 2.0.91", 717 | ] 718 | 719 | [[package]] 720 | name = "minimal-lexical" 721 | version = "0.2.1" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 724 | 725 | [[package]] 726 | name = "miniz_oxide" 727 | version = "0.8.0" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 730 | dependencies = [ 731 | "adler2", 732 | ] 733 | 734 | [[package]] 735 | name = "mio" 736 | version = "1.0.3" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 739 | dependencies = [ 740 | "libc", 741 | "log", 742 | "wasi", 743 | "windows-sys 0.52.0", 744 | ] 745 | 746 | [[package]] 747 | name = "nix" 748 | version = "0.29.0" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 751 | dependencies = [ 752 | "bitflags", 753 | "cfg-if", 754 | "cfg_aliases", 755 | "libc", 756 | ] 757 | 758 | [[package]] 759 | name = "nom" 760 | version = "7.1.3" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 763 | dependencies = [ 764 | "memchr", 765 | "minimal-lexical", 766 | ] 767 | 768 | [[package]] 769 | name = "ntapi" 770 | version = "0.4.1" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" 773 | dependencies = [ 774 | "winapi", 775 | ] 776 | 777 | [[package]] 778 | name = "nu-ansi-term" 779 | version = "0.50.1" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" 782 | dependencies = [ 783 | "windows-sys 0.52.0", 784 | ] 785 | 786 | [[package]] 787 | name = "nu-derive-value" 788 | version = "0.101.0" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "71f7c8ed6ba88a567ec6f7c4cad4a7a8465ab93b8cdaf89d3dc72347a83c2d1f" 791 | dependencies = [ 792 | "heck", 793 | "proc-macro-error", 794 | "proc-macro2", 795 | "quote", 796 | "syn 2.0.91", 797 | ] 798 | 799 | [[package]] 800 | name = "nu-engine" 801 | version = "0.101.0" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "5c6619583ed281060a9ea0a3f4532eea918370c94e703b903065f35e5aa49b14" 804 | dependencies = [ 805 | "log", 806 | "nu-glob", 807 | "nu-path", 808 | "nu-protocol", 809 | "nu-utils", 810 | "terminal_size", 811 | ] 812 | 813 | [[package]] 814 | name = "nu-glob" 815 | version = "0.101.0" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "acd0a9fe69412acdc8501f5ef19031f9cac119d93823cb957b14ddfe1cb97660" 818 | 819 | [[package]] 820 | name = "nu-path" 821 | version = "0.101.0" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "3ccd1bbaf370d79118bd1a807abb07d8d1386751d0ae9266baafa91bd0b5523f" 824 | dependencies = [ 825 | "dirs", 826 | "omnipath", 827 | "pwd", 828 | ] 829 | 830 | [[package]] 831 | name = "nu-plugin" 832 | version = "0.101.0" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "eb600f0a19542803252f93de96871bbafadb2c08d9b16877116182b662bd4bbc" 835 | dependencies = [ 836 | "log", 837 | "nix", 838 | "nu-engine", 839 | "nu-plugin-core", 840 | "nu-plugin-protocol", 841 | "nu-protocol", 842 | "nu-utils", 843 | "thiserror 2.0.9", 844 | ] 845 | 846 | [[package]] 847 | name = "nu-plugin-core" 848 | version = "0.101.0" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "b01fd60b1cccbb58124941ac0cd6f8f9b51e1c13a5390fdc884cb7eba838aa2b" 851 | dependencies = [ 852 | "interprocess", 853 | "log", 854 | "nu-plugin-protocol", 855 | "nu-protocol", 856 | "rmp-serde", 857 | "serde", 858 | "serde_json", 859 | "windows", 860 | ] 861 | 862 | [[package]] 863 | name = "nu-plugin-protocol" 864 | version = "0.101.0" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "1fcbd5c3e36f995c763c284ed39e9d4425dc58ba19e71d1396897529277e2868" 867 | dependencies = [ 868 | "nu-protocol", 869 | "nu-utils", 870 | "rmp-serde", 871 | "semver", 872 | "serde", 873 | "typetag", 874 | ] 875 | 876 | [[package]] 877 | name = "nu-protocol" 878 | version = "0.101.0" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "72f49a395b632530d7f46fd24183c7f42423677f70afb3cb4726e3abfe92273b" 881 | dependencies = [ 882 | "brotli", 883 | "byte-unit", 884 | "bytes", 885 | "chrono", 886 | "chrono-humanize", 887 | "dirs", 888 | "dirs-sys", 889 | "fancy-regex", 890 | "heck", 891 | "indexmap", 892 | "log", 893 | "lru", 894 | "miette", 895 | "nix", 896 | "nu-derive-value", 897 | "nu-path", 898 | "nu-system", 899 | "nu-utils", 900 | "num-format", 901 | "os_pipe", 902 | "rmp-serde", 903 | "serde", 904 | "serde_json", 905 | "thiserror 2.0.9", 906 | "typetag", 907 | "windows-sys 0.48.0", 908 | ] 909 | 910 | [[package]] 911 | name = "nu-system" 912 | version = "0.101.0" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "81182f7e64bd5dd16ab844d8e40f78e389d06d95f5a0c419f4701fb8fc163077" 915 | dependencies = [ 916 | "chrono", 917 | "itertools", 918 | "libc", 919 | "libproc", 920 | "log", 921 | "mach2", 922 | "nix", 923 | "ntapi", 924 | "procfs", 925 | "sysinfo", 926 | "windows", 927 | ] 928 | 929 | [[package]] 930 | name = "nu-utils" 931 | version = "0.101.0" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "53d1468fa8e6e12d9d53c90b44f3d11a37d87502d7a30d145f122341c5b33745" 934 | dependencies = [ 935 | "crossterm", 936 | "crossterm_winapi", 937 | "fancy-regex", 938 | "log", 939 | "lscolors", 940 | "nix", 941 | "num-format", 942 | "serde", 943 | "serde_json", 944 | "strip-ansi-escapes", 945 | "sys-locale", 946 | "unicase", 947 | ] 948 | 949 | [[package]] 950 | name = "nu_plugin_dbus" 951 | version = "0.13.0" 952 | dependencies = [ 953 | "dbus", 954 | "nu-plugin", 955 | "nu-protocol", 956 | "serde", 957 | "serde-xml-rs", 958 | ] 959 | 960 | [[package]] 961 | name = "num-format" 962 | version = "0.4.4" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" 965 | dependencies = [ 966 | "arrayvec", 967 | "itoa", 968 | ] 969 | 970 | [[package]] 971 | name = "num-traits" 972 | version = "0.2.19" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 975 | dependencies = [ 976 | "autocfg", 977 | ] 978 | 979 | [[package]] 980 | name = "omnipath" 981 | version = "0.1.6" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "80adb31078122c880307e9cdfd4e3361e6545c319f9b9dcafcb03acd3b51a575" 984 | 985 | [[package]] 986 | name = "once_cell" 987 | version = "1.20.2" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 990 | 991 | [[package]] 992 | name = "option-ext" 993 | version = "0.2.0" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 996 | 997 | [[package]] 998 | name = "os_pipe" 999 | version = "1.2.1" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" 1002 | dependencies = [ 1003 | "libc", 1004 | "windows-sys 0.59.0", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "owo-colors" 1009 | version = "4.1.0" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" 1012 | 1013 | [[package]] 1014 | name = "parking_lot" 1015 | version = "0.12.3" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1018 | dependencies = [ 1019 | "lock_api", 1020 | "parking_lot_core", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "parking_lot_core" 1025 | version = "0.9.10" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1028 | dependencies = [ 1029 | "cfg-if", 1030 | "libc", 1031 | "redox_syscall", 1032 | "smallvec", 1033 | "windows-targets 0.52.6", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "paste" 1038 | version = "1.0.15" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1041 | 1042 | [[package]] 1043 | name = "pkg-config" 1044 | version = "0.3.31" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 1047 | 1048 | [[package]] 1049 | name = "ppv-lite86" 1050 | version = "0.2.20" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1053 | dependencies = [ 1054 | "zerocopy", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "proc-macro-crate" 1059 | version = "3.2.0" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" 1062 | dependencies = [ 1063 | "toml_edit", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "proc-macro-error" 1068 | version = "1.0.4" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1071 | dependencies = [ 1072 | "proc-macro-error-attr", 1073 | "proc-macro2", 1074 | "quote", 1075 | "version_check", 1076 | ] 1077 | 1078 | [[package]] 1079 | name = "proc-macro-error-attr" 1080 | version = "1.0.4" 1081 | source = "registry+https://github.com/rust-lang/crates.io-index" 1082 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1083 | dependencies = [ 1084 | "proc-macro2", 1085 | "quote", 1086 | "version_check", 1087 | ] 1088 | 1089 | [[package]] 1090 | name = "proc-macro2" 1091 | version = "1.0.92" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 1094 | dependencies = [ 1095 | "unicode-ident", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "procfs" 1100 | version = "0.17.0" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" 1103 | dependencies = [ 1104 | "bitflags", 1105 | "chrono", 1106 | "flate2", 1107 | "hex", 1108 | "procfs-core", 1109 | "rustix", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "procfs-core" 1114 | version = "0.17.0" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" 1117 | dependencies = [ 1118 | "bitflags", 1119 | "chrono", 1120 | "hex", 1121 | ] 1122 | 1123 | [[package]] 1124 | name = "ptr_meta" 1125 | version = "0.1.4" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" 1128 | dependencies = [ 1129 | "ptr_meta_derive", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "ptr_meta_derive" 1134 | version = "0.1.4" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" 1137 | dependencies = [ 1138 | "proc-macro2", 1139 | "quote", 1140 | "syn 1.0.109", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "pure-rust-locales" 1145 | version = "0.8.1" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" 1148 | 1149 | [[package]] 1150 | name = "pwd" 1151 | version = "1.4.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "72c71c0c79b9701efe4e1e4b563b2016dd4ee789eb99badcb09d61ac4b92e4a2" 1154 | dependencies = [ 1155 | "libc", 1156 | "thiserror 1.0.64", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "quote" 1161 | version = "1.0.37" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1164 | dependencies = [ 1165 | "proc-macro2", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "radium" 1170 | version = "0.7.0" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 1173 | 1174 | [[package]] 1175 | name = "rand" 1176 | version = "0.8.5" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1179 | dependencies = [ 1180 | "libc", 1181 | "rand_chacha", 1182 | "rand_core", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "rand_chacha" 1187 | version = "0.3.1" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1190 | dependencies = [ 1191 | "ppv-lite86", 1192 | "rand_core", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "rand_core" 1197 | version = "0.6.4" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1200 | dependencies = [ 1201 | "getrandom", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "rayon" 1206 | version = "1.10.0" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 1209 | dependencies = [ 1210 | "either", 1211 | "rayon-core", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "rayon-core" 1216 | version = "1.12.1" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 1219 | dependencies = [ 1220 | "crossbeam-deque", 1221 | "crossbeam-utils", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "recvmsg" 1226 | version = "1.0.0" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" 1229 | 1230 | [[package]] 1231 | name = "redox_syscall" 1232 | version = "0.5.8" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 1235 | dependencies = [ 1236 | "bitflags", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "redox_users" 1241 | version = "0.4.6" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 1244 | dependencies = [ 1245 | "getrandom", 1246 | "libredox", 1247 | "thiserror 1.0.64", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "regex" 1252 | version = "1.11.0" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" 1255 | dependencies = [ 1256 | "aho-corasick", 1257 | "memchr", 1258 | "regex-automata", 1259 | "regex-syntax", 1260 | ] 1261 | 1262 | [[package]] 1263 | name = "regex-automata" 1264 | version = "0.4.8" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 1267 | dependencies = [ 1268 | "aho-corasick", 1269 | "memchr", 1270 | "regex-syntax", 1271 | ] 1272 | 1273 | [[package]] 1274 | name = "regex-syntax" 1275 | version = "0.8.5" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1278 | 1279 | [[package]] 1280 | name = "rend" 1281 | version = "0.4.2" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" 1284 | dependencies = [ 1285 | "bytecheck", 1286 | ] 1287 | 1288 | [[package]] 1289 | name = "rkyv" 1290 | version = "0.7.45" 1291 | source = "registry+https://github.com/rust-lang/crates.io-index" 1292 | checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" 1293 | dependencies = [ 1294 | "bitvec", 1295 | "bytecheck", 1296 | "bytes", 1297 | "hashbrown 0.12.3", 1298 | "ptr_meta", 1299 | "rend", 1300 | "rkyv_derive", 1301 | "seahash", 1302 | "tinyvec", 1303 | "uuid", 1304 | ] 1305 | 1306 | [[package]] 1307 | name = "rkyv_derive" 1308 | version = "0.7.45" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" 1311 | dependencies = [ 1312 | "proc-macro2", 1313 | "quote", 1314 | "syn 1.0.109", 1315 | ] 1316 | 1317 | [[package]] 1318 | name = "rmp" 1319 | version = "0.8.14" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" 1322 | dependencies = [ 1323 | "byteorder", 1324 | "num-traits", 1325 | "paste", 1326 | ] 1327 | 1328 | [[package]] 1329 | name = "rmp-serde" 1330 | version = "1.3.0" 1331 | source = "registry+https://github.com/rust-lang/crates.io-index" 1332 | checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" 1333 | dependencies = [ 1334 | "byteorder", 1335 | "rmp", 1336 | "serde", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "rust_decimal" 1341 | version = "1.36.0" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" 1344 | dependencies = [ 1345 | "arrayvec", 1346 | "borsh", 1347 | "bytes", 1348 | "num-traits", 1349 | "rand", 1350 | "rkyv", 1351 | "serde", 1352 | "serde_json", 1353 | ] 1354 | 1355 | [[package]] 1356 | name = "rustc-hash" 1357 | version = "1.1.0" 1358 | source = "registry+https://github.com/rust-lang/crates.io-index" 1359 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1360 | 1361 | [[package]] 1362 | name = "rustix" 1363 | version = "0.38.37" 1364 | source = "registry+https://github.com/rust-lang/crates.io-index" 1365 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 1366 | dependencies = [ 1367 | "bitflags", 1368 | "errno", 1369 | "libc", 1370 | "linux-raw-sys", 1371 | "windows-sys 0.52.0", 1372 | ] 1373 | 1374 | [[package]] 1375 | name = "ryu" 1376 | version = "1.0.18" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1379 | 1380 | [[package]] 1381 | name = "scopeguard" 1382 | version = "1.2.0" 1383 | source = "registry+https://github.com/rust-lang/crates.io-index" 1384 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1385 | 1386 | [[package]] 1387 | name = "seahash" 1388 | version = "4.1.0" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" 1391 | 1392 | [[package]] 1393 | name = "semver" 1394 | version = "1.0.23" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 1397 | 1398 | [[package]] 1399 | name = "serde" 1400 | version = "1.0.210" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 1403 | dependencies = [ 1404 | "serde_derive", 1405 | ] 1406 | 1407 | [[package]] 1408 | name = "serde-xml-rs" 1409 | version = "0.6.0" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782" 1412 | dependencies = [ 1413 | "log", 1414 | "serde", 1415 | "thiserror 1.0.64", 1416 | "xml-rs", 1417 | ] 1418 | 1419 | [[package]] 1420 | name = "serde_derive" 1421 | version = "1.0.210" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 1424 | dependencies = [ 1425 | "proc-macro2", 1426 | "quote", 1427 | "syn 2.0.91", 1428 | ] 1429 | 1430 | [[package]] 1431 | name = "serde_json" 1432 | version = "1.0.128" 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" 1434 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 1435 | dependencies = [ 1436 | "itoa", 1437 | "memchr", 1438 | "ryu", 1439 | "serde", 1440 | ] 1441 | 1442 | [[package]] 1443 | name = "shlex" 1444 | version = "1.3.0" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1447 | 1448 | [[package]] 1449 | name = "signal-hook" 1450 | version = "0.3.17" 1451 | source = "registry+https://github.com/rust-lang/crates.io-index" 1452 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1453 | dependencies = [ 1454 | "libc", 1455 | "signal-hook-registry", 1456 | ] 1457 | 1458 | [[package]] 1459 | name = "signal-hook-mio" 1460 | version = "0.2.4" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 1463 | dependencies = [ 1464 | "libc", 1465 | "mio", 1466 | "signal-hook", 1467 | ] 1468 | 1469 | [[package]] 1470 | name = "signal-hook-registry" 1471 | version = "1.4.2" 1472 | source = "registry+https://github.com/rust-lang/crates.io-index" 1473 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1474 | dependencies = [ 1475 | "libc", 1476 | ] 1477 | 1478 | [[package]] 1479 | name = "simdutf8" 1480 | version = "0.1.5" 1481 | source = "registry+https://github.com/rust-lang/crates.io-index" 1482 | checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" 1483 | 1484 | [[package]] 1485 | name = "smallvec" 1486 | version = "1.13.2" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1489 | 1490 | [[package]] 1491 | name = "strip-ansi-escapes" 1492 | version = "0.2.0" 1493 | source = "registry+https://github.com/rust-lang/crates.io-index" 1494 | checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" 1495 | dependencies = [ 1496 | "vte", 1497 | ] 1498 | 1499 | [[package]] 1500 | name = "supports-color" 1501 | version = "3.0.1" 1502 | source = "registry+https://github.com/rust-lang/crates.io-index" 1503 | checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" 1504 | dependencies = [ 1505 | "is_ci", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "supports-hyperlinks" 1510 | version = "3.0.0" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee" 1513 | 1514 | [[package]] 1515 | name = "supports-unicode" 1516 | version = "3.0.0" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" 1519 | 1520 | [[package]] 1521 | name = "syn" 1522 | version = "1.0.109" 1523 | source = "registry+https://github.com/rust-lang/crates.io-index" 1524 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1525 | dependencies = [ 1526 | "proc-macro2", 1527 | "quote", 1528 | "unicode-ident", 1529 | ] 1530 | 1531 | [[package]] 1532 | name = "syn" 1533 | version = "2.0.91" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" 1536 | dependencies = [ 1537 | "proc-macro2", 1538 | "quote", 1539 | "unicode-ident", 1540 | ] 1541 | 1542 | [[package]] 1543 | name = "syn_derive" 1544 | version = "0.1.8" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" 1547 | dependencies = [ 1548 | "proc-macro-error", 1549 | "proc-macro2", 1550 | "quote", 1551 | "syn 2.0.91", 1552 | ] 1553 | 1554 | [[package]] 1555 | name = "sys-locale" 1556 | version = "0.3.1" 1557 | source = "registry+https://github.com/rust-lang/crates.io-index" 1558 | checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" 1559 | dependencies = [ 1560 | "libc", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "sysinfo" 1565 | version = "0.32.0" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "e3b5ae3f4f7d64646c46c4cae4e3f01d1c5d255c7406fdd7c7f999a94e488791" 1568 | dependencies = [ 1569 | "core-foundation-sys", 1570 | "libc", 1571 | "memchr", 1572 | "ntapi", 1573 | "rayon", 1574 | "windows", 1575 | ] 1576 | 1577 | [[package]] 1578 | name = "tap" 1579 | version = "1.0.1" 1580 | source = "registry+https://github.com/rust-lang/crates.io-index" 1581 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 1582 | 1583 | [[package]] 1584 | name = "terminal_size" 1585 | version = "0.4.1" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" 1588 | dependencies = [ 1589 | "rustix", 1590 | "windows-sys 0.59.0", 1591 | ] 1592 | 1593 | [[package]] 1594 | name = "textwrap" 1595 | version = "0.16.1" 1596 | source = "registry+https://github.com/rust-lang/crates.io-index" 1597 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" 1598 | dependencies = [ 1599 | "unicode-linebreak", 1600 | "unicode-width", 1601 | ] 1602 | 1603 | [[package]] 1604 | name = "thiserror" 1605 | version = "1.0.64" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 1608 | dependencies = [ 1609 | "thiserror-impl 1.0.64", 1610 | ] 1611 | 1612 | [[package]] 1613 | name = "thiserror" 1614 | version = "2.0.9" 1615 | source = "registry+https://github.com/rust-lang/crates.io-index" 1616 | checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" 1617 | dependencies = [ 1618 | "thiserror-impl 2.0.9", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "thiserror-impl" 1623 | version = "1.0.64" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 1626 | dependencies = [ 1627 | "proc-macro2", 1628 | "quote", 1629 | "syn 2.0.91", 1630 | ] 1631 | 1632 | [[package]] 1633 | name = "thiserror-impl" 1634 | version = "2.0.9" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" 1637 | dependencies = [ 1638 | "proc-macro2", 1639 | "quote", 1640 | "syn 2.0.91", 1641 | ] 1642 | 1643 | [[package]] 1644 | name = "tinyvec" 1645 | version = "1.8.0" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 1648 | dependencies = [ 1649 | "tinyvec_macros", 1650 | ] 1651 | 1652 | [[package]] 1653 | name = "tinyvec_macros" 1654 | version = "0.1.1" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1657 | 1658 | [[package]] 1659 | name = "toml_datetime" 1660 | version = "0.6.8" 1661 | source = "registry+https://github.com/rust-lang/crates.io-index" 1662 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 1663 | 1664 | [[package]] 1665 | name = "toml_edit" 1666 | version = "0.22.22" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 1669 | dependencies = [ 1670 | "indexmap", 1671 | "toml_datetime", 1672 | "winnow", 1673 | ] 1674 | 1675 | [[package]] 1676 | name = "typeid" 1677 | version = "1.0.2" 1678 | source = "registry+https://github.com/rust-lang/crates.io-index" 1679 | checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" 1680 | 1681 | [[package]] 1682 | name = "typetag" 1683 | version = "0.2.18" 1684 | source = "registry+https://github.com/rust-lang/crates.io-index" 1685 | checksum = "52ba3b6e86ffe0054b2c44f2d86407388b933b16cb0a70eea3929420db1d9bbe" 1686 | dependencies = [ 1687 | "erased-serde", 1688 | "inventory", 1689 | "once_cell", 1690 | "serde", 1691 | "typetag-impl", 1692 | ] 1693 | 1694 | [[package]] 1695 | name = "typetag-impl" 1696 | version = "0.2.18" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "70b20a22c42c8f1cd23ce5e34f165d4d37038f5b663ad20fb6adbdf029172483" 1699 | dependencies = [ 1700 | "proc-macro2", 1701 | "quote", 1702 | "syn 2.0.91", 1703 | ] 1704 | 1705 | [[package]] 1706 | name = "unicase" 1707 | version = "2.8.0" 1708 | source = "registry+https://github.com/rust-lang/crates.io-index" 1709 | checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" 1710 | 1711 | [[package]] 1712 | name = "unicode-ident" 1713 | version = "1.0.13" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1716 | 1717 | [[package]] 1718 | name = "unicode-linebreak" 1719 | version = "0.1.5" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" 1722 | 1723 | [[package]] 1724 | name = "unicode-width" 1725 | version = "0.1.14" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1728 | 1729 | [[package]] 1730 | name = "utf8-width" 1731 | version = "0.1.7" 1732 | source = "registry+https://github.com/rust-lang/crates.io-index" 1733 | checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" 1734 | 1735 | [[package]] 1736 | name = "utf8parse" 1737 | version = "0.2.2" 1738 | source = "registry+https://github.com/rust-lang/crates.io-index" 1739 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1740 | 1741 | [[package]] 1742 | name = "uuid" 1743 | version = "1.11.0" 1744 | source = "registry+https://github.com/rust-lang/crates.io-index" 1745 | checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" 1746 | 1747 | [[package]] 1748 | name = "version_check" 1749 | version = "0.9.5" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1752 | 1753 | [[package]] 1754 | name = "vte" 1755 | version = "0.11.1" 1756 | source = "registry+https://github.com/rust-lang/crates.io-index" 1757 | checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" 1758 | dependencies = [ 1759 | "utf8parse", 1760 | "vte_generate_state_changes", 1761 | ] 1762 | 1763 | [[package]] 1764 | name = "vte_generate_state_changes" 1765 | version = "0.1.2" 1766 | source = "registry+https://github.com/rust-lang/crates.io-index" 1767 | checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" 1768 | dependencies = [ 1769 | "proc-macro2", 1770 | "quote", 1771 | ] 1772 | 1773 | [[package]] 1774 | name = "wasi" 1775 | version = "0.11.0+wasi-snapshot-preview1" 1776 | source = "registry+https://github.com/rust-lang/crates.io-index" 1777 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1778 | 1779 | [[package]] 1780 | name = "wasm-bindgen" 1781 | version = "0.2.95" 1782 | source = "registry+https://github.com/rust-lang/crates.io-index" 1783 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 1784 | dependencies = [ 1785 | "cfg-if", 1786 | "once_cell", 1787 | "wasm-bindgen-macro", 1788 | ] 1789 | 1790 | [[package]] 1791 | name = "wasm-bindgen-backend" 1792 | version = "0.2.95" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 1795 | dependencies = [ 1796 | "bumpalo", 1797 | "log", 1798 | "once_cell", 1799 | "proc-macro2", 1800 | "quote", 1801 | "syn 2.0.91", 1802 | "wasm-bindgen-shared", 1803 | ] 1804 | 1805 | [[package]] 1806 | name = "wasm-bindgen-macro" 1807 | version = "0.2.95" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 1810 | dependencies = [ 1811 | "quote", 1812 | "wasm-bindgen-macro-support", 1813 | ] 1814 | 1815 | [[package]] 1816 | name = "wasm-bindgen-macro-support" 1817 | version = "0.2.95" 1818 | source = "registry+https://github.com/rust-lang/crates.io-index" 1819 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 1820 | dependencies = [ 1821 | "proc-macro2", 1822 | "quote", 1823 | "syn 2.0.91", 1824 | "wasm-bindgen-backend", 1825 | "wasm-bindgen-shared", 1826 | ] 1827 | 1828 | [[package]] 1829 | name = "wasm-bindgen-shared" 1830 | version = "0.2.95" 1831 | source = "registry+https://github.com/rust-lang/crates.io-index" 1832 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 1833 | 1834 | [[package]] 1835 | name = "widestring" 1836 | version = "1.1.0" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" 1839 | 1840 | [[package]] 1841 | name = "winapi" 1842 | version = "0.3.9" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1845 | dependencies = [ 1846 | "winapi-i686-pc-windows-gnu", 1847 | "winapi-x86_64-pc-windows-gnu", 1848 | ] 1849 | 1850 | [[package]] 1851 | name = "winapi-i686-pc-windows-gnu" 1852 | version = "0.4.0" 1853 | source = "registry+https://github.com/rust-lang/crates.io-index" 1854 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1855 | 1856 | [[package]] 1857 | name = "winapi-x86_64-pc-windows-gnu" 1858 | version = "0.4.0" 1859 | source = "registry+https://github.com/rust-lang/crates.io-index" 1860 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1861 | 1862 | [[package]] 1863 | name = "windows" 1864 | version = "0.56.0" 1865 | source = "registry+https://github.com/rust-lang/crates.io-index" 1866 | checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" 1867 | dependencies = [ 1868 | "windows-core 0.56.0", 1869 | "windows-targets 0.52.6", 1870 | ] 1871 | 1872 | [[package]] 1873 | name = "windows-core" 1874 | version = "0.52.0" 1875 | source = "registry+https://github.com/rust-lang/crates.io-index" 1876 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1877 | dependencies = [ 1878 | "windows-targets 0.52.6", 1879 | ] 1880 | 1881 | [[package]] 1882 | name = "windows-core" 1883 | version = "0.56.0" 1884 | source = "registry+https://github.com/rust-lang/crates.io-index" 1885 | checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" 1886 | dependencies = [ 1887 | "windows-implement", 1888 | "windows-interface", 1889 | "windows-result", 1890 | "windows-targets 0.52.6", 1891 | ] 1892 | 1893 | [[package]] 1894 | name = "windows-implement" 1895 | version = "0.56.0" 1896 | source = "registry+https://github.com/rust-lang/crates.io-index" 1897 | checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" 1898 | dependencies = [ 1899 | "proc-macro2", 1900 | "quote", 1901 | "syn 2.0.91", 1902 | ] 1903 | 1904 | [[package]] 1905 | name = "windows-interface" 1906 | version = "0.56.0" 1907 | source = "registry+https://github.com/rust-lang/crates.io-index" 1908 | checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" 1909 | dependencies = [ 1910 | "proc-macro2", 1911 | "quote", 1912 | "syn 2.0.91", 1913 | ] 1914 | 1915 | [[package]] 1916 | name = "windows-result" 1917 | version = "0.1.2" 1918 | source = "registry+https://github.com/rust-lang/crates.io-index" 1919 | checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" 1920 | dependencies = [ 1921 | "windows-targets 0.52.6", 1922 | ] 1923 | 1924 | [[package]] 1925 | name = "windows-sys" 1926 | version = "0.48.0" 1927 | source = "registry+https://github.com/rust-lang/crates.io-index" 1928 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1929 | dependencies = [ 1930 | "windows-targets 0.48.5", 1931 | ] 1932 | 1933 | [[package]] 1934 | name = "windows-sys" 1935 | version = "0.52.0" 1936 | source = "registry+https://github.com/rust-lang/crates.io-index" 1937 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1938 | dependencies = [ 1939 | "windows-targets 0.52.6", 1940 | ] 1941 | 1942 | [[package]] 1943 | name = "windows-sys" 1944 | version = "0.59.0" 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" 1946 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1947 | dependencies = [ 1948 | "windows-targets 0.52.6", 1949 | ] 1950 | 1951 | [[package]] 1952 | name = "windows-targets" 1953 | version = "0.48.5" 1954 | source = "registry+https://github.com/rust-lang/crates.io-index" 1955 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1956 | dependencies = [ 1957 | "windows_aarch64_gnullvm 0.48.5", 1958 | "windows_aarch64_msvc 0.48.5", 1959 | "windows_i686_gnu 0.48.5", 1960 | "windows_i686_msvc 0.48.5", 1961 | "windows_x86_64_gnu 0.48.5", 1962 | "windows_x86_64_gnullvm 0.48.5", 1963 | "windows_x86_64_msvc 0.48.5", 1964 | ] 1965 | 1966 | [[package]] 1967 | name = "windows-targets" 1968 | version = "0.52.6" 1969 | source = "registry+https://github.com/rust-lang/crates.io-index" 1970 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1971 | dependencies = [ 1972 | "windows_aarch64_gnullvm 0.52.6", 1973 | "windows_aarch64_msvc 0.52.6", 1974 | "windows_i686_gnu 0.52.6", 1975 | "windows_i686_gnullvm", 1976 | "windows_i686_msvc 0.52.6", 1977 | "windows_x86_64_gnu 0.52.6", 1978 | "windows_x86_64_gnullvm 0.52.6", 1979 | "windows_x86_64_msvc 0.52.6", 1980 | ] 1981 | 1982 | [[package]] 1983 | name = "windows_aarch64_gnullvm" 1984 | version = "0.48.5" 1985 | source = "registry+https://github.com/rust-lang/crates.io-index" 1986 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1987 | 1988 | [[package]] 1989 | name = "windows_aarch64_gnullvm" 1990 | version = "0.52.6" 1991 | source = "registry+https://github.com/rust-lang/crates.io-index" 1992 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1993 | 1994 | [[package]] 1995 | name = "windows_aarch64_msvc" 1996 | version = "0.48.5" 1997 | source = "registry+https://github.com/rust-lang/crates.io-index" 1998 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1999 | 2000 | [[package]] 2001 | name = "windows_aarch64_msvc" 2002 | version = "0.52.6" 2003 | source = "registry+https://github.com/rust-lang/crates.io-index" 2004 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2005 | 2006 | [[package]] 2007 | name = "windows_i686_gnu" 2008 | version = "0.48.5" 2009 | source = "registry+https://github.com/rust-lang/crates.io-index" 2010 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2011 | 2012 | [[package]] 2013 | name = "windows_i686_gnu" 2014 | version = "0.52.6" 2015 | source = "registry+https://github.com/rust-lang/crates.io-index" 2016 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2017 | 2018 | [[package]] 2019 | name = "windows_i686_gnullvm" 2020 | version = "0.52.6" 2021 | source = "registry+https://github.com/rust-lang/crates.io-index" 2022 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2023 | 2024 | [[package]] 2025 | name = "windows_i686_msvc" 2026 | version = "0.48.5" 2027 | source = "registry+https://github.com/rust-lang/crates.io-index" 2028 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2029 | 2030 | [[package]] 2031 | name = "windows_i686_msvc" 2032 | version = "0.52.6" 2033 | source = "registry+https://github.com/rust-lang/crates.io-index" 2034 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2035 | 2036 | [[package]] 2037 | name = "windows_x86_64_gnu" 2038 | version = "0.48.5" 2039 | source = "registry+https://github.com/rust-lang/crates.io-index" 2040 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2041 | 2042 | [[package]] 2043 | name = "windows_x86_64_gnu" 2044 | version = "0.52.6" 2045 | source = "registry+https://github.com/rust-lang/crates.io-index" 2046 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2047 | 2048 | [[package]] 2049 | name = "windows_x86_64_gnullvm" 2050 | version = "0.48.5" 2051 | source = "registry+https://github.com/rust-lang/crates.io-index" 2052 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2053 | 2054 | [[package]] 2055 | name = "windows_x86_64_gnullvm" 2056 | version = "0.52.6" 2057 | source = "registry+https://github.com/rust-lang/crates.io-index" 2058 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2059 | 2060 | [[package]] 2061 | name = "windows_x86_64_msvc" 2062 | version = "0.48.5" 2063 | source = "registry+https://github.com/rust-lang/crates.io-index" 2064 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2065 | 2066 | [[package]] 2067 | name = "windows_x86_64_msvc" 2068 | version = "0.52.6" 2069 | source = "registry+https://github.com/rust-lang/crates.io-index" 2070 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2071 | 2072 | [[package]] 2073 | name = "winnow" 2074 | version = "0.6.20" 2075 | source = "registry+https://github.com/rust-lang/crates.io-index" 2076 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 2077 | dependencies = [ 2078 | "memchr", 2079 | ] 2080 | 2081 | [[package]] 2082 | name = "wyz" 2083 | version = "0.5.1" 2084 | source = "registry+https://github.com/rust-lang/crates.io-index" 2085 | checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" 2086 | dependencies = [ 2087 | "tap", 2088 | ] 2089 | 2090 | [[package]] 2091 | name = "xml-rs" 2092 | version = "0.8.22" 2093 | source = "registry+https://github.com/rust-lang/crates.io-index" 2094 | checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" 2095 | 2096 | [[package]] 2097 | name = "zerocopy" 2098 | version = "0.7.35" 2099 | source = "registry+https://github.com/rust-lang/crates.io-index" 2100 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2101 | dependencies = [ 2102 | "byteorder", 2103 | "zerocopy-derive", 2104 | ] 2105 | 2106 | [[package]] 2107 | name = "zerocopy-derive" 2108 | version = "0.7.35" 2109 | source = "registry+https://github.com/rust-lang/crates.io-index" 2110 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2111 | dependencies = [ 2112 | "proc-macro2", 2113 | "quote", 2114 | "syn 2.0.91", 2115 | ] 2116 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nu_plugin_dbus" 3 | version = "0.14.0" 4 | edition = "2021" 5 | 6 | description = "Nushell plugin for communicating with D-Bus" 7 | authors = ["Devyn Cairns "] 8 | license = "MIT" 9 | keywords = ["plugin", "dbus", "nu"] 10 | categories = ["command-line-utilities"] 11 | repository = "https://github.com/devyn/nu_plugin_dbus" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | dbus = "0.9.7" 17 | nu-plugin = "0.101.0" 18 | nu-protocol = { version = "0.101.0", features = ["plugin"] } 19 | serde = { version = "1.0.196", features = ["derive"] } 20 | serde-xml-rs = "0.6.0" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Devyn Cairns 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nu_plugin_dbus 2 | 3 | [Nushell](https://nushell.sh/) plugin for interacting with [D-Bus](https://dbus.freedesktop.org/) 4 | 5 | With the commands provided by this plugin, you can interact with many of the desktop-oriented 6 | systems on UNIX-like systems that use D-Bus, including Linux and FreeBSD. You can control media 7 | players, on-screen displays, power policies, and even administer services. 8 | 9 | Nushell provides a particularly nice environment for interacting with D-Bus, as both support typed 10 | structured data, and interacting with this on a traditional UNIX command line with tools like 11 | `dbus-send` and `busctl` is cumbersome and tricky to automate. 12 | 13 | This plugin automatically determines the correct input types through D-Bus introspection when 14 | available, unlike either of the aforementioned tools, making it easier to interact with objects on 15 | the bus without having to implement boilerplate from documentation. 16 | 17 | ## Install with Cargo 18 | 19 | From within nushell: 20 | 21 | ```nushell 22 | cargo install --locked nu_plugin_dbus 23 | plugin add ~/.cargo/bin/nu_plugin_dbus 24 | plugin use dbus # or restart nu 25 | ``` 26 | 27 | ## Usage 28 | 29 | Commands for interacting with D-Bus 30 | 31 | Search terms: dbus 32 | 33 | Usage: 34 | > dbus 35 | 36 | Subcommands: 37 | dbus call - Call a method and get its response 38 | dbus get - Get a D-Bus property 39 | dbus get-all - Get all D-Bus properties for the given object 40 | dbus introspect - Introspect a D-Bus object 41 | dbus list - List all available connection names on the bus 42 | dbus set - Set a D-Bus property 43 | 44 | Flags: 45 | -h, --help - Display the help message for this command 46 | 47 | # `dbus call` 48 | 49 | Call a method and get its response 50 | 51 | Returns an array if the method call returns more than one value. 52 | 53 | Search terms: dbus 54 | 55 | Usage: 56 | > dbus call {flags} ...(args) 57 | 58 | Flags: 59 | -h, --help - Display the help message for this command 60 | --session - Send to the session message bus (default) 61 | --system - Send to the system message bus 62 | --started - Send to the bus that started this process, if applicable 63 | --bus - Send to the bus server at the given address 64 | --peer - Send to a non-bus D-Bus server at the given address. Will not call the Hello method on initialization. 65 | --timeout - How long to wait for a response 66 | --signature - Signature of the arguments to send, in D-Bus format. 67 | If not provided, they will be determined from introspection. 68 | If --no-introspect is specified and this is not provided, they will be guessed (poorly) 69 | --no-flatten - Always return a list of all return values 70 | --no-introspect - Don't use introspection to determine the correct argument signature 71 | --dest (required parameter) - The name of the connection to send the method to 72 | 73 | Parameters: 74 | object : The path to the object to call the method on 75 | interface : The name of the interface the method belongs to 76 | method : The name of the method to send 77 | ...args : Arguments to send with the method call 78 | 79 | Input/output types: 80 | ╭───┬─────────┬────────╮ 81 | │ # │ input │ output │ 82 | ├───┼─────────┼────────┤ 83 | │ 0 │ nothing │ any │ 84 | ╰───┴─────────┴────────╯ 85 | 86 | Examples: 87 | Ping the D-Bus server itself 88 | > dbus call --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.Peer Ping 89 | 90 | Show a notification on the desktop for 5 seconds 91 | > dbus call --dest=org.freedesktop.Notifications /org/freedesktop/Notifications org.freedesktop.Notifications Notify "Floppy disks" 0 "media-floppy" "Rarely seen" "But sometimes still used" [] {} 5000 92 | 93 | # `dbus get` 94 | 95 | Get a D-Bus property 96 | 97 | Search terms: dbus 98 | 99 | Usage: 100 | > dbus get {flags} 101 | 102 | Flags: 103 | -h, --help - Display the help message for this command 104 | --session - Send to the session message bus (default) 105 | --system - Send to the system message bus 106 | --started - Send to the bus that started this process, if applicable 107 | --bus - Send to the bus server at the given address 108 | --peer - Send to a non-bus D-Bus server at the given address. Will not call the Hello method on initialization. 109 | --timeout - How long to wait for a response 110 | --dest (required parameter) - The name of the connection to read the property from 111 | 112 | Parameters: 113 | object : The path to the object to read the property from 114 | interface : The name of the interface the property belongs to 115 | property : The name of the property to read 116 | 117 | Input/output types: 118 | ╭───┬─────────┬────────╮ 119 | │ # │ input │ output │ 120 | ├───┼─────────┼────────┤ 121 | │ 0 │ nothing │ any │ 122 | ╰───┴─────────┴────────╯ 123 | 124 | Examples: 125 | Get the currently playing song in Spotify 126 | > dbus get --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player Metadata 127 | ╭──────────────┬───────────────────────────────────────────────────────╮ 128 | │ xesam:title │ Birdie │ 129 | │ xesam:artist │ [list 1 item] │ 130 | │ xesam:album │ Love Your Love │ 131 | │ xesam:url │ https://open.spotify.com/track/51748BvzeeMs4PIdPuyZmv │ 132 | ╰──────────────┴───────────────────────────────────────────────────────╯ 133 | 134 | # `dbus get-all` 135 | 136 | Get all D-Bus properties for the given object 137 | 138 | Search terms: dbus 139 | 140 | Usage: 141 | > dbus get-all {flags} 142 | 143 | Flags: 144 | -h, --help - Display the help message for this command 145 | --session - Send to the session message bus (default) 146 | --system - Send to the system message bus 147 | --started - Send to the bus that started this process, if applicable 148 | --bus - Send to the bus server at the given address 149 | --peer - Send to a non-bus D-Bus server at the given address. Will not call the Hello method on initialization. 150 | --timeout - How long to wait for a response 151 | --dest (required parameter) - The name of the connection to read the property from 152 | 153 | Parameters: 154 | object : The path to the object to read the property from 155 | interface : The name of the interface the property belongs to 156 | 157 | Input/output types: 158 | ╭───┬─────────┬────────╮ 159 | │ # │ input │ output │ 160 | ├───┼─────────┼────────┤ 161 | │ 0 │ nothing │ record │ 162 | ╰───┴─────────┴────────╯ 163 | 164 | Examples: 165 | Get the current player state of Spotify 166 | > dbus get-all --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player 167 | ╭────────────────┬────────╮ 168 | │ CanPlay │ true │ 169 | │ Volume │ 0.43 │ 170 | │ PlaybackStatus │ Paused │ 171 | ╰────────────────┴────────╯ 172 | 173 | # `dbus introspect` 174 | 175 | Introspect a D-Bus object 176 | 177 | Returns information about available nodes, interfaces, methods, signals, and properties on the given object path 178 | 179 | Search terms: dbus 180 | 181 | Usage: 182 | > dbus introspect {flags} 183 | 184 | Flags: 185 | -h, --help - Display the help message for this command 186 | --session - Send to the session message bus (default) 187 | --system - Send to the system message bus 188 | --started - Send to the bus that started this process, if applicable 189 | --bus - Send to the bus server at the given address 190 | --peer - Send to a non-bus D-Bus server at the given address. Will not call the Hello method on initialization. 191 | --timeout - How long to wait for a response 192 | --dest (required parameter) - The name of the connection that owns the object 193 | 194 | Parameters: 195 | object : The path to the object to introspect 196 | 197 | Input/output types: 198 | ╭───┬─────────┬────────╮ 199 | │ # │ input │ output │ 200 | ├───┼─────────┼────────┤ 201 | │ 0 │ nothing │ record │ 202 | ╰───┴─────────┴────────╯ 203 | 204 | Examples: 205 | Look at the MPRIS2 interfaces exposed by Spotify 206 | > dbus introspect --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 | explore 207 | 208 | Get methods exposed by KDE Plasma's on-screen display service 209 | > dbus introspect --dest=org.kde.plasmashell /org/kde/osdService | get interfaces | where name == org.kde.osdService | get 0.methods 210 | 211 | List objects exposed by KWin 212 | > dbus introspect --dest=org.kde.KWin / | get children | select name 213 | 214 | # `dbus list` 215 | 216 | List all available connection names on the bus 217 | 218 | These can be used as arguments for --dest on any of the other commands. 219 | 220 | Search terms: dbus 221 | 222 | Usage: 223 | > dbus list {flags} (pattern) 224 | 225 | Flags: 226 | -h, --help - Display the help message for this command 227 | --session - Send to the session message bus (default) 228 | --system - Send to the system message bus 229 | --started - Send to the bus that started this process, if applicable 230 | --bus - Send to the bus server at the given address 231 | --peer - Send to a non-bus D-Bus server at the given address. Will not call the Hello method on initialization. 232 | --timeout - How long to wait for a response 233 | 234 | Parameters: 235 | pattern : An optional glob-like pattern to filter the result by (optional) 236 | 237 | Input/output types: 238 | ╭───┬─────────┬──────────────╮ 239 | │ # │ input │ output │ 240 | ├───┼─────────┼──────────────┤ 241 | │ 0 │ nothing │ list │ 242 | ╰───┴─────────┴──────────────╯ 243 | 244 | Examples: 245 | List all names available on the bus 246 | > dbus list 247 | 248 | List top-level freedesktop.org names on the bus (e.g. matches `org.freedesktop.PowerManagement`, but not `org.freedesktop.Management.Inhibit`) 249 | > dbus list org.freedesktop.* 250 | ╭───┬───────────────────────────────╮ 251 | │ 0 │ org.freedesktop.DBus │ 252 | │ 1 │ org.freedesktop.Flatpak │ 253 | │ 2 │ org.freedesktop.Notifications │ 254 | ╰───┴───────────────────────────────╯ 255 | 256 | List all MPRIS2 media players on the bus 257 | > dbus list org.mpris.MediaPlayer2.** 258 | ╭───┬────────────────────────────────────────────────╮ 259 | │ 0 │ org.mpris.MediaPlayer2.spotify │ 260 | │ 1 │ org.mpris.MediaPlayer2.kdeconnect.mpris_000001 │ 261 | ╰───┴────────────────────────────────────────────────╯ 262 | 263 | # `dbus set` 264 | 265 | Set a D-Bus property 266 | 267 | Search terms: dbus 268 | 269 | Usage: 270 | > dbus set {flags} 271 | 272 | Flags: 273 | -h, --help - Display the help message for this command 274 | --session - Send to the session message bus (default) 275 | --system - Send to the system message bus 276 | --started - Send to the bus that started this process, if applicable 277 | --bus - Send to the bus server at the given address 278 | --peer - Send to a non-bus D-Bus server at the given address. Will not call the Hello method on initialization. 279 | --timeout - How long to wait for a response 280 | --signature - Signature of the value to set, in D-Bus format. 281 | If not provided, it will be determined from introspection. 282 | If --no-introspect is specified and this is not provided, it will be guessed (poorly) 283 | --dest (required parameter) - The name of the connection to write the property on 284 | 285 | Parameters: 286 | object : The path to the object to write the property on 287 | interface : The name of the interface the property belongs to 288 | property : The name of the property to write 289 | value : The value to write to the property 290 | 291 | Input/output types: 292 | ╭───┬─────────┬─────────╮ 293 | │ # │ input │ output │ 294 | ├───┼─────────┼─────────┤ 295 | │ 0 │ nothing │ nothing │ 296 | ╰───┴─────────┴─────────╯ 297 | 298 | Examples: 299 | Set the volume of Spotify to 50% 300 | > dbus set --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player Volume 0.5 301 | 302 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | # This should be in sync with nushell 2 | [toolchain] 3 | profile = "default" 4 | channel = "1.81.0" 5 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use dbus::{ 2 | arg::messageitem::MessageItem, 3 | channel::{BusType, Channel}, 4 | Message, 5 | }; 6 | use nu_protocol::{LabeledError, Spanned, Value}; 7 | 8 | use crate::{ 9 | config::{DbusBusChoice, DbusClientConfig}, 10 | convert::to_message_item, 11 | dbus_type::DbusType, 12 | introspection::Node, 13 | pattern::Pattern, 14 | }; 15 | 16 | /// Executes D-Bus actions on a connection, handling nushell types 17 | pub struct DbusClient { 18 | config: DbusClientConfig, 19 | conn: Channel, 20 | } 21 | 22 | // Convenience macros for error handling 23 | macro_rules! validate_with { 24 | ($type:ty, $spanned:expr) => { 25 | <$type>::new(&$spanned.item) 26 | .map_err(|msg| LabeledError::new("Invalid argument").with_label(msg, $spanned.span)) 27 | }; 28 | } 29 | 30 | impl DbusClient { 31 | pub fn new(config: DbusClientConfig) -> Result { 32 | // Try to connect to the correct D-Bus destination, as specified in the config 33 | let channel = match &config.bus_choice.item { 34 | DbusBusChoice::Session => Channel::get_private(BusType::Session), 35 | DbusBusChoice::System => Channel::get_private(BusType::System), 36 | DbusBusChoice::Started => Channel::get_private(BusType::Starter), 37 | DbusBusChoice::Peer(address) => Channel::open_private(address), 38 | DbusBusChoice::Bus(address) => Channel::open_private(address).and_then(|mut ch| { 39 | ch.register()?; 40 | Ok(ch) 41 | }), 42 | } 43 | .map_err(|err| { 44 | LabeledError::new(err.to_string()).with_label( 45 | "while connecting to D-Bus as specified here", 46 | config.bus_choice.span, 47 | ) 48 | })?; 49 | Ok(DbusClient { 50 | config, 51 | conn: channel, 52 | }) 53 | } 54 | 55 | fn error(&self, err: impl std::fmt::Display, msg: impl std::fmt::Display) -> LabeledError { 56 | LabeledError::new(err.to_string()).with_label(msg.to_string(), self.config.span) 57 | } 58 | 59 | /// Introspect a D-Bus object 60 | pub fn introspect( 61 | &self, 62 | dest: &Spanned, 63 | object: &Spanned, 64 | ) -> Result { 65 | let context = "while introspecting a D-Bus method"; 66 | let valid_dest = validate_with!(dbus::strings::BusName, dest)?; 67 | let valid_object = validate_with!(dbus::strings::Path, object)?; 68 | 69 | // Create the introspection method call 70 | let message = Message::new_method_call( 71 | valid_dest, 72 | valid_object, 73 | "org.freedesktop.DBus.Introspectable", 74 | "Introspect", 75 | ) 76 | .map_err(|err| self.error(err, context))?; 77 | 78 | // Send and get the response 79 | let resp = self 80 | .conn 81 | .send_with_reply_and_block(message, self.config.timeout.item) 82 | .map_err(|err| self.error(err, context))?; 83 | 84 | // Parse it to a Node 85 | let xml: &str = resp 86 | .get1() 87 | .ok_or_else(|| self.error("Introspect method returned the wrong type", context))?; 88 | 89 | Node::from_xml(xml).map_err(|err| self.error(err, context)) 90 | } 91 | 92 | /// Try to use introspection to get the signature of a method 93 | fn get_method_signature_by_introspection( 94 | &self, 95 | dest: &Spanned, 96 | object: &Spanned, 97 | interface: &Spanned, 98 | method: &Spanned, 99 | ) -> Result, LabeledError> { 100 | let node = self.introspect(dest, object)?; 101 | 102 | if let Some(sig) = node.get_method_args_signature(&interface.item, &method.item) { 103 | DbusType::parse_all(&sig).map_err(|err| { 104 | LabeledError::new(format!( 105 | "while getting interface {:?} method {:?} signature: {}", 106 | interface.item, method.item, err 107 | )) 108 | .with_label( 109 | "try running with --no-introspect or --signature", 110 | self.config.span, 111 | ) 112 | }) 113 | } else { 114 | Err(LabeledError::new(format!( 115 | "Method {:?} not found on {:?}", 116 | method.item, interface.item 117 | )) 118 | .with_label("check that this method/interface is correct", method.span)) 119 | } 120 | } 121 | 122 | /// Try to use introspection to get the signature of a property 123 | fn get_property_signature_by_introspection( 124 | &self, 125 | dest: &Spanned, 126 | object: &Spanned, 127 | interface: &Spanned, 128 | property: &Spanned, 129 | ) -> Result, LabeledError> { 130 | let node = self.introspect(dest, object)?; 131 | 132 | if let Some(sig) = node.get_property_signature(&interface.item, &property.item) { 133 | DbusType::parse_all(sig).map_err(|err| { 134 | LabeledError::new(format!( 135 | "while getting interface {:?} property {:?} signature: {}", 136 | interface.item, property.item, err 137 | )) 138 | .with_label( 139 | "try running with --no-introspect or --signature", 140 | self.config.span, 141 | ) 142 | }) 143 | } else { 144 | Err(LabeledError::new(format!( 145 | "Property {:?} not found on {:?}", 146 | property.item, interface.item 147 | )) 148 | .with_label( 149 | "check that this property or interface is correct", 150 | property.span, 151 | )) 152 | } 153 | } 154 | 155 | /// Call a D-Bus method and wait for the response 156 | pub fn call( 157 | &self, 158 | dest: &Spanned, 159 | object: &Spanned, 160 | interface: &Spanned, 161 | method: &Spanned, 162 | signature: Option<&Spanned>, 163 | args: &[Value], 164 | ) -> Result, LabeledError> { 165 | let context = "while calling a D-Bus method"; 166 | 167 | // Validate inputs before sending to the dbus lib so we don't panic 168 | let valid_dest = validate_with!(dbus::strings::BusName, dest)?; 169 | let valid_object = validate_with!(dbus::strings::Path, object)?; 170 | let valid_interface = validate_with!(dbus::strings::Interface, interface)?; 171 | let valid_method = validate_with!(dbus::strings::Member, method)?; 172 | 173 | // Parse the signature 174 | let mut valid_signature = signature 175 | .map(|s| { 176 | DbusType::parse_all(&s.item).map_err(|err| { 177 | LabeledError::new(err).with_label("in signature specified here", s.span) 178 | }) 179 | }) 180 | .transpose()?; 181 | 182 | // If not provided, try introspection (unless disabled) 183 | if valid_signature.is_none() && self.config.introspect { 184 | match self.get_method_signature_by_introspection(dest, object, interface, method) { 185 | Ok(sig) => { 186 | valid_signature = Some(sig); 187 | } 188 | Err(err) => { 189 | eprintln!( 190 | "Warning: D-Bus introspection failed on {:?}. \ 191 | Use `--no-introspect` or pass `--signature` to silence this warning. \ 192 | Cause: {}", 193 | object.item, err 194 | ); 195 | } 196 | } 197 | } 198 | 199 | if let Some(sig) = &valid_signature { 200 | if sig.len() != args.len() { 201 | self.error( 202 | format!("expected {} arguments, got {}", sig.len(), args.len()), 203 | context, 204 | ); 205 | } 206 | } 207 | 208 | // Construct the method call message 209 | let mut message = 210 | Message::new_method_call(valid_dest, valid_object, valid_interface, valid_method) 211 | .map_err(|err| self.error(err, context))?; 212 | 213 | // Convert the args to message items 214 | let sigs_iter = valid_signature 215 | .iter() 216 | .flatten() 217 | .map(Some) 218 | .chain(std::iter::repeat(None)); 219 | for (val, sig) in args.iter().zip(sigs_iter) { 220 | message = message.append1(to_message_item(val, sig)?); 221 | } 222 | 223 | // Send it on the channel and get the response 224 | let resp = self 225 | .conn 226 | .send_with_reply_and_block(message, self.config.timeout.item) 227 | .map_err(|err| self.error(err, context))?; 228 | 229 | crate::convert::from_message(&resp, self.config.span) 230 | .map_err(|err| self.error(err, context)) 231 | } 232 | 233 | /// Get a D-Bus property from the given object 234 | pub fn get( 235 | &self, 236 | dest: &Spanned, 237 | object: &Spanned, 238 | interface: &Spanned, 239 | property: &Spanned, 240 | ) -> Result { 241 | let interface_val = Value::string(&interface.item, interface.span); 242 | let property_val = Value::string(&property.item, property.span); 243 | 244 | self.call( 245 | dest, 246 | object, 247 | &Spanned { 248 | item: "org.freedesktop.DBus.Properties".into(), 249 | span: self.config.span, 250 | }, 251 | &Spanned { 252 | item: "Get".into(), 253 | span: self.config.span, 254 | }, 255 | Some(&Spanned { 256 | item: "ss".into(), 257 | span: self.config.span, 258 | }), 259 | &[interface_val, property_val], 260 | ) 261 | .map(|val| val.into_iter().nth(0).unwrap_or_default()) 262 | } 263 | 264 | /// Get all D-Bus properties from the given object 265 | pub fn get_all( 266 | &self, 267 | dest: &Spanned, 268 | object: &Spanned, 269 | interface: &Spanned, 270 | ) -> Result { 271 | let interface_val = Value::string(&interface.item, interface.span); 272 | 273 | self.call( 274 | dest, 275 | object, 276 | &Spanned { 277 | item: "org.freedesktop.DBus.Properties".into(), 278 | span: self.config.span, 279 | }, 280 | &Spanned { 281 | item: "GetAll".into(), 282 | span: self.config.span, 283 | }, 284 | Some(&Spanned { 285 | item: "s".into(), 286 | span: self.config.span, 287 | }), 288 | &[interface_val], 289 | ) 290 | .map(|val| val.into_iter().nth(0).unwrap_or_default()) 291 | } 292 | 293 | /// Set a D-Bus property on the given object 294 | pub fn set( 295 | &self, 296 | dest: &Spanned, 297 | object: &Spanned, 298 | interface: &Spanned, 299 | property: &Spanned, 300 | signature: Option<&Spanned>, 301 | value: &Value, 302 | ) -> Result<(), LabeledError> { 303 | let context = "while setting a D-Bus property"; 304 | 305 | // Validate inputs before sending to the dbus lib so we don't panic 306 | let valid_dest = validate_with!(dbus::strings::BusName, dest)?; 307 | let valid_object = validate_with!(dbus::strings::Path, object)?; 308 | 309 | // Parse the signature 310 | let mut valid_signature = signature 311 | .map(|s| { 312 | DbusType::parse_all(&s.item).map_err(|err| { 313 | LabeledError::new(err).with_label("in signature specified here", s.span) 314 | }) 315 | }) 316 | .transpose()?; 317 | 318 | // If not provided, try introspection (unless disabled) 319 | if valid_signature.is_none() && self.config.introspect { 320 | match self.get_property_signature_by_introspection(dest, object, interface, property) { 321 | Ok(sig) => { 322 | valid_signature = Some(sig); 323 | } 324 | Err(err) => { 325 | eprintln!( 326 | "Warning: D-Bus introspection failed on {:?}. \ 327 | Use `--no-introspect` or pass `--signature` to silence this warning. \ 328 | Cause: {}", 329 | object.item, err 330 | ); 331 | } 332 | } 333 | } 334 | 335 | if let Some(sig) = &valid_signature { 336 | if sig.len() != 1 { 337 | self.error( 338 | format!( 339 | "expected single object signature, but there are {}", 340 | sig.len() 341 | ), 342 | context, 343 | ); 344 | } 345 | } 346 | 347 | // Construct the method call message 348 | let message = Message::new_method_call( 349 | valid_dest, 350 | valid_object, 351 | "org.freedesktop.DBus.Properties", 352 | "Set", 353 | ) 354 | .map_err(|err| self.error(err, context))? 355 | .append2(&interface.item, &property.item) 356 | .append1( 357 | // Box it in a variant as required for property setting 358 | MessageItem::Variant(Box::new(to_message_item( 359 | value, 360 | valid_signature.as_ref().map(|s| &s[0]), 361 | )?)), 362 | ); 363 | 364 | // Send it on the channel and get the response 365 | self.conn 366 | .send_with_reply_and_block(message, self.config.timeout.item) 367 | .map_err(|err| self.error(err, context))?; 368 | 369 | Ok(()) 370 | } 371 | 372 | pub fn list(&self, pattern: Option<&Pattern>) -> Result, LabeledError> { 373 | let context = "while listing D-Bus connection names"; 374 | 375 | let message = Message::new_method_call( 376 | "org.freedesktop.DBus", 377 | "/org/freedesktop/DBus", 378 | "org.freedesktop.DBus", 379 | "ListNames", 380 | ) 381 | .map_err(|err| self.error(err, context))?; 382 | 383 | self.conn 384 | .send_with_reply_and_block(message, self.config.timeout.item) 385 | .map_err(|err| self.error(err, context)) 386 | .and_then(|reply| reply.read1().map_err(|err| self.error(err, context))) 387 | .map(|names: Vec| { 388 | // Filter the names by the pattern 389 | if let Some(pattern) = pattern { 390 | names 391 | .into_iter() 392 | .filter(|name| pattern.is_match(name)) 393 | .collect() 394 | } else { 395 | names 396 | } 397 | }) 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/commands/call.rs: -------------------------------------------------------------------------------- 1 | use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 | use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 | 4 | use crate::{client::DbusClient, config::DbusClientConfig, DbusSignatureUtilExt}; 5 | 6 | pub struct Call; 7 | 8 | impl SimplePluginCommand for Call { 9 | type Plugin = crate::NuPluginDbus; 10 | 11 | fn name(&self) -> &str { 12 | "dbus call" 13 | } 14 | 15 | fn signature(&self) -> Signature { 16 | Signature::build(self.name()) 17 | .dbus_command() 18 | .accepts_dbus_client_options() 19 | .accepts_timeout() 20 | .input_output_type(Type::Nothing, Type::Any) 21 | .named( 22 | "signature", 23 | SyntaxShape::String, 24 | "Signature of the arguments to send, in D-Bus format.\n \ 25 | If not provided, they will be determined from introspection.\n \ 26 | If --no-introspect is specified and this is not provided, they will \ 27 | be guessed (poorly)", 28 | None, 29 | ) 30 | .switch( 31 | "no-flatten", 32 | "Always return a list of all return values", 33 | None, 34 | ) 35 | .switch( 36 | "no-introspect", 37 | "Don't use introspection to determine the correct argument signature", 38 | None, 39 | ) 40 | .required_named( 41 | "dest", 42 | SyntaxShape::String, 43 | "The name of the connection to send the method to", 44 | None, 45 | ) 46 | .required( 47 | "object", 48 | SyntaxShape::String, 49 | "The path to the object to call the method on", 50 | ) 51 | .required( 52 | "interface", 53 | SyntaxShape::String, 54 | "The name of the interface the method belongs to", 55 | ) 56 | .required( 57 | "method", 58 | SyntaxShape::String, 59 | "The name of the method to send", 60 | ) 61 | .rest( 62 | "args", 63 | SyntaxShape::Any, 64 | "Arguments to send with the method call", 65 | ) 66 | } 67 | 68 | fn description(&self) -> &str { 69 | "Call a method and get its response" 70 | } 71 | 72 | fn extra_description(&self) -> &str { 73 | "Returns an array if the method call returns more than one value." 74 | } 75 | 76 | fn search_terms(&self) -> Vec<&str> { 77 | vec!["dbus"] 78 | } 79 | 80 | fn examples(&self) -> Vec { 81 | vec![ 82 | Example { 83 | example: "dbus call --dest=org.freedesktop.DBus \ 84 | /org/freedesktop/DBus org.freedesktop.DBus.Peer Ping", 85 | description: "Ping the D-Bus server itself", 86 | result: None, 87 | }, 88 | Example { 89 | example: "dbus call --dest=org.freedesktop.Notifications \ 90 | /org/freedesktop/Notifications org.freedesktop.Notifications \ 91 | Notify \"Floppy disks\" 0 \"media-floppy\" \"Rarely seen\" \ 92 | \"But sometimes still used\" [] {} 5000", 93 | description: "Show a notification on the desktop for 5 seconds", 94 | result: None, 95 | }, 96 | ] 97 | } 98 | 99 | fn run( 100 | &self, 101 | _plugin: &Self::Plugin, 102 | _engine: &EngineInterface, 103 | call: &EvaluatedCall, 104 | _input: &Value, 105 | ) -> Result { 106 | let config = DbusClientConfig::try_from(call)?; 107 | let dbus = DbusClient::new(config)?; 108 | let values = dbus.call( 109 | &call.get_flag("dest")?.unwrap(), 110 | &call.req(0)?, 111 | &call.req(1)?, 112 | &call.req(2)?, 113 | call.get_flag("signature")?.as_ref(), 114 | &call.positional[3..], 115 | )?; 116 | 117 | let flatten = !call.get_flag::("no-flatten")?.unwrap_or(false); 118 | 119 | // Make the output easier to deal with by returning a list only if there are multiple return 120 | // values (not so common) 121 | match values.len() { 122 | 0 if flatten => Ok(Value::nothing(call.head)), 123 | 1 if flatten => Ok(values.into_iter().nth(0).unwrap()), 124 | _ => Ok(Value::list(values, call.head)), 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/commands/get.rs: -------------------------------------------------------------------------------- 1 | use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 | use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 | 4 | use crate::{client::DbusClient, config::DbusClientConfig, DbusSignatureUtilExt}; 5 | 6 | pub struct Get; 7 | 8 | impl SimplePluginCommand for Get { 9 | type Plugin = crate::NuPluginDbus; 10 | 11 | fn name(&self) -> &str { 12 | "dbus get" 13 | } 14 | 15 | fn signature(&self) -> Signature { 16 | Signature::build(self.name()) 17 | .dbus_command() 18 | .accepts_dbus_client_options() 19 | .accepts_timeout() 20 | .input_output_type(Type::Nothing, Type::Any) 21 | .required_named( 22 | "dest", 23 | SyntaxShape::String, 24 | "The name of the connection to read the property from", 25 | None, 26 | ) 27 | .required( 28 | "object", 29 | SyntaxShape::String, 30 | "The path to the object to read the property from", 31 | ) 32 | .required( 33 | "interface", 34 | SyntaxShape::String, 35 | "The name of the interface the property belongs to", 36 | ) 37 | .required( 38 | "property", 39 | SyntaxShape::String, 40 | "The name of the property to read", 41 | ) 42 | } 43 | 44 | fn description(&self) -> &str { 45 | "Get a D-Bus property" 46 | } 47 | 48 | fn search_terms(&self) -> Vec<&str> { 49 | vec!["dbus", "property", "read"] 50 | } 51 | 52 | fn examples(&self) -> Vec { 53 | vec![Example { 54 | example: "dbus get --dest=org.mpris.MediaPlayer2.spotify \ 55 | /org/mpris/MediaPlayer2 \ 56 | org.mpris.MediaPlayer2.Player Metadata", 57 | description: "Get the currently playing song in Spotify", 58 | result: Some(Value::test_record(nu_protocol::record!( 59 | "xesam:title" => Value::test_string("Birdie"), 60 | "xesam:artist" => Value::test_list(vec![ 61 | Value::test_string("LOVE PSYCHEDELICO") 62 | ]), 63 | "xesam:album" => Value::test_string("Love Your Love"), 64 | "xesam:url" => Value::test_string("https://open.spotify.com/track/51748BvzeeMs4PIdPuyZmv"), 65 | ))), 66 | }] 67 | } 68 | 69 | fn run( 70 | &self, 71 | _plugin: &Self::Plugin, 72 | _engine: &EngineInterface, 73 | call: &EvaluatedCall, 74 | _input: &Value, 75 | ) -> Result { 76 | let config = DbusClientConfig::try_from(call)?; 77 | let dbus = DbusClient::new(config)?; 78 | dbus.get( 79 | &call.get_flag("dest")?.unwrap(), 80 | &call.req(0)?, 81 | &call.req(1)?, 82 | &call.req(2)?, 83 | ) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/commands/get_all.rs: -------------------------------------------------------------------------------- 1 | use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 | use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 | 4 | use crate::{client::DbusClient, config::DbusClientConfig, DbusSignatureUtilExt}; 5 | 6 | pub struct GetAll; 7 | 8 | impl SimplePluginCommand for GetAll { 9 | type Plugin = crate::NuPluginDbus; 10 | 11 | fn name(&self) -> &str { 12 | "dbus get-all" 13 | } 14 | 15 | fn signature(&self) -> Signature { 16 | Signature::build(self.name()) 17 | .dbus_command() 18 | .accepts_dbus_client_options() 19 | .accepts_timeout() 20 | .input_output_type(Type::Nothing, Type::Record([].into())) 21 | .required_named( 22 | "dest", 23 | SyntaxShape::String, 24 | "The name of the connection to read the property from", 25 | None, 26 | ) 27 | .required( 28 | "object", 29 | SyntaxShape::String, 30 | "The path to the object to read the property from", 31 | ) 32 | .required( 33 | "interface", 34 | SyntaxShape::String, 35 | "The name of the interface the property belongs to", 36 | ) 37 | } 38 | 39 | fn description(&self) -> &str { 40 | "Get all D-Bus properties for the given object" 41 | } 42 | 43 | fn search_terms(&self) -> Vec<&str> { 44 | vec!["dbus", "properties", "property", "get"] 45 | } 46 | 47 | fn examples(&self) -> Vec { 48 | vec![Example { 49 | example: "dbus get-all --dest=org.mpris.MediaPlayer2.spotify \ 50 | /org/mpris/MediaPlayer2 \ 51 | org.mpris.MediaPlayer2.Player", 52 | description: "Get the current player state of Spotify", 53 | result: Some(Value::test_record(nu_protocol::record!( 54 | "CanPlay" => Value::test_bool(true), 55 | "Volume" => Value::test_float(0.43), 56 | "PlaybackStatus" => Value::test_string("Paused"), 57 | ))), 58 | }] 59 | } 60 | 61 | fn run( 62 | &self, 63 | _plugin: &Self::Plugin, 64 | _engine: &EngineInterface, 65 | call: &EvaluatedCall, 66 | _input: &Value, 67 | ) -> Result { 68 | let config = DbusClientConfig::try_from(call)?; 69 | let dbus = DbusClient::new(config)?; 70 | dbus.get_all( 71 | &call.get_flag("dest")?.unwrap(), 72 | &call.req(0)?, 73 | &call.req(1)?, 74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/commands/introspect.rs: -------------------------------------------------------------------------------- 1 | use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 | use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 | 4 | use crate::{client::DbusClient, config::DbusClientConfig, DbusSignatureUtilExt}; 5 | 6 | pub struct Introspect; 7 | 8 | impl SimplePluginCommand for Introspect { 9 | type Plugin = crate::NuPluginDbus; 10 | 11 | fn name(&self) -> &str { 12 | "dbus introspect" 13 | } 14 | 15 | fn signature(&self) -> Signature { 16 | Signature::build(self.name()) 17 | .dbus_command() 18 | .accepts_dbus_client_options() 19 | .accepts_timeout() 20 | .input_output_type(Type::Nothing, Type::Record([].into())) 21 | .required_named( 22 | "dest", 23 | SyntaxShape::String, 24 | "The name of the connection that owns the object", 25 | None, 26 | ) 27 | .required( 28 | "object", 29 | SyntaxShape::String, 30 | "The path to the object to introspect", 31 | ) 32 | } 33 | 34 | fn description(&self) -> &str { 35 | "Introspect a D-Bus object" 36 | } 37 | 38 | fn extra_description(&self) -> &str { 39 | "Returns information about available nodes, interfaces, methods, \ 40 | signals, and properties on the given object path" 41 | } 42 | 43 | fn search_terms(&self) -> Vec<&str> { 44 | vec!["dbus", "help", "method"] 45 | } 46 | 47 | fn examples(&self) -> Vec { 48 | vec![ 49 | Example { 50 | example: "dbus introspect --dest=org.mpris.MediaPlayer2.spotify \ 51 | /org/mpris/MediaPlayer2 | explore", 52 | description: "Look at the MPRIS2 interfaces exposed by Spotify", 53 | result: None, 54 | }, 55 | Example { 56 | example: "dbus introspect --dest=org.kde.plasmashell \ 57 | /org/kde/osdService | get interfaces | \ 58 | where name == org.kde.osdService | get 0.methods", 59 | description: "Get methods exposed by KDE Plasma's on-screen display \ 60 | service", 61 | result: None, 62 | }, 63 | Example { 64 | example: "dbus introspect --dest=org.kde.KWin / | get children | \ 65 | select name", 66 | description: "List objects exposed by KWin", 67 | result: None, 68 | }, 69 | ] 70 | } 71 | 72 | fn run( 73 | &self, 74 | _plugin: &Self::Plugin, 75 | _engine: &EngineInterface, 76 | call: &EvaluatedCall, 77 | _input: &Value, 78 | ) -> Result { 79 | let config = DbusClientConfig::try_from(call)?; 80 | let dbus = DbusClient::new(config)?; 81 | let node = dbus.introspect(&call.get_flag("dest")?.unwrap(), &call.req(0)?)?; 82 | Ok(node.to_value(call.head)) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/commands/list.rs: -------------------------------------------------------------------------------- 1 | use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 | use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 | 4 | use crate::{client::DbusClient, config::DbusClientConfig, pattern::Pattern, DbusSignatureUtilExt}; 5 | 6 | pub struct List; 7 | 8 | impl SimplePluginCommand for List { 9 | type Plugin = crate::NuPluginDbus; 10 | 11 | fn name(&self) -> &str { 12 | "dbus list" 13 | } 14 | 15 | fn signature(&self) -> Signature { 16 | Signature::build(self.name()) 17 | .dbus_command() 18 | .accepts_dbus_client_options() 19 | .accepts_timeout() 20 | .input_output_type(Type::Nothing, Type::List(Type::String.into())) 21 | .optional( 22 | "pattern", 23 | SyntaxShape::String, 24 | "An optional glob-like pattern to filter the result by", 25 | ) 26 | } 27 | 28 | fn description(&self) -> &str { 29 | "List all available connection names on the bus" 30 | } 31 | 32 | fn extra_description(&self) -> &str { 33 | "These can be used as arguments for --dest on any of the other commands." 34 | } 35 | 36 | fn search_terms(&self) -> Vec<&str> { 37 | vec!["dbus", "list", "find", "search", "help"] 38 | } 39 | 40 | fn examples(&self) -> Vec { 41 | vec![ 42 | Example { 43 | example: "dbus list", 44 | description: "List all names available on the bus", 45 | result: None, 46 | }, 47 | Example { 48 | example: "dbus list org.freedesktop.*", 49 | description: "List top-level freedesktop.org names on the bus \ 50 | (e.g. matches `org.freedesktop.PowerManagement`, \ 51 | but not `org.freedesktop.Management.Inhibit`)", 52 | result: Some(Value::test_list(vec![ 53 | Value::test_string("org.freedesktop.DBus"), 54 | Value::test_string("org.freedesktop.Flatpak"), 55 | Value::test_string("org.freedesktop.Notifications"), 56 | ])), 57 | }, 58 | Example { 59 | example: "dbus list org.mpris.MediaPlayer2.**", 60 | description: "List all MPRIS2 media players on the bus", 61 | result: Some(Value::test_list(vec![ 62 | Value::test_string("org.mpris.MediaPlayer2.spotify"), 63 | Value::test_string("org.mpris.MediaPlayer2.kdeconnect.mpris_000001"), 64 | ])), 65 | }, 66 | ] 67 | } 68 | 69 | fn run( 70 | &self, 71 | _plugin: &Self::Plugin, 72 | _engine: &EngineInterface, 73 | call: &EvaluatedCall, 74 | _input: &Value, 75 | ) -> Result { 76 | let config = DbusClientConfig::try_from(call)?; 77 | let dbus = DbusClient::new(config)?; 78 | let pattern = call 79 | .opt::(0)? 80 | .map(|pat| Pattern::new(&pat, Some('.'))); 81 | let result = dbus.list(pattern.as_ref())?; 82 | Ok(Value::list( 83 | result 84 | .into_iter() 85 | .map(|s| Value::string(s, call.head)) 86 | .collect(), 87 | call.head, 88 | )) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/commands/main.rs: -------------------------------------------------------------------------------- 1 | use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 | use nu_protocol::{LabeledError, Signature, Value}; 3 | 4 | use crate::DbusSignatureUtilExt; 5 | 6 | pub struct Main; 7 | 8 | impl SimplePluginCommand for Main { 9 | type Plugin = crate::NuPluginDbus; 10 | 11 | fn name(&self) -> &str { 12 | "dbus" 13 | } 14 | 15 | fn signature(&self) -> Signature { 16 | Signature::build(self.name()).dbus_command() 17 | } 18 | 19 | fn description(&self) -> &str { 20 | "Commands for interacting with D-Bus" 21 | } 22 | 23 | fn search_terms(&self) -> Vec<&str> { 24 | vec!["dbus"] 25 | } 26 | 27 | fn run( 28 | &self, 29 | _plugin: &Self::Plugin, 30 | engine: &EngineInterface, 31 | call: &EvaluatedCall, 32 | _input: &Value, 33 | ) -> Result { 34 | Ok(Value::string(engine.get_help()?, call.head)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | mod call; 2 | mod get; 3 | mod get_all; 4 | mod introspect; 5 | mod list; 6 | mod main; 7 | mod set; 8 | 9 | pub use call::Call; 10 | pub use get::Get; 11 | pub use get_all::GetAll; 12 | pub use introspect::Introspect; 13 | pub use list::List; 14 | pub use main::Main; 15 | pub use set::Set; 16 | -------------------------------------------------------------------------------- /src/commands/set.rs: -------------------------------------------------------------------------------- 1 | use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand}; 2 | use nu_protocol::{Example, LabeledError, Signature, SyntaxShape, Type, Value}; 3 | 4 | use crate::{client::DbusClient, config::DbusClientConfig, DbusSignatureUtilExt}; 5 | 6 | pub struct Set; 7 | 8 | impl SimplePluginCommand for Set { 9 | type Plugin = crate::NuPluginDbus; 10 | 11 | fn name(&self) -> &str { 12 | "dbus set" 13 | } 14 | 15 | fn signature(&self) -> Signature { 16 | Signature::build(self.name()) 17 | .dbus_command() 18 | .accepts_dbus_client_options() 19 | .accepts_timeout() 20 | .input_output_type(Type::Nothing, Type::Nothing) 21 | .named( 22 | "signature", 23 | SyntaxShape::String, 24 | "Signature of the value to set, in D-Bus format.\n \ 25 | If not provided, it will be determined from introspection.\n \ 26 | If --no-introspect is specified and this is not provided, it will \ 27 | be guessed (poorly)", 28 | None, 29 | ) 30 | .required_named( 31 | "dest", 32 | SyntaxShape::String, 33 | "The name of the connection to write the property on", 34 | None, 35 | ) 36 | .required( 37 | "object", 38 | SyntaxShape::String, 39 | "The path to the object to write the property on", 40 | ) 41 | .required( 42 | "interface", 43 | SyntaxShape::String, 44 | "The name of the interface the property belongs to", 45 | ) 46 | .required( 47 | "property", 48 | SyntaxShape::String, 49 | "The name of the property to write", 50 | ) 51 | .required( 52 | "value", 53 | SyntaxShape::Any, 54 | "The value to write to the property", 55 | ) 56 | } 57 | 58 | fn description(&self) -> &str { 59 | "Set a D-Bus property" 60 | } 61 | 62 | fn search_terms(&self) -> Vec<&str> { 63 | vec!["dbus", "property", "write", "put"] 64 | } 65 | 66 | fn examples(&self) -> Vec { 67 | vec![Example { 68 | example: "dbus set --dest=org.mpris.MediaPlayer2.spotify \ 69 | /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player \ 70 | Volume 0.5", 71 | description: "Set the volume of Spotify to 50%", 72 | result: None, 73 | }] 74 | } 75 | 76 | fn run( 77 | &self, 78 | _plugin: &Self::Plugin, 79 | _engine: &EngineInterface, 80 | call: &EvaluatedCall, 81 | _input: &Value, 82 | ) -> Result { 83 | let config = DbusClientConfig::try_from(call)?; 84 | let dbus = DbusClient::new(config)?; 85 | dbus.set( 86 | &call.get_flag("dest")?.unwrap(), 87 | &call.req(0)?, 88 | &call.req(1)?, 89 | &call.req(2)?, 90 | call.get_flag("signature")?.as_ref(), 91 | &call.req(3)?, 92 | )?; 93 | Ok(Value::nothing(call.head)) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use nu_plugin::EvaluatedCall; 4 | use nu_protocol::{LabeledError, Span, Spanned}; 5 | 6 | /// General configuration related to the D-Bus client connection 7 | #[derive(Debug, Clone)] 8 | pub struct DbusClientConfig { 9 | pub span: Span, 10 | /// Which bus should we connect to? 11 | pub bus_choice: Spanned, 12 | /// How long to wait for a method call to return 13 | pub timeout: Spanned, 14 | /// Enable introspection if signature unknown (default true) 15 | pub introspect: bool, 16 | } 17 | 18 | /// Where to connect to the D-Bus server 19 | #[derive(Debug, Clone, Default)] 20 | pub enum DbusBusChoice { 21 | /// Connect to the session bus 22 | #[default] 23 | Session, 24 | /// Connect to the system bus 25 | System, 26 | /// Connect to the bus that started this process 27 | Started, 28 | /// Connect to a bus instance at the given address 29 | Bus(String), 30 | /// Connect to a non-bus D-Bus server at the given address (will not send Hello) 31 | Peer(String), 32 | } 33 | 34 | impl TryFrom<&EvaluatedCall> for DbusClientConfig { 35 | type Error = LabeledError; 36 | 37 | fn try_from(call: &EvaluatedCall) -> Result { 38 | let mut config = DbusClientConfig { 39 | span: call.head, 40 | bus_choice: Spanned { 41 | item: DbusBusChoice::default(), 42 | span: call.head, 43 | }, 44 | timeout: Spanned { 45 | item: Duration::from_secs(2), 46 | span: call.head, 47 | }, 48 | introspect: true, 49 | }; 50 | 51 | // Handle recognized config args 52 | for (name, value) in &call.named { 53 | match &name.item[..] { 54 | r#type @ ("session" | "system" | "started") => { 55 | if value.is_none() || value.as_ref().is_some_and(|v| v.is_true()) { 56 | let dest = match r#type { 57 | "session" => DbusBusChoice::Session, 58 | "system" => DbusBusChoice::System, 59 | "started" => DbusBusChoice::Started, 60 | _ => unreachable!(), 61 | }; 62 | config.bus_choice = Spanned { 63 | item: dest, 64 | span: name.span, 65 | }; 66 | } 67 | } 68 | r#type @ ("bus" | "peer") => { 69 | if let Some(value) = value { 70 | let address = value.as_str()?; 71 | let dest = match r#type { 72 | "bus" => DbusBusChoice::Bus(address.to_owned()), 73 | "peer" => DbusBusChoice::Peer(address.to_owned()), 74 | _ => unreachable!(), 75 | }; 76 | config.bus_choice = Spanned { 77 | item: dest, 78 | span: value.span(), 79 | }; 80 | } 81 | } 82 | "timeout" => { 83 | if let Some(value) = value { 84 | let nanos: u64 = value.as_duration()?.try_into().map_err(|_| { 85 | LabeledError::new("Timeout must be a positive duration") 86 | .with_label("invalid timeout specified here", value.span()) 87 | })?; 88 | let item = Duration::from_nanos(nanos); 89 | config.timeout = Spanned { 90 | item, 91 | span: value.span(), 92 | }; 93 | } 94 | } 95 | "no-introspect" => { 96 | config.introspect = !value 97 | .as_ref() 98 | .and_then(|v| v.as_bool().ok()) 99 | .unwrap_or(false); 100 | } 101 | _ => (), 102 | } 103 | } 104 | 105 | Ok(config) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/convert.rs: -------------------------------------------------------------------------------- 1 | use dbus::{ 2 | arg::{ 3 | messageitem::{MessageItem, MessageItemArray, MessageItemDict}, 4 | ArgType, RefArg, 5 | }, 6 | Message, Signature, 7 | }; 8 | use nu_protocol::{LabeledError, Record, Span, Value}; 9 | use std::str::FromStr; 10 | 11 | use crate::dbus_type::DbusType; 12 | 13 | /// Get the arguments of a message as nushell Values 14 | pub fn from_message(message: &Message, span: Span) -> Result, String> { 15 | let mut out = vec![]; 16 | for refarg in message.iter_init() { 17 | out.push(from_refarg(&refarg, span)?); 18 | } 19 | Ok(out) 20 | } 21 | 22 | pub fn from_refarg(refarg: &dyn RefArg, span: Span) -> Result { 23 | Ok(match refarg.arg_type() { 24 | ArgType::Array => { 25 | if refarg.signature().starts_with("a{") { 26 | // This is a dictionary 27 | let mut record = Record::new(); 28 | let mut iter = refarg.as_iter().unwrap(); 29 | while let Some(key) = iter.next() { 30 | if let Some(val) = iter.next() { 31 | if let Some(key_str) = key.as_str() { 32 | record.insert(key_str, from_refarg(val, span)?); 33 | } 34 | } 35 | } 36 | Value::record(record, span) 37 | } else if &*refarg.signature() == "ay" { 38 | // Byte array - better to return as binary 39 | let bytes = dbus::arg::cast::>(&refarg.box_clone()) 40 | .unwrap() 41 | .to_owned(); 42 | Value::binary(bytes, span) 43 | } else { 44 | // It's an array 45 | Value::list( 46 | refarg 47 | .as_iter() 48 | .unwrap() 49 | .flat_map(|v| from_refarg(v, span)) 50 | .collect(), 51 | span, 52 | ) 53 | } 54 | } 55 | ArgType::Variant => { 56 | let inner = refarg.as_iter().unwrap().next().unwrap(); 57 | return from_refarg(inner, span); 58 | } 59 | ArgType::Boolean => Value::bool(refarg.as_i64().unwrap() != 0, span), 60 | 61 | // Strings 62 | ArgType::String | ArgType::ObjectPath | ArgType::Signature => { 63 | Value::string(refarg.as_str().unwrap(), span) 64 | } 65 | // Ints 66 | ArgType::Byte 67 | | ArgType::Int16 68 | | ArgType::UInt16 69 | | ArgType::Int32 70 | | ArgType::UInt32 71 | | ArgType::Int64 72 | | ArgType::UnixFd => Value::int(refarg.as_i64().unwrap(), span), 73 | 74 | // Nushell doesn't support u64, so present it as a string 75 | ArgType::UInt64 => Value::string(refarg.as_u64().unwrap().to_string(), span), 76 | 77 | // Floats 78 | ArgType::Double => Value::float(refarg.as_f64().unwrap(), span), 79 | 80 | ArgType::Struct => Value::list( 81 | refarg 82 | .as_iter() 83 | .unwrap() 84 | .flat_map(|v| from_refarg(v, span)) 85 | .collect(), 86 | span, 87 | ), 88 | 89 | ArgType::DictEntry => { 90 | return Err("Encountered dictionary entry outside of dictionary".into()) 91 | } 92 | ArgType::Invalid => return Err("Encountered invalid D-Bus value".into()), 93 | }) 94 | } 95 | 96 | pub fn to_message_item( 97 | value: &Value, 98 | expected_type: Option<&DbusType>, 99 | ) -> Result { 100 | // Report errors from conversion. Error must support Display 101 | macro_rules! try_convert { 102 | ($result_expr:expr) => { 103 | $result_expr.map_err(|err| { 104 | LabeledError::new(format!( 105 | "Failed to convert value to the D-Bus `{:?}` type", 106 | expected_type.unwrap() 107 | )) 108 | .with_label(err.to_string(), value.span()) 109 | })? 110 | }; 111 | } 112 | 113 | // Try to match values to expected types 114 | match (value, expected_type) { 115 | // Boolean 116 | (Value::Bool { val, .. }, Some(DbusType::Boolean)) => Ok(MessageItem::Bool(*val)), 117 | 118 | // Strings and specialized strings 119 | (Value::String { val, .. }, Some(DbusType::String)) => Ok(MessageItem::Str(val.to_owned())), 120 | (Value::String { val, .. }, Some(DbusType::ObjectPath)) => Ok(MessageItem::ObjectPath( 121 | try_convert!(dbus::strings::Path::new(val)), 122 | )), 123 | (Value::String { val, .. }, Some(DbusType::Signature)) => Ok(MessageItem::Signature( 124 | try_convert!(dbus::strings::Signature::new(val)), 125 | )), 126 | 127 | // Signed ints 128 | (Value::Int { val, .. }, Some(DbusType::Int64)) => Ok(MessageItem::Int64(*val)), 129 | (Value::Int { val, .. }, Some(DbusType::Int32)) => { 130 | Ok(MessageItem::Int32(try_convert!(i32::try_from(*val)))) 131 | } 132 | (Value::Int { val, .. }, Some(DbusType::Int16)) => { 133 | Ok(MessageItem::Int16(try_convert!(i16::try_from(*val)))) 134 | } 135 | 136 | // Unsigned ints 137 | (Value::Int { val, .. }, Some(DbusType::UInt64)) => { 138 | Ok(MessageItem::UInt64(try_convert!(u64::try_from(*val)))) 139 | } 140 | (Value::Int { val, .. }, Some(DbusType::UInt32)) => { 141 | Ok(MessageItem::UInt32(try_convert!(u32::try_from(*val)))) 142 | } 143 | (Value::Int { val, .. }, Some(DbusType::UInt16)) => { 144 | Ok(MessageItem::UInt16(try_convert!(u16::try_from(*val)))) 145 | } 146 | (Value::Int { val, .. }, Some(DbusType::Byte)) => { 147 | Ok(MessageItem::Byte(try_convert!(u8::try_from(*val)))) 148 | } 149 | 150 | // Ints from string 151 | (Value::String { val, .. }, Some(DbusType::Int64)) => { 152 | Ok(MessageItem::Int64(try_convert!(i64::from_str(&val[..])))) 153 | } 154 | (Value::String { val, .. }, Some(DbusType::Int32)) => { 155 | Ok(MessageItem::Int32(try_convert!(i32::from_str(&val[..])))) 156 | } 157 | (Value::String { val, .. }, Some(DbusType::Int16)) => { 158 | Ok(MessageItem::Int16(try_convert!(i16::from_str(&val[..])))) 159 | } 160 | (Value::String { val, .. }, Some(DbusType::UInt64)) => { 161 | Ok(MessageItem::UInt64(try_convert!(u64::from_str(&val[..])))) 162 | } 163 | (Value::String { val, .. }, Some(DbusType::UInt32)) => { 164 | Ok(MessageItem::UInt32(try_convert!(u32::from_str(&val[..])))) 165 | } 166 | (Value::String { val, .. }, Some(DbusType::UInt16)) => { 167 | Ok(MessageItem::UInt16(try_convert!(u16::from_str(&val[..])))) 168 | } 169 | (Value::String { val, .. }, Some(DbusType::Byte)) => { 170 | Ok(MessageItem::Byte(try_convert!(u8::from_str(&val[..])))) 171 | } 172 | 173 | // Float 174 | (Value::Float { val, .. }, Some(DbusType::Double)) => Ok(MessageItem::Double(*val)), 175 | (Value::String { val, .. }, Some(DbusType::Double)) => { 176 | Ok(MessageItem::Double(try_convert!(f64::from_str(&val[..])))) 177 | } 178 | 179 | // Binary 180 | (Value::Binary { val, .. }, Some(r#type @ DbusType::Array(content_type))) 181 | if matches!(**content_type, DbusType::Byte) => 182 | { 183 | // FIXME: this is likely pretty inefficient for a bunch of bytes 184 | let sig = Signature::from(r#type.stringify()); 185 | let items = val 186 | .iter() 187 | .cloned() 188 | .map(MessageItem::Byte) 189 | .collect::>(); 190 | Ok(MessageItem::Array( 191 | MessageItemArray::new(items, sig).unwrap(), 192 | )) 193 | } 194 | 195 | // List/array 196 | (Value::List { vals, .. }, Some(r#type @ DbusType::Array(content_type))) => { 197 | let sig = Signature::from(r#type.stringify()); 198 | let items = vals 199 | .iter() 200 | .map(|content| to_message_item(content, Some(content_type))) 201 | .collect::, _>>()?; 202 | Ok(MessageItem::Array( 203 | MessageItemArray::new(items, sig).unwrap(), 204 | )) 205 | } 206 | 207 | // Struct 208 | (Value::List { vals, .. }, Some(DbusType::Struct(types))) => { 209 | if vals.len() != types.len() { 210 | return Err(LabeledError::new(format!( 211 | "expected struct with {} element(s) ({:?})", 212 | types.len(), 213 | types 214 | )) 215 | .with_label( 216 | format!("this list has {} element(s) instead", vals.len()), 217 | value.span(), 218 | )); 219 | } 220 | let items = vals 221 | .iter() 222 | .zip(types) 223 | .map(|(content, r#type)| to_message_item(content, Some(r#type))) 224 | .collect::, _>>()?; 225 | Ok(MessageItem::Struct(items)) 226 | } 227 | 228 | // Record/dict 229 | (Value::Record { val, .. }, Some(DbusType::Array(content_type))) 230 | if matches!(**content_type, DbusType::DictEntry(_, _)) => 231 | { 232 | if let DbusType::DictEntry(ref key_type, ref val_type) = **content_type { 233 | let key_sig = Signature::from(key_type.stringify()); 234 | let val_sig = Signature::from(val_type.stringify()); 235 | let pairs = val 236 | .iter() 237 | .map(|(key, val)| { 238 | let key_as_value = Value::string(key, value.span()); 239 | let key_message_item = to_message_item(&key_as_value, Some(key_type))?; 240 | let val_message_item = to_message_item(val, Some(val_type))?; 241 | Ok((key_message_item, val_message_item)) 242 | }) 243 | .collect::, LabeledError>>()?; 244 | Ok(MessageItem::Dict( 245 | MessageItemDict::new(pairs, key_sig, val_sig).unwrap(), 246 | )) 247 | } else { 248 | unreachable!() 249 | } 250 | } 251 | 252 | // Variant - use automatic type 253 | (other_value, Some(DbusType::Variant)) => Ok(MessageItem::Variant(Box::new( 254 | to_message_item(other_value, None)?, 255 | ))), 256 | 257 | // Value not compatible with expected type 258 | (other_value, Some(expectation)) => Err(LabeledError::new(format!( 259 | "`{}` can not be converted to the D-Bus `{:?}` type", 260 | other_value.get_type(), 261 | expectation 262 | )) 263 | .with_label( 264 | format!("expected a `{:?}` here", expectation), 265 | other_value.span(), 266 | )), 267 | 268 | // Automatic types (with no type expectation) 269 | (Value::String { .. }, None) => to_message_item(value, Some(&DbusType::String)), 270 | (Value::Int { .. }, None) => to_message_item(value, Some(&DbusType::Int64)), 271 | (Value::Float { .. }, None) => to_message_item(value, Some(&DbusType::Double)), 272 | (Value::Bool { .. }, None) => to_message_item(value, Some(&DbusType::Boolean)), 273 | (Value::List { .. }, None) => { 274 | to_message_item(value, Some(&DbusType::Array(DbusType::Variant.into()))) 275 | } 276 | (Value::Record { .. }, None) => to_message_item( 277 | value, 278 | Some(&DbusType::Array( 279 | DbusType::DictEntry(DbusType::String.into(), DbusType::Variant.into()).into(), 280 | )), 281 | ), 282 | 283 | // No expected type, but can't handle this type 284 | _ => Err(LabeledError::new(format!( 285 | "can not use values of type `{}` in D-Bus calls", 286 | value.get_type() 287 | )) 288 | .with_label("use a supported type here instead", value.span())), 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/dbus_type.rs: -------------------------------------------------------------------------------- 1 | /// Representation of fully specified D-Bus types 2 | /// 3 | /// [dbus::arg::ArgType] does not sufficiently specify the types inside of a container type, 4 | /// and also doesn't provide any parse/dump capability 5 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 6 | pub enum DbusType { 7 | Byte, 8 | Boolean, 9 | Int16, 10 | UInt16, 11 | Int32, 12 | UInt32, 13 | Int64, 14 | UInt64, 15 | Double, 16 | String, 17 | ObjectPath, 18 | Signature, 19 | Array(Box), 20 | Struct(Vec), 21 | Variant, 22 | DictEntry(Box, Box), 23 | } 24 | 25 | impl DbusType { 26 | /// Parse one type from a D-Bus signature, and return the remainder 27 | pub fn parse(input: &str) -> Result<(DbusType, &str), String> { 28 | use self::DbusType::*; 29 | 30 | if input.is_empty() { 31 | return Err("unexpected end of D-Bus type string".into()); 32 | } 33 | 34 | match input.chars().nth(0).unwrap() { 35 | 'y' => Ok((Byte, &input[1..])), 36 | 'b' => Ok((Boolean, &input[1..])), 37 | 'n' => Ok((Int16, &input[1..])), 38 | 'q' => Ok((UInt16, &input[1..])), 39 | 'i' => Ok((Int32, &input[1..])), 40 | 'u' => Ok((UInt32, &input[1..])), 41 | 'x' => Ok((Int64, &input[1..])), 42 | 't' => Ok((UInt64, &input[1..])), 43 | 'd' => Ok((Double, &input[1..])), 44 | 's' => Ok((String, &input[1..])), 45 | 'o' => Ok((ObjectPath, &input[1..])), 46 | 'g' => Ok((Signature, &input[1..])), 47 | 'a' => { 48 | // The next type is the content type of the array 49 | let (content_type, remainder) = Self::parse(&input[1..])?; 50 | Ok((Array(content_type.into()), remainder)) 51 | } 52 | '(' => { 53 | // Parse the struct content until we get to the end ) char 54 | let mut remainder = &input[1..]; 55 | let mut types = vec![]; 56 | loop { 57 | if remainder.is_empty() { 58 | break Err("unexpected end of D-Bus type string \ 59 | before end of array" 60 | .into()); 61 | } else if let Some(new_remainder) = remainder.strip_prefix(')') { 62 | break Ok((DbusType::Struct(types), new_remainder)); 63 | } else { 64 | let (r#type, new_remainder) = Self::parse(remainder)?; 65 | types.push(r#type); 66 | remainder = new_remainder; 67 | } 68 | } 69 | } 70 | 'v' => Ok((Variant, &input[1..])), 71 | '{' => { 72 | // Expect two types 73 | let (key_type, key_remainder) = Self::parse(&input[1..])?; 74 | let (val_type, val_remainder) = Self::parse(key_remainder)?; 75 | // Must end with } 76 | if let Some(new_remainder) = val_remainder.strip_prefix('}') { 77 | Ok(( 78 | DbusType::DictEntry(key_type.into(), val_type.into()), 79 | new_remainder, 80 | )) 81 | } else { 82 | Err(format!( 83 | "expected `}}` char to end dictionary in D-Bus type \ 84 | but remainder is {:?}", 85 | val_remainder 86 | )) 87 | } 88 | } 89 | other => Err(format!( 90 | "unexpected char {other:?} in D-Bus type representation" 91 | )), 92 | } 93 | } 94 | 95 | /// Parse multiple types from a D-Bus signature 96 | pub fn parse_all(mut input: &str) -> Result, String> { 97 | let mut out = vec![]; 98 | while !input.is_empty() { 99 | let (parsed, remainder) = Self::parse(input)?; 100 | out.push(parsed); 101 | input = remainder; 102 | } 103 | Ok(out) 104 | } 105 | 106 | /// Convert the D-Bus type into a string suitable for the wire format 107 | pub fn stringify(&self) -> String { 108 | use self::DbusType::*; 109 | 110 | match self { 111 | Byte => 'y'.into(), 112 | Boolean => 'b'.into(), 113 | Int16 => 'n'.into(), 114 | UInt16 => 'q'.into(), 115 | Int32 => 'i'.into(), 116 | UInt32 => 'u'.into(), 117 | Int64 => 'x'.into(), 118 | UInt64 => 't'.into(), 119 | Double => 'd'.into(), 120 | String => 's'.into(), 121 | ObjectPath => 'o'.into(), 122 | Signature => 'g'.into(), 123 | 124 | // a 125 | Array(content) => format!("a{}", content.stringify()), 126 | 127 | // () 128 | Struct(types) => std::iter::once("(".to_owned()) 129 | .chain(types.iter().map(|t| t.stringify())) 130 | .chain(std::iter::once(")".to_owned())) 131 | .collect(), 132 | 133 | Variant => 'v'.into(), 134 | 135 | // {} 136 | DictEntry(key, val) => format!("{{{}{}}}", key.stringify(), val.stringify()), 137 | } 138 | } 139 | } 140 | 141 | #[cfg(test)] 142 | macro_rules! should_parse_to { 143 | ($str:expr, $result:expr) => { 144 | assert_eq!(DbusType::parse($str), Ok(($result, ""))) 145 | }; 146 | } 147 | 148 | #[test] 149 | fn test_parse_simple_types() { 150 | use self::DbusType::*; 151 | should_parse_to!("y", Byte); 152 | should_parse_to!("b", Boolean); 153 | should_parse_to!("n", Int16); 154 | should_parse_to!("q", UInt16); 155 | should_parse_to!("i", Int32); 156 | should_parse_to!("u", UInt32); 157 | should_parse_to!("x", Int64); 158 | should_parse_to!("t", UInt64); 159 | should_parse_to!("d", Double); 160 | should_parse_to!("s", String); 161 | should_parse_to!("o", ObjectPath); 162 | should_parse_to!("g", Signature); 163 | should_parse_to!("v", Variant); 164 | } 165 | 166 | #[test] 167 | fn test_parse_simple_type_remainder() -> Result<(), String> { 168 | let (_, remainder) = DbusType::parse("gyn")?; 169 | assert_eq!(remainder, "yn"); 170 | Ok(()) 171 | } 172 | 173 | #[test] 174 | fn test_parse_simple_invalid() { 175 | assert!(DbusType::parse("*").is_err()); 176 | } 177 | 178 | #[test] 179 | fn test_parse_simple_array() { 180 | use self::DbusType::*; 181 | should_parse_to!("ay", Array(Byte.into())); 182 | } 183 | 184 | #[test] 185 | fn test_parse_nested_array() { 186 | use self::DbusType::*; 187 | should_parse_to!("aai", Array(Array(Int32.into()).into())); 188 | } 189 | 190 | #[test] 191 | fn test_parse_array_remainder() -> Result<(), String> { 192 | let (_, remainder) = DbusType::parse("ay(oi)")?; 193 | assert_eq!(remainder, "(oi)"); 194 | Ok(()) 195 | } 196 | 197 | #[test] 198 | fn test_parse_array_unclosed() { 199 | assert!(DbusType::parse("a").is_err()); 200 | } 201 | 202 | #[test] 203 | fn test_parse_simple_struct() { 204 | use self::DbusType::*; 205 | should_parse_to!("()", Struct(vec![])); 206 | should_parse_to!("(y)", Struct(vec![Byte])); 207 | should_parse_to!("(sy)", Struct(vec![String, Byte])); 208 | should_parse_to!("(xto)", Struct(vec![Int64, UInt64, ObjectPath])); 209 | } 210 | 211 | #[test] 212 | fn test_parse_nested_struct() { 213 | use self::DbusType::*; 214 | should_parse_to!("((xx))", Struct(vec![Struct(vec![Int64, Int64])])); 215 | should_parse_to!("(y(xx))", Struct(vec![Byte, Struct(vec![Int64, Int64])])); 216 | should_parse_to!( 217 | "(y(ss)o)", 218 | Struct(vec![Byte, Struct(vec![String, String]), ObjectPath]) 219 | ); 220 | should_parse_to!("((yy)s)", Struct(vec![Struct(vec![Byte, Byte]), String])); 221 | } 222 | 223 | #[test] 224 | fn test_parse_struct_remainder() -> Result<(), String> { 225 | let (_, remainder) = DbusType::parse("(oi)ay")?; 226 | assert_eq!(remainder, "ay"); 227 | Ok(()) 228 | } 229 | 230 | #[test] 231 | fn test_parse_struct_unclosed() { 232 | assert!(DbusType::parse("(ss").is_err()); 233 | } 234 | 235 | #[test] 236 | fn test_parse_dict_entry() { 237 | use self::DbusType::*; 238 | should_parse_to!("{ss}", DictEntry(String.into(), String.into())); 239 | should_parse_to!( 240 | "{s(bd)}", 241 | DictEntry(String.into(), Struct(vec![Boolean, Double]).into()) 242 | ); 243 | } 244 | 245 | #[test] 246 | fn test_parse_array_dict() { 247 | use self::DbusType::*; 248 | should_parse_to!( 249 | "a{sd}", 250 | Array(DictEntry(String.into(), Double.into()).into()) 251 | ); 252 | } 253 | 254 | #[test] 255 | fn test_parse_dict_entry_remainder() -> Result<(), String> { 256 | let (_, remainder) = DbusType::parse("{sd}{sai}")?; 257 | assert_eq!(remainder, "{sai}"); 258 | Ok(()) 259 | } 260 | 261 | #[test] 262 | fn test_parse_dict_entry_unclosed() { 263 | assert!(DbusType::parse("{ss").is_err()); 264 | } 265 | 266 | #[test] 267 | fn test_parse_all() { 268 | use self::DbusType::*; 269 | assert_eq!(DbusType::parse_all(""), Ok(vec![])); 270 | assert_eq!(DbusType::parse_all("s"), Ok(vec![String,])); 271 | assert_eq!( 272 | DbusType::parse_all("isbb"), 273 | Ok(vec![Int32, String, Boolean, Boolean,]) 274 | ); 275 | assert_eq!( 276 | DbusType::parse_all("ia{s(bi)}s"), 277 | Ok(vec![ 278 | Int32, 279 | Array(DictEntry(String.into(), Struct(vec![Boolean, Int32]).into()).into()), 280 | String, 281 | ]) 282 | ); 283 | } 284 | 285 | #[cfg(test)] 286 | macro_rules! should_stringify_to { 287 | ($type:expr, $result:expr) => { 288 | assert_eq!(DbusType::stringify(&$type), $result) 289 | }; 290 | } 291 | 292 | #[test] 293 | fn test_stringify_simple_types() { 294 | use self::DbusType::*; 295 | should_stringify_to!(Byte, "y"); 296 | should_stringify_to!(Boolean, "b"); 297 | should_stringify_to!(Int16, "n"); 298 | should_stringify_to!(UInt16, "q"); 299 | should_stringify_to!(Int32, "i"); 300 | should_stringify_to!(UInt32, "u"); 301 | should_stringify_to!(Int64, "x"); 302 | should_stringify_to!(UInt64, "t"); 303 | should_stringify_to!(Double, "d"); 304 | should_stringify_to!(String, "s"); 305 | should_stringify_to!(ObjectPath, "o"); 306 | should_stringify_to!(Signature, "g"); 307 | should_stringify_to!(Variant, "v"); 308 | } 309 | 310 | #[test] 311 | fn test_stringify_array() { 312 | use self::DbusType::*; 313 | should_stringify_to!(Array(Variant.into()), "av"); 314 | should_stringify_to!(Array(Array(String.into()).into()), "aas"); 315 | } 316 | 317 | #[test] 318 | fn test_stringify_struct() { 319 | use self::DbusType::*; 320 | should_stringify_to!(Struct(vec![]), "()"); 321 | should_stringify_to!(Struct(vec![Int32]), "(i)"); 322 | should_stringify_to!(Struct(vec![Int32, String]), "(is)"); 323 | should_stringify_to!(Struct(vec![Byte, Int32, String]), "(yis)"); 324 | should_stringify_to!( 325 | Struct(vec![Byte, Struct(vec![String, Boolean]), String]), 326 | "(y(sb)s)" 327 | ); 328 | } 329 | 330 | #[test] 331 | fn test_stringify_dict_entry() { 332 | use self::DbusType::*; 333 | should_stringify_to!(DictEntry(String.into(), Int32.into()), "{si}"); 334 | should_stringify_to!(DictEntry(Int32.into(), String.into()), "{is}"); 335 | } 336 | 337 | #[test] 338 | fn test_stringify_nested() { 339 | use self::DbusType::*; 340 | should_stringify_to!( 341 | Array(DictEntry(String.into(), Int32.into()).into()), 342 | "a{si}" 343 | ); 344 | should_stringify_to!( 345 | Array( 346 | DictEntry( 347 | String.into(), 348 | Struct(vec![Byte, Array(Int32.into())]).into() 349 | ) 350 | .into() 351 | ), 352 | "a{s(yai)}" 353 | ); 354 | } 355 | -------------------------------------------------------------------------------- /src/introspection.rs: -------------------------------------------------------------------------------- 1 | use nu_protocol::{record, Span, Value}; 2 | use serde::Deserialize; 3 | 4 | macro_rules! list_to_value { 5 | ($list:expr, $span:expr) => { 6 | Value::list($list.iter().map(|i| i.to_value($span)).collect(), $span) 7 | }; 8 | } 9 | 10 | #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Default)] 11 | #[serde(rename_all = "kebab-case")] 12 | pub struct Node { 13 | #[serde(default)] 14 | pub name: Option, 15 | #[serde(default, rename = "interface")] 16 | pub interfaces: Vec, 17 | #[serde(default, rename = "node")] 18 | pub children: Vec, 19 | } 20 | 21 | impl Node { 22 | pub fn from_xml(xml: &str) -> Result { 23 | let mut deserializer = serde_xml_rs::de::Deserializer::new_from_reader(xml.as_bytes()) 24 | .non_contiguous_seq_elements(true); 25 | Node::deserialize(&mut deserializer) 26 | } 27 | 28 | #[cfg(test)] 29 | pub fn with_name(name: impl Into) -> Node { 30 | Node { 31 | name: Some(name.into()), 32 | interfaces: vec![], 33 | children: vec![], 34 | } 35 | } 36 | 37 | pub fn get_interface(&self, name: &str) -> Option<&Interface> { 38 | self.interfaces.iter().find(|i| i.name == name) 39 | } 40 | 41 | /// Find a method on an interface on this node, and then generate the signature of the method 42 | /// args 43 | pub fn get_method_args_signature(&self, interface: &str, method: &str) -> Option { 44 | Some( 45 | self.get_interface(interface)? 46 | .get_method(method)? 47 | .in_signature(), 48 | ) 49 | } 50 | 51 | /// Find the signature of a property on an interface on this node 52 | pub fn get_property_signature(&self, interface: &str, property: &str) -> Option<&str> { 53 | Some( 54 | &self 55 | .get_interface(interface)? 56 | .get_property(property)? 57 | .r#type, 58 | ) 59 | } 60 | 61 | /// Represent the node as a nushell [Value] 62 | pub fn to_value(&self, span: Span) -> Value { 63 | Value::record( 64 | record! { 65 | "name" => self.name.as_ref().map(|s| Value::string(s, span)).unwrap_or_default(), 66 | "interfaces" => list_to_value!(self.interfaces, span), 67 | "children" => list_to_value!(self.children, span), 68 | }, 69 | span, 70 | ) 71 | } 72 | } 73 | 74 | #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 75 | #[serde(rename_all = "kebab-case")] 76 | pub struct Interface { 77 | pub name: String, 78 | #[serde(default, rename = "method")] 79 | pub methods: Vec, 80 | #[serde(default, rename = "signal")] 81 | pub signals: Vec, 82 | #[serde(default, rename = "property")] 83 | pub properties: Vec, 84 | #[serde(default, rename = "annotation")] 85 | pub annotations: Vec, 86 | } 87 | 88 | impl Interface { 89 | pub fn get_method(&self, name: &str) -> Option<&Method> { 90 | self.methods.iter().find(|m| m.name == name) 91 | } 92 | 93 | #[allow(dead_code)] 94 | pub fn get_signal(&self, name: &str) -> Option<&Signal> { 95 | self.signals.iter().find(|s| s.name == name) 96 | } 97 | 98 | pub fn get_property(&self, name: &str) -> Option<&Property> { 99 | self.properties.iter().find(|p| p.name == name) 100 | } 101 | 102 | /// Represent the interface as a nushell [Value] 103 | pub fn to_value(&self, span: Span) -> Value { 104 | Value::record( 105 | record! { 106 | "name" => Value::string(&self.name, span), 107 | "methods" => list_to_value!(self.methods, span), 108 | "signals" => list_to_value!(self.signals, span), 109 | "properties" => list_to_value!(self.properties, span), 110 | "annotations" => list_to_value!(self.annotations, span), 111 | }, 112 | span, 113 | ) 114 | } 115 | } 116 | 117 | #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 118 | #[serde(rename_all = "kebab-case")] 119 | pub struct Method { 120 | pub name: String, 121 | #[serde(default, rename = "arg")] 122 | pub args: Vec, 123 | #[serde(default, rename = "annotation")] 124 | pub annotations: Vec, 125 | } 126 | 127 | impl Method { 128 | /// Get the signature of the method args 129 | pub fn in_signature(&self) -> String { 130 | self.args 131 | .iter() 132 | .filter(|arg| arg.direction == Direction::In) 133 | .map(|arg| &arg.r#type[..]) 134 | .collect() 135 | } 136 | 137 | #[allow(dead_code)] 138 | /// Get the signature of the method result 139 | pub fn out_signature(&self) -> String { 140 | self.args 141 | .iter() 142 | .filter(|arg| arg.direction == Direction::Out) 143 | .map(|arg| &arg.r#type[..]) 144 | .collect() 145 | } 146 | 147 | /// Represent the method as a nushell [Value] 148 | pub fn to_value(&self, span: Span) -> Value { 149 | Value::record( 150 | record! { 151 | "name" => Value::string(&self.name, span), 152 | "args" => list_to_value!(self.args, span), 153 | "annotations" => list_to_value!(self.annotations, span), 154 | }, 155 | span, 156 | ) 157 | } 158 | } 159 | 160 | #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 161 | #[serde(rename_all = "kebab-case")] 162 | pub struct MethodArg { 163 | #[serde(default)] 164 | pub name: Option, 165 | pub r#type: String, 166 | #[serde(default)] 167 | pub direction: Direction, 168 | } 169 | 170 | impl MethodArg { 171 | #[cfg(test)] 172 | pub fn new( 173 | name: impl Into, 174 | r#type: impl Into, 175 | direction: Direction, 176 | ) -> MethodArg { 177 | MethodArg { 178 | name: Some(name.into()), 179 | r#type: r#type.into(), 180 | direction, 181 | } 182 | } 183 | 184 | /// Represent the method as a nushell [Value] 185 | pub fn to_value(&self, span: Span) -> Value { 186 | Value::record( 187 | record! { 188 | "name" => self.name.as_ref().map(|n| Value::string(n, span)).unwrap_or_default(), 189 | "type" => Value::string(&self.r#type, span), 190 | "direction" => self.direction.to_value(span), 191 | }, 192 | span, 193 | ) 194 | } 195 | } 196 | 197 | #[derive(Debug, Clone, Copy, Deserialize, Default, PartialEq, Eq)] 198 | #[serde(rename_all = "kebab-case")] 199 | pub enum Direction { 200 | #[default] 201 | In, 202 | Out, 203 | } 204 | 205 | impl Direction { 206 | /// Represent the direction as a nushell [Value] 207 | pub fn to_value(self, span: Span) -> Value { 208 | match self { 209 | Direction::In => Value::string("in", span), 210 | Direction::Out => Value::string("out", span), 211 | } 212 | } 213 | } 214 | 215 | #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 216 | #[serde(rename_all = "kebab-case")] 217 | pub struct Signal { 218 | pub name: String, 219 | #[serde(default, rename = "arg")] 220 | pub args: Vec, 221 | #[serde(default, rename = "annotation")] 222 | pub annotations: Vec, 223 | } 224 | 225 | impl Signal { 226 | /// Represent the signal as a nushell [Value] 227 | pub fn to_value(&self, span: Span) -> Value { 228 | Value::record( 229 | record! { 230 | "name" => Value::string(&self.name, span), 231 | "args" => list_to_value!(self.args, span), 232 | "annotations" => list_to_value!(self.annotations, span), 233 | }, 234 | span, 235 | ) 236 | } 237 | } 238 | 239 | #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 240 | #[serde(rename_all = "kebab-case")] 241 | pub struct SignalArg { 242 | #[serde(default)] 243 | pub name: Option, 244 | pub r#type: String, 245 | } 246 | 247 | impl SignalArg { 248 | /// Represent the argument as a nushell [Value] 249 | pub fn to_value(&self, span: Span) -> Value { 250 | Value::record( 251 | record! { 252 | "name" => self.name.as_ref().map(|n| Value::string(n, span)).unwrap_or_default(), 253 | "type" => Value::string(&self.r#type, span), 254 | }, 255 | span, 256 | ) 257 | } 258 | } 259 | 260 | #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 261 | #[serde(rename_all = "kebab-case")] 262 | pub struct Property { 263 | pub name: String, 264 | pub r#type: String, 265 | pub access: Access, 266 | #[serde(default, rename = "annotation")] 267 | pub annotations: Vec, 268 | } 269 | 270 | impl Property { 271 | /// Represent the property as a nushell [Value] 272 | pub fn to_value(&self, span: Span) -> Value { 273 | Value::record( 274 | record! { 275 | "name" => Value::string(&self.name, span), 276 | "type" => Value::string(&self.r#type, span), 277 | "args" => self.access.to_value(span), 278 | "annotations" => list_to_value!(self.annotations, span), 279 | }, 280 | span, 281 | ) 282 | } 283 | } 284 | 285 | #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 286 | #[serde(rename_all = "lowercase")] 287 | pub enum Access { 288 | Read, 289 | Write, 290 | ReadWrite, 291 | } 292 | 293 | impl Access { 294 | /// Represent the access as a nushell [Value] 295 | pub fn to_value(&self, span: Span) -> Value { 296 | match self { 297 | Access::Read => Value::string("read", span), 298 | Access::Write => Value::string("write", span), 299 | Access::ReadWrite => Value::string("readwrite", span), 300 | } 301 | } 302 | } 303 | 304 | #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] 305 | #[serde(rename_all = "kebab-case")] 306 | pub struct Annotation { 307 | pub name: String, 308 | pub value: String, 309 | } 310 | 311 | impl Annotation { 312 | #[cfg(test)] 313 | pub fn new(name: impl Into, value: impl Into) -> Annotation { 314 | Annotation { 315 | name: name.into(), 316 | value: value.into(), 317 | } 318 | } 319 | 320 | /// Represent the annotation as a nushell [Value] 321 | pub fn to_value(&self, span: Span) -> Value { 322 | Value::record( 323 | record! { 324 | "name" => Value::string(&self.name, span), 325 | "value" => Value::string(&self.value, span), 326 | }, 327 | span, 328 | ) 329 | } 330 | } 331 | 332 | #[cfg(test)] 333 | pub fn test_introspection_doc_rs() -> Node { 334 | Node { 335 | name: Some("/com/example/sample_object0".into()), 336 | interfaces: vec![Interface { 337 | name: "com.example.SampleInterface0".into(), 338 | methods: vec![ 339 | Method { 340 | name: "Frobate".into(), 341 | args: vec![ 342 | MethodArg::new("foo", "i", Direction::In), 343 | MethodArg::new("bar", "as", Direction::In), 344 | MethodArg::new("baz", "a{us}", Direction::Out), 345 | ], 346 | annotations: vec![Annotation::new("org.freedesktop.DBus.Deprecated", "true")], 347 | }, 348 | Method { 349 | name: "Bazify".into(), 350 | args: vec![ 351 | MethodArg::new("bar", "(iiu)", Direction::In), 352 | MethodArg::new("len", "u", Direction::Out), 353 | MethodArg::new("bar", "v", Direction::Out), 354 | ], 355 | annotations: vec![], 356 | }, 357 | Method { 358 | name: "Mogrify".into(), 359 | args: vec![MethodArg::new("bar", "(iiav)", Direction::In)], 360 | annotations: vec![], 361 | }, 362 | ], 363 | signals: vec![Signal { 364 | name: "Changed".into(), 365 | args: vec![SignalArg { 366 | name: Some("new_value".into()), 367 | r#type: "b".into(), 368 | }], 369 | annotations: vec![], 370 | }], 371 | properties: vec![Property { 372 | name: "Bar".into(), 373 | r#type: "y".into(), 374 | access: Access::ReadWrite, 375 | annotations: vec![], 376 | }], 377 | annotations: vec![], 378 | }], 379 | children: vec![ 380 | Node::with_name("child_of_sample_object"), 381 | Node::with_name("another_child_of_sample_object"), 382 | ], 383 | } 384 | } 385 | 386 | #[test] 387 | pub fn test_parse_introspection_doc() -> Result<(), serde_xml_rs::Error> { 388 | let xml = include_str!("test_introspection_doc.xml"); 389 | let result = Node::from_xml(xml)?; 390 | assert_eq!(result, test_introspection_doc_rs()); 391 | Ok(()) 392 | } 393 | 394 | #[test] 395 | pub fn test_get_method_args_signature() { 396 | assert_eq!( 397 | test_introspection_doc_rs() 398 | .get_method_args_signature("com.example.SampleInterface0", "Frobate"), 399 | Some("ias".into()) 400 | ); 401 | } 402 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use nu_plugin::{serve_plugin, MsgPackSerializer, Plugin, PluginCommand}; 2 | use nu_protocol::SyntaxShape; 3 | 4 | mod client; 5 | mod commands; 6 | mod config; 7 | mod convert; 8 | mod dbus_type; 9 | mod introspection; 10 | mod pattern; 11 | 12 | fn main() { 13 | serve_plugin(&NuPluginDbus, MsgPackSerializer) 14 | } 15 | 16 | /// The main plugin interface for nushell 17 | pub struct NuPluginDbus; 18 | 19 | impl Plugin for NuPluginDbus { 20 | fn version(&self) -> String { 21 | env!("CARGO_PKG_VERSION").into() 22 | } 23 | 24 | fn commands(&self) -> Vec>> { 25 | vec![ 26 | Box::new(commands::Main), 27 | Box::new(commands::Introspect), 28 | Box::new(commands::Call), 29 | Box::new(commands::Get), 30 | Box::new(commands::GetAll), 31 | Box::new(commands::Set), 32 | Box::new(commands::List), 33 | ] 34 | } 35 | } 36 | 37 | /// For conveniently adding the base options to a dbus command 38 | trait DbusSignatureUtilExt { 39 | fn dbus_command(self) -> Self; 40 | fn accepts_dbus_client_options(self) -> Self; 41 | fn accepts_timeout(self) -> Self; 42 | } 43 | 44 | impl DbusSignatureUtilExt for nu_protocol::Signature { 45 | fn dbus_command(self) -> Self { 46 | self.category(nu_protocol::Category::Platform) 47 | } 48 | 49 | fn accepts_dbus_client_options(self) -> Self { 50 | self.switch("session", "Send to the session message bus (default)", None) 51 | .switch("system", "Send to the system message bus", None) 52 | .switch( 53 | "started", 54 | "Send to the bus that started this process, if applicable", 55 | None, 56 | ) 57 | .named( 58 | "bus", 59 | SyntaxShape::String, 60 | "Send to the bus server at the given address", 61 | None, 62 | ) 63 | .named( 64 | "peer", 65 | SyntaxShape::String, 66 | "Send to a non-bus D-Bus server at the given address. \ 67 | Will not call the Hello method on initialization.", 68 | None, 69 | ) 70 | } 71 | 72 | fn accepts_timeout(self) -> Self { 73 | self.named( 74 | "timeout", 75 | SyntaxShape::Duration, 76 | "How long to wait for a response", 77 | None, 78 | ) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/pattern.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, PartialEq, Eq)] 2 | pub struct Pattern { 3 | separator: Option, 4 | tokens: Vec, 5 | } 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq)] 8 | enum PatternToken { 9 | Exact(String), 10 | OneWildcard, 11 | ManyWildcard, 12 | AnyChar, 13 | } 14 | 15 | impl Pattern { 16 | pub fn new(pattern: &str, separator: Option) -> Pattern { 17 | let mut tokens = vec![]; 18 | for ch in pattern.chars() { 19 | match ch { 20 | '*' => { 21 | if tokens.last() == Some(&PatternToken::OneWildcard) { 22 | *tokens.last_mut().unwrap() = PatternToken::ManyWildcard; 23 | } else { 24 | tokens.push(PatternToken::OneWildcard); 25 | } 26 | } 27 | '?' => tokens.push(PatternToken::AnyChar), 28 | _ => match tokens.last_mut() { 29 | Some(PatternToken::Exact(ref mut s)) => s.push(ch), 30 | _ => tokens.push(PatternToken::Exact(ch.into())), 31 | }, 32 | } 33 | } 34 | Pattern { separator, tokens } 35 | } 36 | 37 | pub fn is_match(&self, string: &str) -> bool { 38 | #[derive(Debug)] 39 | enum MatchState { 40 | Precise, 41 | ScanAhead { stop_at_separator: bool }, 42 | } 43 | let mut state = MatchState::Precise; 44 | let mut tokens = &self.tokens[..]; 45 | let mut search_str = string; 46 | while !tokens.is_empty() { 47 | match tokens.first().unwrap() { 48 | PatternToken::Exact(s) => { 49 | if search_str.starts_with(s) { 50 | // Exact match passed. Consume the token and string and continue 51 | tokens = &tokens[1..]; 52 | search_str = &search_str[s.len()..]; 53 | state = MatchState::Precise; 54 | } else { 55 | match state { 56 | MatchState::Precise => { 57 | // Can't possibly match 58 | return false; 59 | } 60 | MatchState::ScanAhead { stop_at_separator } => { 61 | if search_str.is_empty() { 62 | // End of input, can't match 63 | return false; 64 | } 65 | if stop_at_separator 66 | && self 67 | .separator 68 | .is_some_and(|sep| search_str.starts_with(sep)) 69 | { 70 | // Found the separator. Consume a char and revert to precise 71 | // mode 72 | search_str = &search_str[1..]; 73 | state = MatchState::Precise; 74 | } else { 75 | // Skip the non-matching char and continue 76 | search_str = &search_str[1..]; 77 | } 78 | } 79 | } 80 | } 81 | } 82 | PatternToken::OneWildcard => { 83 | // Set the mode to ScanAhead, stopping at separator 84 | state = MatchState::ScanAhead { 85 | stop_at_separator: true, 86 | }; 87 | tokens = &tokens[1..]; 88 | } 89 | PatternToken::ManyWildcard => { 90 | // Set the mode to ScanAhead, ignoring separator 91 | state = MatchState::ScanAhead { 92 | stop_at_separator: false, 93 | }; 94 | tokens = &tokens[1..]; 95 | } 96 | PatternToken::AnyChar => { 97 | if !search_str.is_empty() { 98 | // Take a char from the search str and continue 99 | search_str = &search_str[1..]; 100 | tokens = &tokens[1..]; 101 | } else { 102 | // End of input 103 | return false; 104 | } 105 | } 106 | } 107 | } 108 | #[cfg(test)] 109 | { 110 | println!( 111 | "end, state={:?}, search_str={:?}, tokens={:?}", 112 | state, search_str, tokens 113 | ); 114 | } 115 | if !search_str.is_empty() { 116 | // If the search str is not empty at the end 117 | match state { 118 | // We didn't end with a wildcard, so this is a fail 119 | MatchState::Precise => false, 120 | // This could be a match as long as the separator isn't contained in the remainder 121 | MatchState::ScanAhead { 122 | stop_at_separator: true, 123 | } => { 124 | if let Some(separator) = self.separator { 125 | !search_str.contains(separator) 126 | } else { 127 | // No separator specified, so this is a success 128 | true 129 | } 130 | } 131 | // Always a success, no matter what remains 132 | MatchState::ScanAhead { 133 | stop_at_separator: false, 134 | } => true, 135 | } 136 | } else { 137 | // The match has succeeded - there is nothing more to match 138 | true 139 | } 140 | } 141 | } 142 | 143 | #[test] 144 | fn test_pattern_new() { 145 | assert_eq!( 146 | Pattern::new("", Some('/')), 147 | Pattern { 148 | separator: Some('/'), 149 | tokens: vec![] 150 | } 151 | ); 152 | assert_eq!( 153 | Pattern::new("", None), 154 | Pattern { 155 | separator: None, 156 | tokens: vec![] 157 | } 158 | ); 159 | assert_eq!( 160 | Pattern::new("org.freedesktop.DBus", Some('.')), 161 | Pattern { 162 | separator: Some('.'), 163 | tokens: vec![PatternToken::Exact("org.freedesktop.DBus".into()),] 164 | } 165 | ); 166 | assert_eq!( 167 | Pattern::new("*", Some('.')), 168 | Pattern { 169 | separator: Some('.'), 170 | tokens: vec![PatternToken::OneWildcard,] 171 | } 172 | ); 173 | assert_eq!( 174 | Pattern::new("**", Some('.')), 175 | Pattern { 176 | separator: Some('.'), 177 | tokens: vec![PatternToken::ManyWildcard,] 178 | } 179 | ); 180 | assert_eq!( 181 | Pattern::new("?", Some('.')), 182 | Pattern { 183 | separator: Some('.'), 184 | tokens: vec![PatternToken::AnyChar,] 185 | } 186 | ); 187 | assert_eq!( 188 | Pattern::new("org.freedesktop.*", Some('.')), 189 | Pattern { 190 | separator: Some('.'), 191 | tokens: vec![ 192 | PatternToken::Exact("org.freedesktop.".into()), 193 | PatternToken::OneWildcard, 194 | ] 195 | } 196 | ); 197 | assert_eq!( 198 | Pattern::new("org.freedesktop.**", Some('.')), 199 | Pattern { 200 | separator: Some('.'), 201 | tokens: vec![ 202 | PatternToken::Exact("org.freedesktop.".into()), 203 | PatternToken::ManyWildcard, 204 | ] 205 | } 206 | ); 207 | assert_eq!( 208 | Pattern::new("org.*.DBus", Some('.')), 209 | Pattern { 210 | separator: Some('.'), 211 | tokens: vec![ 212 | PatternToken::Exact("org.".into()), 213 | PatternToken::OneWildcard, 214 | PatternToken::Exact(".DBus".into()), 215 | ] 216 | } 217 | ); 218 | assert_eq!( 219 | Pattern::new("org.**.DBus", Some('.')), 220 | Pattern { 221 | separator: Some('.'), 222 | tokens: vec![ 223 | PatternToken::Exact("org.".into()), 224 | PatternToken::ManyWildcard, 225 | PatternToken::Exact(".DBus".into()), 226 | ] 227 | } 228 | ); 229 | assert_eq!( 230 | Pattern::new("org.**.?Bus", Some('.')), 231 | Pattern { 232 | separator: Some('.'), 233 | tokens: vec![ 234 | PatternToken::Exact("org.".into()), 235 | PatternToken::ManyWildcard, 236 | PatternToken::Exact(".".into()), 237 | PatternToken::AnyChar, 238 | PatternToken::Exact("Bus".into()), 239 | ] 240 | } 241 | ); 242 | assert_eq!( 243 | Pattern::new("org.free*top", Some('.')), 244 | Pattern { 245 | separator: Some('.'), 246 | tokens: vec![ 247 | PatternToken::Exact("org.free".into()), 248 | PatternToken::OneWildcard, 249 | PatternToken::Exact("top".into()), 250 | ] 251 | } 252 | ); 253 | assert_eq!( 254 | Pattern::new("org.free**top", Some('.')), 255 | Pattern { 256 | separator: Some('.'), 257 | tokens: vec![ 258 | PatternToken::Exact("org.free".into()), 259 | PatternToken::ManyWildcard, 260 | PatternToken::Exact("top".into()), 261 | ] 262 | } 263 | ); 264 | assert_eq!( 265 | Pattern::new("org.**top", Some('.')), 266 | Pattern { 267 | separator: Some('.'), 268 | tokens: vec![ 269 | PatternToken::Exact("org.".into()), 270 | PatternToken::ManyWildcard, 271 | PatternToken::Exact("top".into()), 272 | ] 273 | } 274 | ); 275 | assert_eq!( 276 | Pattern::new("**top", Some('.')), 277 | Pattern { 278 | separator: Some('.'), 279 | tokens: vec![ 280 | PatternToken::ManyWildcard, 281 | PatternToken::Exact("top".into()), 282 | ] 283 | } 284 | ); 285 | assert_eq!( 286 | Pattern::new("org.free**", Some('.')), 287 | Pattern { 288 | separator: Some('.'), 289 | tokens: vec![ 290 | PatternToken::Exact("org.free".into()), 291 | PatternToken::ManyWildcard, 292 | ] 293 | } 294 | ); 295 | } 296 | 297 | #[test] 298 | fn test_pattern_is_match_empty() { 299 | let pat = Pattern { 300 | separator: Some('.'), 301 | tokens: vec![], 302 | }; 303 | assert!(pat.is_match("")); 304 | assert!(!pat.is_match("anystring")); 305 | assert!(!pat.is_match("anystring.anyotherstring")); 306 | } 307 | 308 | #[test] 309 | fn test_pattern_is_match_exact() { 310 | let pat = Pattern { 311 | separator: Some('.'), 312 | tokens: vec![PatternToken::Exact("specific".into())], 313 | }; 314 | assert!(pat.is_match("specific")); 315 | assert!(!pat.is_match("")); 316 | assert!(!pat.is_match("specifi")); 317 | assert!(!pat.is_match("specifica")); 318 | } 319 | 320 | #[test] 321 | fn test_pattern_is_match_one_wildcard() { 322 | let pat = Pattern { 323 | separator: Some('.'), 324 | tokens: vec![ 325 | PatternToken::Exact("foo.".into()), 326 | PatternToken::OneWildcard, 327 | PatternToken::Exact(".baz".into()), 328 | ], 329 | }; 330 | assert!(pat.is_match("foo.bar.baz")); 331 | assert!(pat.is_match("foo.grok.baz")); 332 | assert!(pat.is_match("foo..baz")); 333 | assert!(!pat.is_match("foo.ono.notmatch.baz")); 334 | assert!(!pat.is_match("")); 335 | assert!(!pat.is_match("specifi")); 336 | assert!(!pat.is_match("specifica.baz")); 337 | assert!(!pat.is_match("foo.specifica")); 338 | } 339 | 340 | #[test] 341 | fn test_pattern_is_match_one_wildcard_at_end() { 342 | let pat = Pattern { 343 | separator: Some('.'), 344 | tokens: vec![ 345 | PatternToken::Exact("foo.".into()), 346 | PatternToken::OneWildcard, 347 | ], 348 | }; 349 | assert!(pat.is_match("foo.bar")); 350 | assert!(pat.is_match("foo.grok")); 351 | assert!(pat.is_match("foo.")); 352 | assert!(!pat.is_match("foo.ono.notmatch.baz")); 353 | assert!(!pat.is_match("")); 354 | assert!(!pat.is_match("specifi")); 355 | assert!(!pat.is_match("specifica.baz")); 356 | } 357 | 358 | #[test] 359 | fn test_pattern_is_match_one_wildcard_at_start() { 360 | let pat = Pattern { 361 | separator: Some('.'), 362 | tokens: vec![ 363 | PatternToken::OneWildcard, 364 | PatternToken::Exact(".bar".into()), 365 | ], 366 | }; 367 | assert!(pat.is_match("foo.bar")); 368 | assert!(pat.is_match("grok.bar")); 369 | assert!(pat.is_match(".bar")); 370 | assert!(!pat.is_match("foo.ono.notmatch.bar")); 371 | assert!(!pat.is_match("")); 372 | assert!(!pat.is_match("specifi")); 373 | assert!(!pat.is_match("specifica.baz")); 374 | } 375 | 376 | #[test] 377 | fn test_pattern_is_match_one_wildcard_no_separator() { 378 | let pat = Pattern { 379 | separator: None, 380 | tokens: vec![ 381 | PatternToken::Exact("foo.".into()), 382 | PatternToken::OneWildcard, 383 | PatternToken::Exact(".baz".into()), 384 | ], 385 | }; 386 | assert!(pat.is_match("foo.bar.baz")); 387 | assert!(pat.is_match("foo.grok.baz")); 388 | assert!(pat.is_match("foo..baz")); 389 | assert!(pat.is_match("foo.this.shouldmatch.baz")); 390 | assert!(pat.is_match("foo.this.should.match.baz")); 391 | assert!(!pat.is_match("")); 392 | assert!(!pat.is_match("specifi")); 393 | assert!(!pat.is_match("specifica.baz")); 394 | assert!(!pat.is_match("foo.specifica")); 395 | } 396 | 397 | #[test] 398 | fn test_pattern_is_match_many_wildcard() { 399 | let pat = Pattern { 400 | separator: Some('.'), 401 | tokens: vec![ 402 | PatternToken::Exact("foo.".into()), 403 | PatternToken::ManyWildcard, 404 | PatternToken::Exact(".baz".into()), 405 | ], 406 | }; 407 | assert!(pat.is_match("foo.bar.baz")); 408 | assert!(pat.is_match("foo.grok.baz")); 409 | assert!(pat.is_match("foo..baz")); 410 | assert!(pat.is_match("foo.this.shouldmatch.baz")); 411 | assert!(pat.is_match("foo.this.should.match.baz")); 412 | assert!(!pat.is_match("")); 413 | assert!(!pat.is_match("specifi")); 414 | assert!(!pat.is_match("specifica.baz")); 415 | assert!(!pat.is_match("foo.specifica")); 416 | } 417 | 418 | #[test] 419 | fn test_pattern_is_match_many_wildcard_at_end() { 420 | let pat = Pattern { 421 | separator: Some('.'), 422 | tokens: vec![ 423 | PatternToken::Exact("foo.".into()), 424 | PatternToken::ManyWildcard, 425 | ], 426 | }; 427 | assert!(pat.is_match("foo.bar")); 428 | assert!(pat.is_match("foo.grok")); 429 | assert!(pat.is_match("foo.")); 430 | assert!(pat.is_match("foo.this.should.match")); 431 | assert!(!pat.is_match("")); 432 | assert!(!pat.is_match("specifi")); 433 | assert!(!pat.is_match("specifica.baz")); 434 | } 435 | 436 | #[test] 437 | fn test_pattern_is_match_many_wildcard_at_start() { 438 | let pat = Pattern { 439 | separator: Some('.'), 440 | tokens: vec![ 441 | PatternToken::ManyWildcard, 442 | PatternToken::Exact(".bar".into()), 443 | ], 444 | }; 445 | assert!(pat.is_match("foo.bar")); 446 | assert!(pat.is_match("grok.bar")); 447 | assert!(pat.is_match("should.match.bar")); 448 | assert!(pat.is_match(".bar")); 449 | assert!(!pat.is_match("")); 450 | assert!(!pat.is_match("specifi")); 451 | assert!(!pat.is_match("specifica.baz")); 452 | } 453 | 454 | #[test] 455 | fn test_pattern_is_match_any_char() { 456 | let pat = Pattern { 457 | separator: Some('.'), 458 | tokens: vec![ 459 | PatternToken::Exact("fo".into()), 460 | PatternToken::AnyChar, 461 | PatternToken::Exact(".baz".into()), 462 | ], 463 | }; 464 | assert!(pat.is_match("foo.baz")); 465 | assert!(pat.is_match("foe.baz")); 466 | assert!(pat.is_match("foi.baz")); 467 | assert!(!pat.is_match("")); 468 | assert!(!pat.is_match("fooo.baz")); 469 | assert!(!pat.is_match("fo.baz")); 470 | } 471 | -------------------------------------------------------------------------------- /src/test_introspection_doc.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | --------------------------------------------------------------------------------