├── .github └── workflows │ └── test.yml ├── .gitignore ├── .nix └── nixops.nix ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── README.md ├── default.nix ├── glade └── ui.glade ├── imgs ├── screenshot.png └── screenshot2.png ├── nix ├── default.nix └── overlay.nix ├── rustfmt.toml ├── shell.nix ├── src ├── bin │ └── nix-query-tree-viewer.rs ├── lib.rs ├── nix_query_tree.rs ├── nix_query_tree │ ├── exec_nix_store.rs │ └── parsing.rs ├── opts.rs ├── tree.rs ├── ui.rs └── ui │ ├── builder.rs │ ├── css.rs │ ├── menu.rs │ ├── prelude.rs │ ├── stack.rs │ ├── stack │ ├── raw.rs │ ├── tree.rs │ └── tree │ │ ├── columns.rs │ │ ├── path.rs │ │ ├── signals.rs │ │ └── store.rs │ ├── state.rs │ ├── statusbar.rs │ └── toolbar.rs ├── style └── style.css └── tests └── parsing.rs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Test" 2 | on: 3 | pull_request: 4 | push: 5 | jobs: 6 | tests: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: cachix/install-nix-action@v14 11 | - run: nix-build 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | 4 | # glade saves backup files with a tilde at the end of the filename 5 | *~ 6 | 7 | # nix result output files 8 | result 9 | -------------------------------------------------------------------------------- /.nix/nixops.nix: -------------------------------------------------------------------------------- 1 | # This file can be used with `nixops` to create a virtual machine that has 2 | # nix-query-tree-viewer installed. 3 | # 4 | # I use this to test out nix-query-tree-viewer in a desktop environment that 5 | # provides window decorations. 6 | # 7 | # On my development machine, I use XMonad as a Window Manager, so there are 8 | # no window decorations for any X application. This file creates a VM 9 | # with nix-query-tree-viewer installed in Gnome 3. This lets you see what 10 | # nix-query-tree-viewer looks like when it has window decorations, a title bar, 11 | # etc. 12 | # 13 | # A virtual machine can be created based on this file with the following 14 | # commands: 15 | # 16 | # $ nixops create --deployment nix-query-tree-viewer-test .nix/nixops.nix 17 | # $ nixops deploy --deployment nix-query-tree-viewer-test 18 | # 19 | # This should open up a VirtualBox machine and start installing Gnome 3, 20 | # nix-query-tree-viewer, etc. 21 | # 22 | # You should be able to login with the username "myuser" and password "foobar". 23 | # 24 | # When you are done you can destroy the machine and delete the deployment: 25 | # 26 | # $ nixops destroy --deployment nix-query-tree-viewer-test 27 | # $ nixops delete --deployment nix-query-tree-viewer-test 28 | # 29 | 30 | { 31 | network.description = "Gnome With nix-query-tree-viewer"; 32 | 33 | nqtv-machine = 34 | { config, pkgs, ...}: 35 | { 36 | imports = [ ]; 37 | 38 | deployment = { 39 | targetEnv = "virtualbox"; 40 | virtualbox = { 41 | # disks.disk1.size = 20480; 42 | headless = false; 43 | memorySize = 2024; 44 | vcpu = 1; 45 | }; 46 | }; 47 | 48 | environment = { 49 | systemPackages = 50 | let 51 | pkgList = with pkgs; [ 52 | acpi aspell aspellDicts.en autojump bash bash-completion bc 53 | chromium curl dmenu emacs evince file firefoxWrapper gcc geeqie 54 | gimp gitAndTools.gitFull gitAndTools.hub gnumake gnupg hexchat 55 | htop imagemagick jq k2pdfopt ltrace manpages ncurses 56 | nix-bash-completions nixops p7zip pkgconfig psmisc python3 57 | redshift roxterm screen strace tree unzip usbutils vimHugeX wget 58 | wirelesstools xfce.terminal xorg.xbacklight xorg.xmodmap 59 | xscreensaver xterm zlib 60 | ]; 61 | nix-query-tree-viewer = import ../default.nix; 62 | in [ nix-query-tree-viewer ] ++ pkgList; 63 | variables.EDITOR = "vim"; 64 | }; 65 | 66 | fonts.fonts = with pkgs; [ 67 | dejavu_fonts ipafont source-code-pro ttf_bitstream_vera 68 | ]; 69 | 70 | i18n = { 71 | consoleFont = "Lat2-Terminus16"; 72 | consoleKeyMap = "us"; 73 | defaultLocale = "en_US.UTF-8"; 74 | inputMethod = { 75 | enabled = "fcitx"; 76 | fcitx.engines = with pkgs.fcitx-engines; [ mozc ]; 77 | }; 78 | }; 79 | 80 | programs.bash.enableCompletion = true; 81 | 82 | services = { 83 | xserver = { 84 | enable = true; 85 | layout = "us"; 86 | desktopManager.gnome3.enable = true; 87 | }; 88 | openssh = { 89 | enable = true; 90 | forwardX11 = true; 91 | challengeResponseAuthentication = true; 92 | passwordAuthentication = true; 93 | permitRootLogin = "yes"; 94 | }; 95 | }; 96 | 97 | security.sudo = { 98 | enable = true; 99 | extraConfig = '' 100 | %wheel ALL=(ALL:ALL) NOPASSWD: ${pkgs.systemd}/bin/poweroff 101 | %wheel ALL=(ALL:ALL) NOPASSWD: ${pkgs.systemd}/bin/reboot 102 | %wheel ALL=(ALL:ALL) NOPASSWD: ${pkgs.systemd}/bin/systemctl suspend 103 | ''; 104 | }; 105 | 106 | users.extraUsers.myuser = { 107 | extraGroups = [ "audio" "systemd-journal" "video" "wheel" ]; 108 | initialPassword = "foobar"; 109 | isNormalUser = true; 110 | }; 111 | }; 112 | } 113 | 114 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0.2.1 3 | 4 | * Cleanup GTK ui. 5 | [#5](https://github.com/cdepillabout/nix-query-tree-viewer/pull/5). Thanks 6 | [@turboMaCk](https://github.com/turboMaCk)! 7 | 8 | * Make sure the unicode-style output from `nix-store --query --tree` is parsable. 9 | [#10](https://github.com/cdepillabout/nix-query-tree-viewer/pull/10). 10 | 11 | * Make sure `nix-query-tree-viewer` can be built with recent versions of 12 | rustc. 7717c1df8273196eda7 13 | 14 | ## 0.2.0 15 | 16 | * Add support for git-style short hashes. 17 | [#3](https://github.com/cdepillabout/nix-query-tree-viewer/pull/3). Thanks 18 | [@jonascarpay](https://github.com/jonascarpay)! 19 | 20 | ## 0.1.0 21 | 22 | * Initial release. 23 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ansi_term" 5 | version = "0.11.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 8 | dependencies = [ 9 | "winapi", 10 | ] 11 | 12 | [[package]] 13 | name = "arrayvec" 14 | version = "0.5.2" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 17 | 18 | [[package]] 19 | name = "atk" 20 | version = "0.8.0" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "444daefa55f229af145ea58d77efd23725024ee1f6f3102743709aa6b18c663e" 23 | dependencies = [ 24 | "atk-sys", 25 | "bitflags", 26 | "glib", 27 | "glib-sys", 28 | "gobject-sys", 29 | "libc", 30 | ] 31 | 32 | [[package]] 33 | name = "atk-sys" 34 | version = "0.9.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "e552c1776737a4c80110d06b36d099f47c727335f9aaa5d942a72b6863a8ec6f" 37 | dependencies = [ 38 | "glib-sys", 39 | "gobject-sys", 40 | "libc", 41 | "pkg-config", 42 | ] 43 | 44 | [[package]] 45 | name = "atty" 46 | version = "0.2.14" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 49 | dependencies = [ 50 | "hermit-abi", 51 | "libc", 52 | "winapi", 53 | ] 54 | 55 | [[package]] 56 | name = "autocfg" 57 | version = "1.0.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 60 | 61 | [[package]] 62 | name = "bitflags" 63 | version = "1.3.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 66 | 67 | [[package]] 68 | name = "cairo-rs" 69 | version = "0.8.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "157049ba9618aa3a61c39d5d785102c04d3b1f40632a706c621a9aedc21e6084" 72 | dependencies = [ 73 | "bitflags", 74 | "cairo-sys-rs", 75 | "glib", 76 | "glib-sys", 77 | "gobject-sys", 78 | "libc", 79 | ] 80 | 81 | [[package]] 82 | name = "cairo-sys-rs" 83 | version = "0.9.2" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "ff65ba02cac715be836f63429ab00a767d48336efc5497c5637afb53b4f14d63" 86 | dependencies = [ 87 | "glib-sys", 88 | "libc", 89 | "pkg-config", 90 | ] 91 | 92 | [[package]] 93 | name = "cc" 94 | version = "1.0.70" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" 97 | 98 | [[package]] 99 | name = "cfg-if" 100 | version = "1.0.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 103 | 104 | [[package]] 105 | name = "clap" 106 | version = "2.33.3" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 109 | dependencies = [ 110 | "ansi_term", 111 | "atty", 112 | "bitflags", 113 | "strsim", 114 | "textwrap", 115 | "unicode-width", 116 | "vec_map", 117 | ] 118 | 119 | [[package]] 120 | name = "futures-channel" 121 | version = "0.3.17" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" 124 | dependencies = [ 125 | "futures-core", 126 | ] 127 | 128 | [[package]] 129 | name = "futures-core" 130 | version = "0.3.17" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" 133 | 134 | [[package]] 135 | name = "futures-executor" 136 | version = "0.3.17" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" 139 | dependencies = [ 140 | "futures-core", 141 | "futures-task", 142 | "futures-util", 143 | ] 144 | 145 | [[package]] 146 | name = "futures-io" 147 | version = "0.3.17" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" 150 | 151 | [[package]] 152 | name = "futures-macro" 153 | version = "0.3.17" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" 156 | dependencies = [ 157 | "autocfg", 158 | "proc-macro-hack", 159 | "proc-macro2", 160 | "quote", 161 | "syn", 162 | ] 163 | 164 | [[package]] 165 | name = "futures-task" 166 | version = "0.3.17" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" 169 | 170 | [[package]] 171 | name = "futures-util" 172 | version = "0.3.17" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" 175 | dependencies = [ 176 | "autocfg", 177 | "futures-core", 178 | "futures-macro", 179 | "futures-task", 180 | "pin-project-lite", 181 | "pin-utils", 182 | "proc-macro-hack", 183 | "proc-macro-nested", 184 | "slab", 185 | ] 186 | 187 | [[package]] 188 | name = "gdk" 189 | version = "0.12.1" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "fbe5e8772fc0865c52460cdd7a59d7d47700f44d9809d1dd00eecceb769a7589" 192 | dependencies = [ 193 | "bitflags", 194 | "cairo-rs", 195 | "cairo-sys-rs", 196 | "gdk-pixbuf", 197 | "gdk-sys", 198 | "gio", 199 | "gio-sys", 200 | "glib", 201 | "glib-sys", 202 | "gobject-sys", 203 | "libc", 204 | "pango", 205 | ] 206 | 207 | [[package]] 208 | name = "gdk-pixbuf" 209 | version = "0.8.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "e248220c46b329b097d4b158d2717f8c688f16dd76d0399ace82b3e98062bdd7" 212 | dependencies = [ 213 | "gdk-pixbuf-sys", 214 | "gio", 215 | "gio-sys", 216 | "glib", 217 | "glib-sys", 218 | "gobject-sys", 219 | "libc", 220 | ] 221 | 222 | [[package]] 223 | name = "gdk-pixbuf-sys" 224 | version = "0.9.1" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "d8991b060a9e9161bafd09bf4a202e6fd404f5b4dd1a08d53a1e84256fb34ab0" 227 | dependencies = [ 228 | "gio-sys", 229 | "glib-sys", 230 | "gobject-sys", 231 | "libc", 232 | "pkg-config", 233 | ] 234 | 235 | [[package]] 236 | name = "gdk-sys" 237 | version = "0.9.1" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "6adf679e91d1bff0c06860287f80403e7db54c2d2424dce0a470023b56c88fbb" 240 | dependencies = [ 241 | "cairo-sys-rs", 242 | "gdk-pixbuf-sys", 243 | "gio-sys", 244 | "glib-sys", 245 | "gobject-sys", 246 | "libc", 247 | "pango-sys", 248 | "pkg-config", 249 | ] 250 | 251 | [[package]] 252 | name = "gio" 253 | version = "0.8.1" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "0cd10f9415cce39b53f8024bf39a21f84f8157afa52da53837b102e585a296a5" 256 | dependencies = [ 257 | "bitflags", 258 | "futures-channel", 259 | "futures-core", 260 | "futures-io", 261 | "futures-util", 262 | "gio-sys", 263 | "glib", 264 | "glib-sys", 265 | "gobject-sys", 266 | "lazy_static", 267 | "libc", 268 | ] 269 | 270 | [[package]] 271 | name = "gio-sys" 272 | version = "0.9.1" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "4fad225242b9eae7ec8a063bb86974aca56885014672375e5775dc0ea3533911" 275 | dependencies = [ 276 | "glib-sys", 277 | "gobject-sys", 278 | "libc", 279 | "pkg-config", 280 | ] 281 | 282 | [[package]] 283 | name = "glib" 284 | version = "0.9.3" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "40fb573a09841b6386ddf15fd4bc6655b4f5b106ca962f57ecaecde32a0061c0" 287 | dependencies = [ 288 | "bitflags", 289 | "futures-channel", 290 | "futures-core", 291 | "futures-executor", 292 | "futures-task", 293 | "futures-util", 294 | "glib-sys", 295 | "gobject-sys", 296 | "lazy_static", 297 | "libc", 298 | ] 299 | 300 | [[package]] 301 | name = "glib-sys" 302 | version = "0.9.1" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "95856f3802f446c05feffa5e24859fe6a183a7cb849c8449afc35c86b1e316e2" 305 | dependencies = [ 306 | "libc", 307 | "pkg-config", 308 | ] 309 | 310 | [[package]] 311 | name = "gobject-sys" 312 | version = "0.9.1" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "31d1a804f62034eccf370006ccaef3708a71c31d561fee88564abe71177553d9" 315 | dependencies = [ 316 | "glib-sys", 317 | "libc", 318 | "pkg-config", 319 | ] 320 | 321 | [[package]] 322 | name = "gtk" 323 | version = "0.8.1" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "87e1e8d70290239c668594002d1b174fcc7d7ef5d26670ee141490ede8facf8f" 326 | dependencies = [ 327 | "atk", 328 | "bitflags", 329 | "cairo-rs", 330 | "cairo-sys-rs", 331 | "cc", 332 | "gdk", 333 | "gdk-pixbuf", 334 | "gdk-pixbuf-sys", 335 | "gdk-sys", 336 | "gio", 337 | "gio-sys", 338 | "glib", 339 | "glib-sys", 340 | "gobject-sys", 341 | "gtk-sys", 342 | "lazy_static", 343 | "libc", 344 | "pango", 345 | "pango-sys", 346 | ] 347 | 348 | [[package]] 349 | name = "gtk-sys" 350 | version = "0.9.2" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "53def660c7b48b00b510c81ef2d2fbd3c570f1527081d8d7947f471513e1a4c1" 353 | dependencies = [ 354 | "atk-sys", 355 | "cairo-sys-rs", 356 | "gdk-pixbuf-sys", 357 | "gdk-sys", 358 | "gio-sys", 359 | "glib-sys", 360 | "gobject-sys", 361 | "libc", 362 | "pango-sys", 363 | "pkg-config", 364 | ] 365 | 366 | [[package]] 367 | name = "heck" 368 | version = "0.3.3" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 371 | dependencies = [ 372 | "unicode-segmentation", 373 | ] 374 | 375 | [[package]] 376 | name = "hermit-abi" 377 | version = "0.1.19" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 380 | dependencies = [ 381 | "libc", 382 | ] 383 | 384 | [[package]] 385 | name = "indoc" 386 | version = "0.3.6" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8" 389 | dependencies = [ 390 | "indoc-impl", 391 | "proc-macro-hack", 392 | ] 393 | 394 | [[package]] 395 | name = "indoc-impl" 396 | version = "0.3.6" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0" 399 | dependencies = [ 400 | "proc-macro-hack", 401 | "proc-macro2", 402 | "quote", 403 | "syn", 404 | "unindent", 405 | ] 406 | 407 | [[package]] 408 | name = "lazy_static" 409 | version = "1.4.0" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 412 | 413 | [[package]] 414 | name = "lexical-core" 415 | version = "0.7.6" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" 418 | dependencies = [ 419 | "arrayvec", 420 | "bitflags", 421 | "cfg-if", 422 | "ryu", 423 | "static_assertions", 424 | ] 425 | 426 | [[package]] 427 | name = "libc" 428 | version = "0.2.101" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" 431 | 432 | [[package]] 433 | name = "memchr" 434 | version = "2.4.1" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 437 | 438 | [[package]] 439 | name = "nix-query-tree-viewer" 440 | version = "0.2.1" 441 | dependencies = [ 442 | "gdk", 443 | "gio", 444 | "glib", 445 | "glib-sys", 446 | "gtk", 447 | "gtk-sys", 448 | "indoc", 449 | "nom", 450 | "pango", 451 | "structopt", 452 | ] 453 | 454 | [[package]] 455 | name = "nom" 456 | version = "5.1.2" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 459 | dependencies = [ 460 | "lexical-core", 461 | "memchr", 462 | "version_check", 463 | ] 464 | 465 | [[package]] 466 | name = "pango" 467 | version = "0.8.0" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "1e9c6b728f1be8edb5f9f981420b651d5ea30bdb9de89f1f1262d0084a020577" 470 | dependencies = [ 471 | "bitflags", 472 | "glib", 473 | "glib-sys", 474 | "gobject-sys", 475 | "lazy_static", 476 | "libc", 477 | "pango-sys", 478 | ] 479 | 480 | [[package]] 481 | name = "pango-sys" 482 | version = "0.9.1" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "86b93d84907b3cf0819bff8f13598ba72843bee579d5ebc2502e4b0367b4be7d" 485 | dependencies = [ 486 | "glib-sys", 487 | "gobject-sys", 488 | "libc", 489 | "pkg-config", 490 | ] 491 | 492 | [[package]] 493 | name = "pin-project-lite" 494 | version = "0.2.7" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" 497 | 498 | [[package]] 499 | name = "pin-utils" 500 | version = "0.1.0" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 503 | 504 | [[package]] 505 | name = "pkg-config" 506 | version = "0.3.19" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 509 | 510 | [[package]] 511 | name = "proc-macro-error" 512 | version = "1.0.4" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 515 | dependencies = [ 516 | "proc-macro-error-attr", 517 | "proc-macro2", 518 | "quote", 519 | "syn", 520 | "version_check", 521 | ] 522 | 523 | [[package]] 524 | name = "proc-macro-error-attr" 525 | version = "1.0.4" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 528 | dependencies = [ 529 | "proc-macro2", 530 | "quote", 531 | "version_check", 532 | ] 533 | 534 | [[package]] 535 | name = "proc-macro-hack" 536 | version = "0.5.19" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 539 | 540 | [[package]] 541 | name = "proc-macro-nested" 542 | version = "0.1.7" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" 545 | 546 | [[package]] 547 | name = "proc-macro2" 548 | version = "1.0.29" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" 551 | dependencies = [ 552 | "unicode-xid", 553 | ] 554 | 555 | [[package]] 556 | name = "quote" 557 | version = "1.0.9" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 560 | dependencies = [ 561 | "proc-macro2", 562 | ] 563 | 564 | [[package]] 565 | name = "ryu" 566 | version = "1.0.5" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 569 | 570 | [[package]] 571 | name = "slab" 572 | version = "0.4.4" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" 575 | 576 | [[package]] 577 | name = "static_assertions" 578 | version = "1.1.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 581 | 582 | [[package]] 583 | name = "strsim" 584 | version = "0.8.0" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 587 | 588 | [[package]] 589 | name = "structopt" 590 | version = "0.3.23" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "bf9d950ef167e25e0bdb073cf1d68e9ad2795ac826f2f3f59647817cf23c0bfa" 593 | dependencies = [ 594 | "clap", 595 | "lazy_static", 596 | "structopt-derive", 597 | ] 598 | 599 | [[package]] 600 | name = "structopt-derive" 601 | version = "0.4.16" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "134d838a2c9943ac3125cf6df165eda53493451b719f3255b2a26b85f772d0ba" 604 | dependencies = [ 605 | "heck", 606 | "proc-macro-error", 607 | "proc-macro2", 608 | "quote", 609 | "syn", 610 | ] 611 | 612 | [[package]] 613 | name = "syn" 614 | version = "1.0.76" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" 617 | dependencies = [ 618 | "proc-macro2", 619 | "quote", 620 | "unicode-xid", 621 | ] 622 | 623 | [[package]] 624 | name = "textwrap" 625 | version = "0.11.0" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 628 | dependencies = [ 629 | "unicode-width", 630 | ] 631 | 632 | [[package]] 633 | name = "unicode-segmentation" 634 | version = "1.8.0" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 637 | 638 | [[package]] 639 | name = "unicode-width" 640 | version = "0.1.8" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 643 | 644 | [[package]] 645 | name = "unicode-xid" 646 | version = "0.2.2" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 649 | 650 | [[package]] 651 | name = "unindent" 652 | version = "0.1.7" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" 655 | 656 | [[package]] 657 | name = "vec_map" 658 | version = "0.8.2" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 661 | 662 | [[package]] 663 | name = "version_check" 664 | version = "0.9.3" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 667 | 668 | [[package]] 669 | name = "winapi" 670 | version = "0.3.9" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 673 | dependencies = [ 674 | "winapi-i686-pc-windows-gnu", 675 | "winapi-x86_64-pc-windows-gnu", 676 | ] 677 | 678 | [[package]] 679 | name = "winapi-i686-pc-windows-gnu" 680 | version = "0.4.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 683 | 684 | [[package]] 685 | name = "winapi-x86_64-pc-windows-gnu" 686 | version = "0.4.0" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 689 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nix-query-tree-viewer" 3 | version = "0.2.1" 4 | authors = ["cdepillabout@gmail.com"] 5 | edition = "2018" 6 | description = "A GTK viewer for `nix-store --query --tree` output" 7 | license = "MIT" 8 | repository = "https://github.com/cdepillabout/nix-query-tree-viewer" 9 | readme = "README.md" 10 | keywords = ["gtk", "nix", "nix-store", "gui"] 11 | categories = ["gui"] 12 | 13 | [dependencies] 14 | gdk = "0.12.0" 15 | gio = "0.8.0" 16 | glib = "0.9.1" 17 | glib-sys = "0.9.1" 18 | gtk-sys = "0.9.2" 19 | nom = "5.1.0" 20 | pango = "0.8.0" 21 | structopt = "0.3.9" 22 | 23 | [dependencies.gtk] 24 | version = "0.8.0" 25 | features = ["v3_22"] 26 | 27 | [dev-dependencies] 28 | indoc = "0.3.4" 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nix-query-tree-viewer 2 | 3 | [![Actions Status](https://github.com/cdepillabout/nix-query-tree-viewer/workflows/Test/badge.svg)](https://github.com/cdepillabout/nix-query-tree-viewer/actions) 4 | [![crates.io](https://img.shields.io/crates/v/nix-query-tree-viewer.svg)](https://crates.io/crates/nix-query-tree-viewer) 5 | [![dependency status](https://deps.rs/repo/github/cdepillabout/nix-query-tree-viewer/status.svg)](https://deps.rs/repo/github/cdepillabout/nix-query-tree-viewer) 6 | ![MIT license](https://img.shields.io/badge/license-MIT-blue.svg) 7 | 8 | `nix-query-tree-viewer` is a convenient way to visualize the output of 9 | the dependencies of a given path in the Nix store. 10 | 11 | ![image of nix-query-tree-viewer](./imgs/screenshot.png) 12 | 13 | This is the same tree information that `nix-store --query --tree ` outputs, 14 | but `nix-query-tree-viewer` makes it easier to understand and interact with. 15 | 16 | ## Usage 17 | 18 | You can run `nix-query-tree-viewer` by passing it a path in the Nix store: 19 | 20 | ```console 21 | $ nix-query-tree-viewer /nix/store/ghzg4kg0sjif58smj2lfm2bdvjwim85y-gcc-wrapper-7.4.0 22 | ``` 23 | 24 | ## Installing 25 | 26 | `nix-query-tree-viewer` can be installed with either Nix or Cargo. 27 | 28 | Installing with `nix-env`: 29 | 30 | ```console 31 | $ nix-env -f channel:nixos-unstable -iA nix-query-tree-viewer 32 | ``` 33 | 34 | This is convenient if you just want to use `nix-query-tree-viewer`. 35 | 36 | Installing with `cargo`: 37 | 38 | ```console 39 | $ cargo install nix-query-tree-viewer 40 | ``` 41 | 42 | You'll need to have GTK libraries available in your environment for this to work. 43 | 44 | ## Why use `nix-query-tree-viewer`? 45 | 46 | The command `nix-store --query --tree` can be used to see the dependencies of a 47 | path in the Nix store in a tree format: 48 | 49 | ```console 50 | $ nix-store --query --tree /nix/store/ghzg4kg0sjif58smj2lfm2bdvjwim85y-gcc-wrapper-7.4.0 51 | /nix/store/ghzg4kg0sjif58smj2lfm2bdvjwim85y-gcc-wrapper-7.4.0 52 | +---/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27 53 | | +---/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27 [...] 54 | +---/nix/store/cinw572b38aln37glr0zb8lxwrgaffl4-bash-4.4-p23 55 | | +---/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27 [...] 56 | | +---/nix/store/cinw572b38aln37glr0zb8lxwrgaffl4-bash-4.4-p23 [...] 57 | +---/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib 58 | | +---/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27 [...] 59 | | +---/nix/store/hlnxw4k6931bachvg5sv0cyaissimswb-gcc-7.4.0-lib [...] 60 | +---/nix/store/f5wl80zkrd3fc1jxsljmnpn7y02lz6v1-glibc-2.27-bin 61 | | +---/nix/store/681354n3k44r8z90m35hm8945vsp95h1-glibc-2.27 [...] 62 | ... 63 | ``` 64 | 65 | This is fine for simple derivations, but it gets very complicated for paths 66 | with deep dependency trees. 67 | 68 | `nix-query-tree-viewer` helps with this by folding branches of the tree by 69 | default. Clicking on a branch allows you to drill down into interesting 70 | branches. 71 | 72 | For example, here is the same picture as above, but with two of the branches 73 | open: 74 | 75 | ![image of nix-query-tree-viewer with two branches open](./imgs/screenshot2.png) 76 | 77 | ## Finding Paths for Derivations 78 | 79 | You can use `nix-build` and `nix-instantiate` to easily find the paths for 80 | derivations. 81 | 82 | For instance, if you want to find the path of `gcc` in the Nix store, you can 83 | use `nix-build`: 84 | 85 | ```console 86 | $ nix-build '' -A gcc --no-out-link 87 | ... 88 | /nix/store/ghzg4kg0sjif58smj2lfm2bdvjwim85y-gcc-wrapper-7.4.0 89 | ``` 90 | 91 | If you want to find the path of the `.drv` file for `gcc`, you can use 92 | `nix-instantiate`: 93 | 94 | ```console 95 | $ nix-instantiate '' -A gcc 96 | ... 97 | /nix/store/dyxdjxyszmlz29mb0jr9qkncj5l41dai-gcc-wrapper-7.4.0.drv 98 | ``` 99 | 100 | You should be able to pass both 101 | `/nix/store/ghzg4kg0sjif58smj2lfm2bdvjwim85y-gcc-wrapper-7.4.0` and 102 | `/nix/store/dyxdjxyszmlz29mb0jr9qkncj5l41dai-gcc-wrapper-7.4.0.drv` to `nix-query-tree-viewer`. 103 | 104 | In general, passing the output of `nix-build` to `nix-query-tree-viewer` will 105 | let you see the run-time dependencies of a derivation, while passing the output 106 | of `nix-instantiate` will let you see the build-time dependencies of a 107 | derivation. 108 | 109 | ## Contributions 110 | 111 | Feel free to open an issue or PR for any 112 | bugs/problems/suggestions/improvements. 113 | 114 | ## Development 115 | 116 | You can get into a development environment by running `nix-shell`. 117 | 118 | This `nix-shell` provides the necessary system libraries for building, as well 119 | as `rustup`. 120 | 121 | `rustup` can be used to fetch the latest Rust compiler, as well as related 122 | tools: 123 | 124 | ```console 125 | $ rustup toolchain install 1.55.0 126 | ``` 127 | 128 | You should now have `cargo` and `rustc`, which can be used for building the project: 129 | 130 | ```console 131 | $ cargo build 132 | ``` 133 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | 2 | (import ./nix).nix-query-tree-viewer 3 | 4 | -------------------------------------------------------------------------------- /glade/ui.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | dialog 8 | Glade 9 | image-missing 10 | 11 | 12 | 13 | 14 | 15 | False 16 | vertical 17 | 2 18 | 19 | 20 | False 21 | end 22 | 23 | 24 | False 25 | False 26 | 0 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | True 38 | False 39 | gtk-ok 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | treeStore 57 | 58 | 59 | False 60 | 61 | 62 | 63 | 64 | 65 | True 66 | False 67 | vertical 68 | 69 | 70 | True 71 | False 72 | 73 | 74 | True 75 | False 76 | _File 77 | True 78 | 79 | 80 | True 81 | False 82 | 83 | 84 | gtk-quit 85 | True 86 | False 87 | True 88 | True 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | True 98 | False 99 | _Help 100 | True 101 | 102 | 103 | True 104 | False 105 | 106 | 107 | gtk-about 108 | True 109 | False 110 | True 111 | True 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | False 121 | False 122 | 0 123 | 124 | 125 | 126 | 127 | True 128 | False 129 | 8 130 | 8 131 | 8 132 | 8 133 | 10 134 | 135 | 136 | True 137 | False 138 | start 139 | 8 140 | 141 | 142 | True 143 | False 144 | Sort: 145 | 146 | 147 | False 148 | True 149 | 0 150 | 151 | 152 | 153 | 154 | True 155 | False 156 | 0 157 | sortComboBoxNixStoreOriginalOutput 158 | 159 | nix-store Original Output 160 | Alphabetical by Hash 161 | Alphabetical by Drv Name 162 | 163 | 164 | 165 | False 166 | True 167 | 1 168 | 169 | 170 | 171 | 172 | True 173 | False 174 | View: 175 | 176 | 177 | False 178 | True 179 | 2 180 | 181 | 182 | 183 | 184 | True 185 | False 186 | 0 187 | viewComboBoxOnlyDrvName 188 | 189 | Full Path 190 | Hash and Drv Name 191 | Short hash and Drv Name 192 | Only Drv Name 193 | 194 | 195 | 196 | False 197 | True 198 | 3 199 | 200 | 201 | 202 | 203 | False 204 | True 205 | 0 206 | 207 | 208 | 209 | 210 | True 211 | False 212 | end 213 | True 214 | 215 | 216 | 500 217 | True 218 | True 219 | 8 220 | edit-find-symbolic 221 | False 222 | False 223 | 224 | 225 | False 226 | True 227 | 0 228 | 229 | 230 | 231 | 232 | Search 233 | True 234 | True 235 | True 236 | searchButtonImage 237 | True 238 | 239 | 240 | False 241 | True 242 | 1 243 | 244 | 245 | 246 | 247 | False 248 | True 249 | 1 250 | 251 | 252 | 253 | 254 | False 255 | True 256 | 1 257 | 258 | 259 | 260 | 261 | True 262 | False 263 | center 264 | 8 265 | stack 266 | 267 | 268 | False 269 | True 270 | 2 271 | 272 | 273 | 274 | 275 | True 276 | False 277 | 278 | 279 | True 280 | True 281 | in 282 | 283 | 284 | True 285 | True 286 | treeModelSort 287 | 0 288 | both 289 | True 290 | True 291 | 292 | 293 | 294 | 295 | 296 | True 297 | Item 298 | 299 | 300 | 301 | 0 302 | 303 | 304 | 305 | 306 | 307 | 308 | True 309 | Repeat 310 | 311 | 312 | blue 313 | single 314 | 315 | 316 | 1 317 | 318 | 319 | 320 | 321 | 324 | 325 | 326 | 327 | 328 | page0 329 | Tree View 330 | 331 | 332 | 333 | 334 | True 335 | True 336 | in 337 | 338 | 339 | True 340 | True 341 | False 342 | rawTextBuffer 343 | True 344 | 345 | 346 | 347 | 348 | page1 349 | Raw 350 | 1 351 | 352 | 353 | 354 | 355 | True 356 | True 357 | 3 358 | 359 | 360 | 361 | 362 | True 363 | False 364 | 365 | 366 | False 367 | False 368 | 4 369 | 370 | 371 | 372 | 373 | 374 | 375 | False 376 | True 377 | True 378 | dialog 379 | appWindow 380 | error 381 | close 382 | Error running nix-store 383 | 384 | 385 | 386 | 387 | 388 | False 389 | vertical 390 | 2 391 | 392 | 393 | False 394 | True 395 | end 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | False 405 | False 406 | 0 407 | 408 | 409 | 410 | 411 | 412 | 413 | -------------------------------------------------------------------------------- /imgs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdepillabout/nix-query-tree-viewer/47b191b485adfc64c914f7c5e6babe5c8ced38bf/imgs/screenshot.png -------------------------------------------------------------------------------- /imgs/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdepillabout/nix-query-tree-viewer/47b191b485adfc64c914f7c5e6babe5c8ced38bf/imgs/screenshot2.png -------------------------------------------------------------------------------- /nix/default.nix: -------------------------------------------------------------------------------- 1 | 2 | let 3 | # nixpkgs master as of 2020-01-26 4 | nixpkgsSrc = builtins.fetchTarball { 5 | url = https://github.com/NixOS/nixpkgs/archive/1e5c35dfbc8e0ad8c35aa1a6446f442ca1ec5234.tar.gz; 6 | sha256 = "0q7q7f7qhcag6mm3sp10gla5k0f9z1b68h04navrfd4dbz8zza0c"; 7 | }; 8 | 9 | overlay = import ./overlay.nix; 10 | 11 | nixpkgs = import nixpkgsSrc { 12 | overlays = [ overlay ]; 13 | }; 14 | in 15 | nixpkgs 16 | -------------------------------------------------------------------------------- /nix/overlay.nix: -------------------------------------------------------------------------------- 1 | 2 | final: prev: { 3 | nix-query-tree-viewer = 4 | final.rustPlatform.buildRustPackage rec { 5 | name = "nix-query-tree-viewer-${version}"; 6 | version = "0.2.1"; 7 | 8 | src = final.nix-gitignore.gitignoreSource [] ./..; 9 | 10 | buildInputs = [ 11 | final.glib 12 | final.gtk3 13 | ]; 14 | 15 | cargoSha256 = "sha256-NSLBIvgo5EdCvZq52d+UbAa7K4uOST++2zbhO9DW38E="; 16 | }; 17 | 18 | nix-query-tree-viewer-shell = final.stdenv.mkDerivation { 19 | name = "nix-query-tree-viewer-rust-env"; 20 | nativeBuildInputs = [ 21 | # Things like cargo, rustc, rustfmt, and clippy can be installed with commands like 22 | # 23 | # $ rustup component add clippy 24 | final.rustup 25 | 26 | # Some rust libraries use pkgconfig. 27 | final.pkgconfig 28 | 29 | # For creating the UI. 30 | final.gnome3.glade 31 | ]; 32 | buildInputs = [ 33 | final.openssl 34 | 35 | final.glib 36 | final.gtk3 37 | ]; 38 | 39 | shellHook = '' 40 | # TODO: This clobbers MANPATH if it is already set. 41 | # export MANPATH=":${final.xorg.libxcb.man}/share/man" 42 | ''; 43 | 44 | # Set Environment Variables 45 | #RUST_BACKTRACE = 1; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | 2 | (import ./nix).nix-query-tree-viewer-shell 3 | -------------------------------------------------------------------------------- /src/bin/nix-query-tree-viewer.rs: -------------------------------------------------------------------------------- 1 | extern crate nix_query_tree_viewer; 2 | 3 | fn main() { 4 | nix_query_tree_viewer::default_main() 5 | } 6 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![warn(clippy::all, clippy::pedantic)] 3 | 4 | pub mod nix_query_tree; 5 | pub mod tree; 6 | 7 | mod opts; 8 | mod ui; 9 | 10 | pub fn default_main() { 11 | ui::run(); 12 | } 13 | -------------------------------------------------------------------------------- /src/nix_query_tree.rs: -------------------------------------------------------------------------------- 1 | pub mod exec_nix_store; 2 | pub mod parsing; 3 | 4 | use super::tree::{Path, Tree, TreePathMap}; 5 | use std::path::PathBuf; 6 | use std::str::FromStr; 7 | 8 | /// This corresponds to a nix store path. 9 | /// 10 | /// ``` 11 | /// use nix_query_tree_viewer::nix_query_tree::NixQueryDrv; 12 | /// 13 | /// let nix_query_drv = 14 | /// NixQueryDrv::from("/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10"); 15 | /// ``` 16 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 17 | pub struct NixQueryDrv(PathBuf); 18 | 19 | impl> From<&T> for NixQueryDrv { 20 | fn from(s: &T) -> NixQueryDrv { 21 | NixQueryDrv(PathBuf::from(s.as_ref().to_os_string())) 22 | } 23 | } 24 | 25 | impl std::ops::Deref for NixQueryDrv { 26 | type Target = std::path::Path; 27 | 28 | fn deref(&self) -> &std::path::Path { 29 | &self.0 30 | } 31 | } 32 | 33 | impl NixQueryDrv { 34 | pub fn cmp_hash(&self, other: &Self) -> std::cmp::Ordering { 35 | self.0.cmp(&other.0) 36 | } 37 | 38 | pub fn cmp_drv_name(&self, other: &Self) -> std::cmp::Ordering { 39 | self.drv_name().cmp(&other.drv_name()) 40 | } 41 | 42 | /// Pull out the hash and derivation name from a `NixQueryDrv` 43 | /// 44 | /// ``` 45 | /// use nix_query_tree_viewer::nix_query_tree::NixQueryDrv; 46 | /// 47 | /// let nix_query_drv = 48 | /// NixQueryDrv::from("/nix/store/az4kl5slhbkmmy4vj98z3hzxxkan7zza-gnugrep-3.3"); 49 | /// assert_eq!( 50 | /// nix_query_drv.hash_and_drv_name(), 51 | /// String::from("az4kl5slhbkmmy4vj98z3hzxxkan7zza-gnugrep-3.3") 52 | /// ); 53 | /// ``` 54 | pub fn hash_and_drv_name(&self) -> String { 55 | let drv_str = self.0.to_string_lossy(); 56 | String::from((drv_str).trim_start_matches("/nix/store/")) 57 | } 58 | 59 | /// Pull out a truncated hash and derivation name from a `NixQueryDrv` 60 | /// 61 | /// ``` 62 | /// use nix_query_tree_viewer::nix_query_tree::NixQueryDrv; 63 | /// 64 | /// let nix_query_drv = 65 | /// NixQueryDrv::from("/nix/store/az4kl5slhbkmmy4vj98z3hzxxkan7zza-gnugrep-3.3"); 66 | /// assert_eq!( 67 | /// nix_query_drv.short_hash_and_drv_name(), 68 | /// String::from("az4kl5s..gnugrep-3.3") 69 | /// ); 70 | /// ``` 71 | pub fn short_hash_and_drv_name(&self) -> String { 72 | let drv_str = self.0.to_string_lossy(); 73 | let drv_str_no_store = 74 | String::from(drv_str.trim_start_matches("/nix/store/")); 75 | let option_drv_name = drv_str_no_store 76 | .find('-') 77 | .and_then(|i| drv_str_no_store.get(i + 1..)); 78 | let option_short_hash = drv_str_no_store.get(0..7); 79 | match (option_drv_name, option_short_hash) { 80 | (Some(drv_name), Some(short_hash)) => { 81 | format!("{}..{}", short_hash, drv_name) 82 | } 83 | _ => panic!("Ill-formed nix path"), 84 | } 85 | } 86 | 87 | /// Pull out a derivation name from a `NixQueryDrv`. 88 | /// 89 | /// ``` 90 | /// use nix_query_tree_viewer::nix_query_tree::NixQueryDrv; 91 | /// 92 | /// let nix_query_drv = 93 | /// NixQueryDrv::from("/nix/store/az4kl5slhbkmmy4vj98z3hzxxkan7zza-gnugrep-3.3"); 94 | /// assert_eq!(nix_query_drv.drv_name(), String::from("gnugrep-3.3")); 95 | /// ``` 96 | /// 97 | /// * Panics 98 | /// 99 | /// This panics if the derivation name doesn't have a `-` in it. All nix derivations have 100 | /// a `-` in them after the hash. 101 | pub fn drv_name(&self) -> String { 102 | let drv_str = self.0.to_string_lossy(); 103 | let option_dash_index = drv_str.find('-'); 104 | match option_dash_index { 105 | None => drv_str.into_owned(), 106 | Some(dash_index) => { 107 | let option_just_drv_name = drv_str.get(dash_index + 1..); 108 | match option_just_drv_name { 109 | None => { 110 | panic!("Nix paths will always have a dash in them.") 111 | } 112 | Some(drv_name) => drv_name.to_string(), 113 | } 114 | } 115 | } 116 | } 117 | } 118 | 119 | impl FromStr for NixQueryDrv { 120 | // This should really be never. 121 | type Err = (); 122 | 123 | fn from_str(s: &str) -> Result { 124 | Ok(s.into()) 125 | } 126 | } 127 | 128 | impl std::fmt::Display for NixQueryDrv { 129 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 130 | write!(f, "{}", self.0.to_string_lossy()) 131 | } 132 | } 133 | 134 | /// Whether or not there is a separate entry in this tree that recurses into the dependencies for 135 | /// this nix store entry. 136 | /// 137 | /// See `NixQueryEntry`. 138 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 139 | pub enum Recurse { 140 | Yes, 141 | No, 142 | } 143 | 144 | /// `NixQueryDrv` coupled with a marker for a recursive entry. 145 | /// 146 | /// ``` 147 | /// use nix_query_tree_viewer::nix_query_tree::{NixQueryEntry, Recurse}; 148 | /// use std::str::FromStr; 149 | /// 150 | /// let nix_query_entry = 151 | /// NixQueryEntry::from_str("/nix/store/az4kl5slhbkmmy4vj98z3hzxxkan7zza-gnugrep-3.3 [...]"); 152 | /// let actual_nix_query_entry = 153 | /// NixQueryEntry::new("/nix/store/az4kl5slhbkmmy4vj98z3hzxxkan7zza-gnugrep-3.3", Recurse::Yes); 154 | /// assert_eq!(nix_query_entry, Ok(actual_nix_query_entry)); 155 | /// ``` 156 | /// 157 | #[derive(Clone, Debug, Eq, Hash, PartialEq)] 158 | pub struct NixQueryEntry(pub NixQueryDrv, pub Recurse); 159 | 160 | impl FromStr for NixQueryEntry { 161 | type Err = nom::Err<(String, nom::error::ErrorKind)>; 162 | 163 | fn from_str(s: &str) -> Result { 164 | parsing::nix_query_entry_parser(s).map_err(|err| err.to_owned()) 165 | } 166 | } 167 | 168 | impl std::ops::Deref for NixQueryEntry { 169 | type Target = std::path::Path; 170 | 171 | fn deref(&self) -> &std::path::Path { 172 | &self.0 173 | } 174 | } 175 | 176 | impl NixQueryEntry { 177 | pub fn new(nix_query_drv: &T, recurse: Recurse) -> NixQueryEntry 178 | where 179 | T: ?Sized + AsRef, 180 | { 181 | NixQueryEntry(NixQueryDrv::from(nix_query_drv), recurse) 182 | } 183 | 184 | pub fn cmp_hash(&self, other: &Self) -> std::cmp::Ordering { 185 | self.0.cmp_hash(&other.0) 186 | } 187 | 188 | pub fn cmp_drv_name(&self, other: &Self) -> std::cmp::Ordering { 189 | self.0.cmp_drv_name(&other.0) 190 | } 191 | 192 | pub fn hash_and_drv_name(&self) -> String { 193 | self.0.hash_and_drv_name() 194 | } 195 | 196 | pub fn short_hash_and_drv_name(&self) -> String { 197 | self.0.short_hash_and_drv_name() 198 | } 199 | 200 | pub fn drv_name(&self) -> String { 201 | self.0.drv_name() 202 | } 203 | } 204 | 205 | /// A `Tree` representing the result from `nix store --query --tree`. 206 | /// 207 | /// ``` 208 | /// use indoc::indoc; 209 | /// use nix_query_tree_viewer::nix_query_tree::NixQueryTree; 210 | /// use std::str::FromStr; 211 | /// 212 | /// let raw_tree = indoc!( 213 | /// "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 214 | /// +---/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 215 | /// | +---/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 [...] 216 | /// +---/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...] 217 | /// " 218 | /// ); 219 | /// let nix_query_tree = NixQueryTree::from_str(raw_tree); 220 | /// 221 | /// assert!(nix_query_tree.is_ok()); 222 | /// ``` 223 | #[derive(Clone, Debug, Eq, PartialEq)] 224 | pub struct NixQueryTree(pub Tree); 225 | 226 | impl NixQueryTree { 227 | pub fn path_map(&self) -> NixQueryPathMap { 228 | let tree: &Tree = &self.0; 229 | let tree_path_map = 230 | tree.path_map_map(&|nix_query_entry| nix_query_entry.0.clone()); 231 | NixQueryPathMap(tree_path_map) 232 | } 233 | 234 | pub fn lookup(&self, path: Path) -> Option<&NixQueryEntry> { 235 | self.0.lookup(path) 236 | } 237 | } 238 | 239 | impl FromStr for NixQueryTree { 240 | type Err = nom::Err<(String, nom::error::ErrorKind)>; 241 | 242 | fn from_str(s: &str) -> Result { 243 | parsing::nix_query_tree_parser(s).map_err(|err| err.to_owned()) 244 | } 245 | } 246 | 247 | /// A mapping of `NixQueryDrv` to `TreePath`. This gives an easy way to 248 | /// figure out where a `NixQueryDrv` is an a `NixQueryTree`. 249 | /// 250 | /// ``` 251 | /// use indoc::indoc; 252 | /// use nix_query_tree_viewer::nix_query_tree::{NixQueryDrv, NixQueryTree}; 253 | /// use nix_query_tree_viewer::tree::Path; 254 | /// use std::str::FromStr; 255 | /// 256 | /// let raw_tree = indoc!( 257 | /// "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 258 | /// +---/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 259 | /// | +---/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 [...] 260 | /// +---/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...] 261 | /// +---/nix/store/9ny6szla9dg61jv8q22qbnqsz37465n0-multiple-outputs.sh 262 | /// +---/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 [...] 263 | /// +---/nix/store/5wvmvcc3b7sisirx1vsqbqdis0sd1x5d-cc-wrapper.sh 264 | /// +---/nix/store/5jzbjvnrz85n454inlyxcpgap9i6k6la-pcre-8.43 265 | /// " 266 | /// ); 267 | /// let nix_query_tree = NixQueryTree::from_str(raw_tree).unwrap(); 268 | /// let map = nix_query_tree.path_map(); 269 | /// let pcre_drv = NixQueryDrv::from("/nix/store/5jzbjvnrz85n454inlyxcpgap9i6k6la-pcre-8.43"); 270 | /// let expected_path = Some(Path::from(vec![2, 0, 1])); 271 | /// 272 | /// assert_eq!(map.lookup_first(&pcre_drv), expected_path.as_ref()); 273 | /// ``` 274 | #[derive(Clone, Debug, Eq, PartialEq)] 275 | pub struct NixQueryPathMap(pub TreePathMap); 276 | 277 | impl NixQueryPathMap { 278 | pub fn lookup_first(&self, k: &NixQueryDrv) -> Option<&Path> { 279 | self.0.lookup_first(k) 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/nix_query_tree/exec_nix_store.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | use std::process::{Command, Output}; 3 | 4 | use super::parsing; 5 | use super::{NixQueryEntry, NixQueryPathMap, NixQueryTree}; 6 | use crate::tree; 7 | 8 | #[derive(Clone, Debug, Eq, PartialEq)] 9 | pub enum NixStoreErr { 10 | CommandErr(String), 11 | Utf8Err(String), 12 | NixStoreErr(String), 13 | ParseErr(String), 14 | } 15 | 16 | impl std::fmt::Display for NixStoreErr { 17 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 18 | let string = match self { 19 | NixStoreErr::CommandErr(string) => string, 20 | NixStoreErr::Utf8Err(string) => string, 21 | NixStoreErr::NixStoreErr(string) => string, 22 | NixStoreErr::ParseErr(string) => string, 23 | }; 24 | write!(f, "{}", string) 25 | } 26 | } 27 | 28 | #[derive(Clone, Debug, Eq, PartialEq)] 29 | pub struct NixStoreRes { 30 | pub raw: String, 31 | pub tree: NixQueryTree, 32 | pub map: NixQueryPathMap, 33 | } 34 | 35 | impl NixStoreRes { 36 | pub fn new(raw: &str, tree: NixQueryTree) -> Self { 37 | let map: NixQueryPathMap = tree.path_map(); 38 | NixStoreRes { 39 | raw: String::from(raw), 40 | tree, 41 | map, 42 | } 43 | } 44 | 45 | pub fn lookup_first_query_entry( 46 | &self, 47 | nix_query_entry: &NixQueryEntry, 48 | ) -> Option<&tree::Path> { 49 | self.map.lookup_first(&nix_query_entry.0) 50 | } 51 | } 52 | 53 | #[derive(Clone, Debug, Eq, PartialEq)] 54 | pub struct ExecNixStoreRes { 55 | pub nix_store_path: PathBuf, 56 | pub res: Result, 57 | } 58 | 59 | impl ExecNixStoreRes { 60 | pub fn new( 61 | nix_store_path: &Path, 62 | res: Result, 63 | ) -> Self { 64 | ExecNixStoreRes { 65 | nix_store_path: nix_store_path.to_path_buf(), 66 | res, 67 | } 68 | } 69 | } 70 | 71 | fn nix_store_res(nix_store_path: &Path) -> Result { 72 | let nix_store_output: Output = Command::new("nix-store") 73 | .args(&["--query", "--tree", &nix_store_path.to_string_lossy()]) 74 | .output() 75 | .map_err(|io_err| NixStoreErr::CommandErr(io_err.to_string()))?; 76 | 77 | if nix_store_output.status.success() { 78 | let stdout = from_utf8(nix_store_output.stdout)?; 79 | parsing::nix_query_tree_parser(&stdout) 80 | .map(|nix_query_tree| NixStoreRes::new(&stdout, nix_query_tree)) 81 | .map_err(|nom_err| NixStoreErr::ParseErr(nom_err.to_string())) 82 | } else { 83 | let stderr = from_utf8(nix_store_output.stderr)?; 84 | Err(NixStoreErr::NixStoreErr(stderr)) 85 | } 86 | } 87 | 88 | /// Run `nix-store --query --tree` for the given nix store path. 89 | pub fn run(nix_store_path: &Path) -> ExecNixStoreRes { 90 | ExecNixStoreRes { 91 | nix_store_path: nix_store_path.to_path_buf(), 92 | res: nix_store_res(nix_store_path), 93 | } 94 | } 95 | 96 | /// Convert a `Vec` to a proper utf8 `String`, converting the error to `NixStoreErr::Utf8Err`. 97 | fn from_utf8(i: Vec) -> Result { 98 | String::from_utf8(i) 99 | .map_err(|utf8_err| NixStoreErr::Utf8Err(utf8_err.to_string())) 100 | } 101 | -------------------------------------------------------------------------------- /src/nix_query_tree/parsing.rs: -------------------------------------------------------------------------------- 1 | use nom::character::complete::{newline, space1}; 2 | use nom::combinator::complete; 3 | use nom::multi::{many0, many_m_n}; 4 | use nom::{alt, do_parse, eof, map, named, opt, tag, take_till, IResult}; 5 | 6 | use super::super::tree::Tree; 7 | use super::{NixQueryDrv, NixQueryEntry, NixQueryTree, Recurse}; 8 | 9 | named!(parse_nix_query_drv<&str, NixQueryDrv>, 10 | map!(take_till!(char::is_whitespace), NixQueryDrv::from)); 11 | 12 | named!(parse_recurse<&str, &str>, 13 | tag!("[...]")); 14 | 15 | named!(parse_nix_query_entry<&str, NixQueryEntry>, 16 | do_parse!( 17 | drv: parse_nix_query_drv >> 18 | opt_recurse: opt!( 19 | do_parse!( 20 | space1 >> 21 | parse_recurse >> 22 | (Recurse::Yes) 23 | )) >> 24 | (NixQueryEntry(drv, opt_recurse.unwrap_or(Recurse::No))) 25 | )); 26 | 27 | pub fn nix_query_entry_parser( 28 | input: &str, 29 | ) -> Result> { 30 | parse_nix_query_entry(input).map(|(_, nix_query_entry)| nix_query_entry) 31 | } 32 | 33 | named!(parse_branch_start<&str, &str>, 34 | alt!(tag!("+---") | tag!("├───") | tag!("└───"))); 35 | 36 | named!(parse_extra_level<&str, &str>, 37 | alt!(tag!("| ") | tag!(" ") | tag!("│ "))); 38 | 39 | /// Run `parse_extra_level` exactly `level` times. 40 | fn parse_extra_levels(level: usize) -> impl Fn(&str) -> IResult<&str, ()> { 41 | move |input| { 42 | let (input, _) = many_m_n(level, level, parse_extra_level)(input)?; 43 | Ok((input, ())) 44 | } 45 | } 46 | 47 | /// Parse a single line of `nix-store --query --entry` output. 48 | fn parse_single_branch( 49 | level: usize, 50 | ) -> impl Fn(&str) -> IResult<&str, NixQueryEntry> { 51 | move |input| { 52 | let (input, _) = parse_extra_levels(level)(input)?; 53 | let (input, _) = parse_branch_start(input)?; 54 | let (input, nix_query_entry) = parse_nix_query_entry(input)?; 55 | let (input, _) = newline(input)?; 56 | Ok((input, nix_query_entry)) 57 | } 58 | } 59 | 60 | /// Parse a branch with all of its children. 61 | fn parse_branch_with_children( 62 | level: usize, 63 | ) -> impl Fn(&str) -> IResult<&str, Tree> { 64 | move |input| { 65 | let (input, nix_query_entry) = parse_single_branch(level)(input)?; 66 | let (input, children) = parse_branches(level + 1)(input)?; 67 | Ok((input, Tree::new(nix_query_entry, children))) 68 | } 69 | } 70 | 71 | fn parse_branches( 72 | level: usize, 73 | ) -> impl Fn(&str) -> IResult<&str, Vec>> { 74 | move |input| { 75 | let (input, children) = 76 | many0(complete(parse_branch_with_children(level)))(input)?; 77 | Ok((input, children)) 78 | } 79 | } 80 | 81 | fn parse_nix_query_tree(input: &str) -> IResult<&str, NixQueryTree> { 82 | let (input, top_drv): (&str, NixQueryDrv) = parse_nix_query_drv(input)?; 83 | let (input, _) = newline(input)?; 84 | let top_entry = NixQueryEntry(top_drv, Recurse::No); 85 | let (input, children) = parse_branches(0)(input)?; 86 | let tree = Tree::new(top_entry, children); 87 | Ok((input, NixQueryTree(tree))) 88 | } 89 | 90 | named!(parse_nix_query_tree_final<&str, NixQueryTree>, 91 | do_parse!( 92 | nix_query_tree: parse_nix_query_tree >> 93 | eof!() >> 94 | (nix_query_tree))); 95 | 96 | /// Parse all output from `nix-store --query --tree`. 97 | pub fn nix_query_tree_parser( 98 | input: &str, 99 | ) -> Result> { 100 | parse_nix_query_tree(input).map(|(_, nix_query_tree)| nix_query_tree) 101 | } 102 | 103 | #[cfg(test)] 104 | mod tests { 105 | use super::*; 106 | 107 | use indoc::indoc; 108 | 109 | #[test] 110 | fn test_parse_nix_query_drv() { 111 | let raw_input = 112 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...]"; 113 | let raw_path = "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10"; 114 | let nix_query_drv: NixQueryDrv = raw_path.into(); 115 | let r = parse_nix_query_drv(raw_input); 116 | assert_eq!(r, Ok((" [...]", nix_query_drv))); 117 | } 118 | 119 | #[test] 120 | fn test_parse_nix_query_entry_no_recurse() { 121 | let raw_input = 122 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10\n"; 123 | let raw_path = "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10"; 124 | let nix_query_entry: NixQueryEntry = 125 | NixQueryEntry(raw_path.into(), Recurse::No); 126 | let r = parse_nix_query_entry(raw_input); 127 | assert_eq!(r, Ok(("\n", nix_query_entry))); 128 | } 129 | 130 | #[test] 131 | fn test_parse_nix_query_entry_recurse() { 132 | let raw_input = 133 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...]\n"; 134 | let raw_path = "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10"; 135 | let nix_query_entry: NixQueryEntry = 136 | NixQueryEntry(raw_path.into(), Recurse::Yes); 137 | let r = parse_nix_query_entry(raw_input); 138 | assert_eq!(r, Ok(("\n", nix_query_entry))); 139 | } 140 | 141 | #[test] 142 | fn test_parse_nix_query_tree_simple() { 143 | let raw_input = indoc!( 144 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 145 | +---/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...] 146 | " 147 | ); 148 | let hello_drv: NixQueryDrv = 149 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10".into(); 150 | let actual_tree = Tree::new( 151 | NixQueryEntry(hello_drv.clone(), Recurse::No), 152 | vec![Tree::singleton(NixQueryEntry(hello_drv, Recurse::Yes))], 153 | ); 154 | 155 | let r = parse_nix_query_tree(raw_input); 156 | assert_eq!(r, Ok(("", NixQueryTree(actual_tree)))); 157 | } 158 | 159 | #[test] 160 | fn test_parse_nix_query_tree_simple_unicode() { 161 | let raw_input = indoc!( 162 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 163 | └───/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...] 164 | " 165 | ); 166 | let hello_drv: NixQueryDrv = 167 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10".into(); 168 | let actual_tree = Tree::new( 169 | NixQueryEntry(hello_drv.clone(), Recurse::No), 170 | vec![Tree::singleton(NixQueryEntry(hello_drv, Recurse::Yes))], 171 | ); 172 | 173 | let r = parse_nix_query_tree(raw_input); 174 | assert_eq!(r, Ok(("", NixQueryTree(actual_tree)))); 175 | } 176 | 177 | #[test] 178 | fn test_parse_nix_query_tree_simple_unicode2() { 179 | let raw_input = indoc!( 180 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 181 | ├───/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...] 182 | " 183 | ); 184 | let hello_drv: NixQueryDrv = 185 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10".into(); 186 | let actual_tree = Tree::new( 187 | NixQueryEntry(hello_drv.clone(), Recurse::No), 188 | vec![Tree::singleton(NixQueryEntry(hello_drv, Recurse::Yes))], 189 | ); 190 | 191 | let r = parse_nix_query_tree(raw_input); 192 | assert_eq!(r, Ok(("", NixQueryTree(actual_tree)))); 193 | } 194 | 195 | #[test] 196 | fn test_parse_branch_start() { 197 | let raw_input = "+---"; 198 | let r = parse_branch_start(raw_input); 199 | assert_eq!(r, Ok(("", "+---"))); 200 | } 201 | 202 | #[test] 203 | fn test_parse_single_branch() { 204 | let raw_input = indoc!( 205 | " 206 | +---/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...] 207 | " 208 | ); 209 | let hello_drv: NixQueryDrv = 210 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10".into(); 211 | let actual_tree = NixQueryEntry(hello_drv.clone(), Recurse::Yes); 212 | 213 | let r = parse_single_branch(0)(raw_input); 214 | assert_eq!(r, Ok(("", actual_tree))); 215 | } 216 | 217 | #[test] 218 | fn test_parse_branch_with_children_no_children() { 219 | let raw_input = indoc!( 220 | " 221 | +---/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...] 222 | " 223 | ); 224 | let hello_drv: NixQueryDrv = 225 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10".into(); 226 | let actual_tree = 227 | Tree::singleton(NixQueryEntry(hello_drv.clone(), Recurse::Yes)); 228 | 229 | let r = parse_branch_with_children(0)(raw_input); 230 | assert_eq!(r, Ok(("", actual_tree))); 231 | } 232 | 233 | #[test] 234 | fn test_parse_empty_branches() { 235 | let raw_input = "foobar"; 236 | let actual_children = vec![]; 237 | 238 | let r = parse_branches(0)(raw_input); 239 | assert_eq!(r, Ok(("foobar", actual_children))); 240 | } 241 | 242 | #[test] 243 | fn test_parse_nix_query_tree_simple_multi_children() { 244 | let raw_input = indoc!( 245 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 246 | +---/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 247 | +---/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 [...] 248 | +---/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...] 249 | " 250 | ); 251 | let hello_drv: NixQueryDrv = 252 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10".into(); 253 | let glibc_drv: NixQueryDrv = 254 | "/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27".into(); 255 | let actual_tree = Tree::new( 256 | NixQueryEntry(hello_drv.clone(), Recurse::No), 257 | vec![ 258 | Tree::singleton(NixQueryEntry(glibc_drv.clone(), Recurse::No)), 259 | Tree::singleton(NixQueryEntry(glibc_drv, Recurse::Yes)), 260 | Tree::singleton(NixQueryEntry(hello_drv, Recurse::Yes)), 261 | ], 262 | ); 263 | 264 | let r = parse_nix_query_tree(raw_input); 265 | assert_eq!(r, Ok(("", NixQueryTree(actual_tree)))); 266 | } 267 | 268 | #[test] 269 | fn test_parse_nix_query_tree_simple_multi_children_unicode() { 270 | let raw_input = indoc!( 271 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 272 | ├───/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 273 | ├───/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 [...] 274 | └───/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...] 275 | " 276 | ); 277 | let hello_drv: NixQueryDrv = 278 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10".into(); 279 | let glibc_drv: NixQueryDrv = 280 | "/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27".into(); 281 | let actual_tree = Tree::new( 282 | NixQueryEntry(hello_drv.clone(), Recurse::No), 283 | vec![ 284 | Tree::singleton(NixQueryEntry(glibc_drv.clone(), Recurse::No)), 285 | Tree::singleton(NixQueryEntry(glibc_drv, Recurse::Yes)), 286 | Tree::singleton(NixQueryEntry(hello_drv, Recurse::Yes)), 287 | ], 288 | ); 289 | 290 | let r = parse_nix_query_tree(raw_input); 291 | assert_eq!(r, Ok(("", NixQueryTree(actual_tree)))); 292 | } 293 | 294 | #[test] 295 | fn test_parse_nix_query_tree_simple_multi_levels() { 296 | let raw_input = indoc!( 297 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 298 | +---/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 299 | | +---/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 [...] 300 | +---/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...] 301 | " 302 | ); 303 | let hello_drv: NixQueryDrv = 304 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10".into(); 305 | let glibc_drv: NixQueryDrv = 306 | "/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27".into(); 307 | let actual_tree = Tree::new( 308 | NixQueryEntry(hello_drv.clone(), Recurse::No), 309 | vec![ 310 | Tree::new( 311 | NixQueryEntry(glibc_drv.clone(), Recurse::No), 312 | vec![Tree::singleton(NixQueryEntry( 313 | glibc_drv, 314 | Recurse::Yes, 315 | ))], 316 | ), 317 | Tree::singleton(NixQueryEntry(hello_drv, Recurse::Yes)), 318 | ], 319 | ); 320 | 321 | let r = parse_nix_query_tree(raw_input); 322 | assert_eq!(r, Ok(("", NixQueryTree(actual_tree)))); 323 | } 324 | 325 | #[test] 326 | fn test_parse_nix_query_tree_simple_multi_levels_unicode() { 327 | let raw_input = indoc!( 328 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 329 | ├───/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 330 | │ └───/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27 [...] 331 | └───/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 [...] 332 | " 333 | ); 334 | let hello_drv: NixQueryDrv = 335 | "/nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10".into(); 336 | let glibc_drv: NixQueryDrv = 337 | "/nix/store/pnd2kl27sag76h23wa5kl95a76n3k9i3-glibc-2.27".into(); 338 | let actual_tree = Tree::new( 339 | NixQueryEntry(hello_drv.clone(), Recurse::No), 340 | vec![ 341 | Tree::new( 342 | NixQueryEntry(glibc_drv.clone(), Recurse::No), 343 | vec![Tree::singleton(NixQueryEntry( 344 | glibc_drv, 345 | Recurse::Yes, 346 | ))], 347 | ), 348 | Tree::singleton(NixQueryEntry(hello_drv, Recurse::Yes)), 349 | ], 350 | ); 351 | 352 | let r = parse_nix_query_tree(raw_input); 353 | assert_eq!(r, Ok(("", NixQueryTree(actual_tree)))); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/opts.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use structopt::StructOpt; 3 | 4 | #[derive(Debug, StructOpt)] 5 | #[structopt(about = "GUI viewer for `nix store --query --tree` output.")] 6 | pub struct Opts { 7 | /// PATH in /nix/store to view references of 8 | #[structopt(name = "PATH", parse(from_os_str))] 9 | pub nix_store_path: PathBuf, 10 | } 11 | 12 | impl Opts { 13 | pub fn parse_from_args() -> Self { 14 | Opts::from_args() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/tree.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, VecDeque}; 2 | use std::hash::Hash; 3 | 4 | #[derive(Clone, Debug, Eq, PartialEq)] 5 | pub struct Tree { 6 | pub item: T, 7 | pub children: Vec>, 8 | } 9 | 10 | impl Tree { 11 | pub fn new(item: T, children: Vec>) -> Tree { 12 | Tree { item, children } 13 | } 14 | 15 | pub fn singleton(item: T) -> Tree { 16 | Tree::new(item, vec![]) 17 | } 18 | 19 | /// Lookup the item in the `Tree` that corresponds to the given `Path`. 20 | pub fn lookup(&self, path: Path) -> Option<&T> { 21 | match path.split_front() { 22 | None => Some(&self.item), 23 | Some((index, child_path)) => match self.children.get(index) { 24 | None => None, 25 | Some(child_tree) => child_tree.lookup(child_path), 26 | }, 27 | } 28 | } 29 | 30 | /// Similar to `path_map`, but take a function for mapping an item in the tree to an 31 | /// alternative type to use to construct the `TreePathMap`. 32 | /// 33 | /// This is useful when there is some information in the item for the `Tree` that 34 | /// can be thrown away when constructing the `TreePathMap`. 35 | pub fn path_map_map(&self, f: &dyn Fn(&T) -> U) -> TreePathMap 36 | where 37 | U: Eq + Hash, 38 | { 39 | let mut map = TreePathMap::new(); 40 | let root_path = Path::new(); 41 | map.insert(f(&self.item), root_path.clone()); 42 | map.insert_children_map(&self.children, &root_path, f); 43 | map 44 | } 45 | } 46 | 47 | impl Tree 48 | where 49 | T: Clone + Eq + Hash, 50 | { 51 | /// Create a `TreePathMap` for the elements in the `Tree`. 52 | pub fn path_map(&self) -> TreePathMap { 53 | self.path_map_map(&|i| i.clone()) 54 | } 55 | } 56 | 57 | /// This represents the path through a `Tree` to a given node. 58 | #[derive(Clone, Debug, Default, Eq, PartialEq)] 59 | pub struct Path(pub VecDeque); 60 | 61 | impl Path { 62 | pub fn split_front(mut self) -> Option<(usize, Path)> { 63 | let option_front_elem: Option = self.0.pop_front(); 64 | match option_front_elem { 65 | None => None, 66 | Some(i) => Some((i, self)), 67 | } 68 | } 69 | 70 | pub fn push_back(&mut self, value: usize) { 71 | self.0.push_back(value); 72 | } 73 | 74 | pub fn new() -> Self { 75 | Path(VecDeque::new()) 76 | } 77 | } 78 | 79 | impl From for Path 80 | where 81 | T: Into>, 82 | { 83 | fn from(other: T) -> Path { 84 | Path(other.into()) 85 | } 86 | } 87 | 88 | /// This is a mapping of items in `Tree` to their `Path`s. A single item in the `Tree` can have 89 | /// multiple `Path`s to it if it is in the `Tree` multiple times. 90 | #[derive(Clone, Debug, Default, Eq, PartialEq)] 91 | pub struct TreePathMap(HashMap>) 92 | where 93 | U: Eq + Hash; 94 | 95 | impl TreePathMap 96 | where 97 | U: Eq + Hash, 98 | { 99 | pub fn new() -> TreePathMap { 100 | TreePathMap(HashMap::new()) 101 | } 102 | 103 | /// Insert a mapping from `U` to `Path`. 104 | pub fn insert(&mut self, k: U, path: Path) { 105 | self.0 106 | .entry(k) 107 | .and_modify(|paths| paths.push(path.clone())) 108 | .or_insert_with(|| vec![path]); 109 | } 110 | 111 | /// Lookup the first `Path` for a given item. 112 | pub fn lookup_first(&self, k: &U) -> Option<&Path> { 113 | let option_paths: Option<&Vec> = self.0.get(k); 114 | option_paths.and_then(|vec: &Vec| vec.first()) 115 | } 116 | } 117 | 118 | impl TreePathMap 119 | where 120 | U: Eq + Hash, 121 | { 122 | /// Insert child `Tree`s starting at `Path`. 123 | /// 124 | /// The function `f` will map the items in the `T` to an alternative type. 125 | fn insert_children_map( 126 | &mut self, 127 | children: &[Tree], 128 | path: &Path, 129 | f: &dyn Fn(&T) -> U, 130 | ) { 131 | for (i, child) in children.iter().enumerate() { 132 | let mut child_path = path.clone(); 133 | child_path.push_back(i); 134 | self.insert(f(&child.item), child_path.clone()); 135 | self.insert_children_map(&child.children, &child_path, f); 136 | } 137 | } 138 | } 139 | 140 | #[cfg(test)] 141 | mod tests { 142 | use super::*; 143 | 144 | use std::ops::Deref; 145 | 146 | #[test] 147 | fn test_path_split_front_empty() { 148 | let res = Path::new().split_front(); 149 | assert_eq!(res, None); 150 | } 151 | 152 | #[test] 153 | fn test_path_push_back() { 154 | let mut path = Path::new(); 155 | path.push_back(1); 156 | path.push_back(2); 157 | path.push_back(3); 158 | 159 | let mut actual_vec = VecDeque::new(); 160 | actual_vec.push_back(1); 161 | actual_vec.push_back(2); 162 | actual_vec.push_back(3); 163 | let actual_path = Path(actual_vec); 164 | 165 | assert_eq!(path, actual_path); 166 | } 167 | 168 | #[test] 169 | fn test_path_split_front_nonempty() { 170 | let mut path = Path::new(); 171 | path.push_back(1); 172 | path.push_back(2); 173 | path.push_back(3); 174 | let res = path.split_front(); 175 | 176 | let mut actual_path = Path::new(); 177 | actual_path.push_back(2); 178 | actual_path.push_back(3); 179 | 180 | let actual = Some((1, actual_path)); 181 | 182 | assert_eq!(res, actual); 183 | } 184 | 185 | #[test] 186 | fn test_lookup_no_item() { 187 | let tree = Tree::new( 188 | "root", 189 | vec![ 190 | Tree::singleton("0"), 191 | Tree::singleton("1"), 192 | Tree::new( 193 | "2", 194 | vec![Tree::singleton("2-0"), Tree::singleton("2-1")], 195 | ), 196 | ], 197 | ); 198 | 199 | let path1 = vec![3].into(); 200 | let path2 = vec![0, 1].into(); 201 | let path3 = vec![1, 0].into(); 202 | let path4 = vec![2, 2].into(); 203 | let path5 = vec![2, 0, 3].into(); 204 | let path6 = vec![0, 1, 2, 3, 4].into(); 205 | 206 | assert_eq!(tree.lookup(path1), None); 207 | assert_eq!(tree.lookup(path2), None); 208 | assert_eq!(tree.lookup(path3), None); 209 | assert_eq!(tree.lookup(path4), None); 210 | assert_eq!(tree.lookup(path5), None); 211 | assert_eq!(tree.lookup(path6), None); 212 | } 213 | 214 | #[test] 215 | fn test_lookup_find_item() { 216 | let tree: Tree = Tree::new( 217 | "root".into(), 218 | vec![ 219 | Tree::singleton("0".into()), 220 | Tree::singleton("1".into()), 221 | Tree::new( 222 | "2".into(), 223 | vec![ 224 | Tree::singleton("2-0".into()), 225 | Tree::new( 226 | "2-1".into(), 227 | vec![ 228 | Tree::singleton("2-1-0".into()), 229 | Tree::singleton("2-1-1".into()), 230 | ], 231 | ), 232 | ], 233 | ), 234 | ], 235 | ); 236 | 237 | let path_root = vec![].into(); 238 | let path0 = vec![0].into(); 239 | let path1 = vec![1].into(); 240 | let path2 = vec![2].into(); 241 | let path2_0 = vec![2, 0].into(); 242 | let path2_1 = vec![2, 1].into(); 243 | let path2_1_0 = vec![2, 1, 0].into(); 244 | let path2_1_1 = vec![2, 1, 1].into(); 245 | 246 | assert_eq!(tree.lookup(path_root).map(String::deref), Some("root")); 247 | assert_eq!(tree.lookup(path0).map(String::deref), Some("0")); 248 | assert_eq!(tree.lookup(path1).map(String::deref), Some("1")); 249 | assert_eq!(tree.lookup(path2).map(String::deref), Some("2")); 250 | assert_eq!(tree.lookup(path2_0).map(String::deref), Some("2-0")); 251 | assert_eq!(tree.lookup(path2_1).map(String::deref), Some("2-1")); 252 | assert_eq!(tree.lookup(path2_1_0).map(String::deref), Some("2-1-0")); 253 | assert_eq!(tree.lookup(path2_1_1).map(String::deref), Some("2-1-1")); 254 | } 255 | 256 | #[test] 257 | fn test_tree_path_map_from_tree_all_unique() { 258 | let tree: Tree = Tree::new( 259 | "root".into(), 260 | vec![ 261 | Tree::singleton("0".into()), 262 | Tree::singleton("1".into()), 263 | Tree::new( 264 | "2".into(), 265 | vec![ 266 | Tree::singleton("2-0".into()), 267 | Tree::new( 268 | "2-1".into(), 269 | vec![ 270 | Tree::singleton("2-1-0".into()), 271 | Tree::singleton("2-1-1".into()), 272 | ], 273 | ), 274 | ], 275 | ), 276 | ], 277 | ); 278 | 279 | let res_tree_path_map: TreePathMap = tree.path_map(); 280 | 281 | let mut actual_tree_path_map: HashMap> = 282 | HashMap::new(); 283 | 284 | actual_tree_path_map.insert("root".into(), vec![Path::new()]); 285 | actual_tree_path_map.insert("0".into(), vec![vec![0].into()]); 286 | actual_tree_path_map.insert("1".into(), vec![vec![1].into()]); 287 | actual_tree_path_map.insert("2".into(), vec![vec![2].into()]); 288 | actual_tree_path_map.insert("2-0".into(), vec![vec![2, 0].into()]); 289 | actual_tree_path_map.insert("2-1".into(), vec![vec![2, 1].into()]); 290 | actual_tree_path_map.insert("2-1-0".into(), vec![vec![2, 1, 0].into()]); 291 | actual_tree_path_map.insert("2-1-1".into(), vec![vec![2, 1, 1].into()]); 292 | 293 | assert_eq!(res_tree_path_map, TreePathMap(actual_tree_path_map)); 294 | } 295 | 296 | #[test] 297 | fn test_tree_path_map_from_tree() { 298 | let tree: Tree = Tree::new( 299 | "cat".into(), // root 300 | vec![ 301 | Tree::singleton("dog".into()), // 0 302 | Tree::singleton("cat".into()), // 1 303 | Tree::new( 304 | "mouse".into(), // 2 305 | vec![ 306 | Tree::singleton("fish".into()), // 2-0 307 | Tree::new( 308 | "fish".into(), // 2-1 309 | vec![ 310 | Tree::singleton("dog".into()), // 2-1-0 311 | Tree::singleton("cat".into()), // 2-1-1 312 | ], 313 | ), 314 | ], 315 | ), 316 | ], 317 | ); 318 | 319 | let res_tree_path_map: TreePathMap = tree.path_map(); 320 | 321 | let mut actual_tree_path_map: HashMap> = 322 | HashMap::new(); 323 | 324 | actual_tree_path_map.insert( 325 | "cat".into(), 326 | vec![Path::new(), vec![1].into(), vec![2, 1, 1].into()], 327 | ); 328 | actual_tree_path_map 329 | .insert("dog".into(), vec![vec![0].into(), vec![2, 1, 0].into()]); 330 | actual_tree_path_map.insert("mouse".into(), vec![vec![2].into()]); 331 | actual_tree_path_map 332 | .insert("fish".into(), vec![vec![2, 0].into(), vec![2, 1].into()]); 333 | 334 | assert_eq!(res_tree_path_map, TreePathMap(actual_tree_path_map)); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/ui.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod css; 3 | mod menu; 4 | mod stack; 5 | mod state; 6 | mod statusbar; 7 | mod toolbar; 8 | 9 | pub mod prelude; 10 | 11 | pub use state::{Message, SortOrder, State, ViewStyle}; 12 | 13 | use glib::clone; 14 | use std::path::Path; 15 | use std::thread; 16 | 17 | use super::nix_query_tree::exec_nix_store::NixStoreErr; 18 | 19 | use prelude::*; 20 | 21 | fn render_nix_store_err( 22 | state: &State, 23 | nix_store_path: &Path, 24 | nix_store_err: &NixStoreErr, 25 | ) { 26 | statusbar::show_msg( 27 | state, 28 | &format!( 29 | "Error running `nix-store --query --tree {}`", 30 | nix_store_path.to_string_lossy() 31 | ), 32 | ); 33 | 34 | let error_dialog: gtk::MessageDialog = state.get_error_dialog(); 35 | let error_msg = &format!( 36 | "Error running `nix-store --query --tree {}`:\n\n{}", 37 | nix_store_path.to_string_lossy(), 38 | nix_store_err 39 | ); 40 | error_dialog.set_property_secondary_text(Some(error_msg)); 41 | error_dialog.run(); 42 | error_dialog.hide(); 43 | } 44 | 45 | fn search_for(state: &State, nix_store_path: &Path) { 46 | // nix-store --query --tree /nix/store/jymg0kanmlgbcv35wxd8d660rw0fawhv-hello-2.10.drv 47 | // nix-store --query --tree /nix/store/qy93dp4a3rqyn2mz63fbxjg228hffwyw-hello-2.10 48 | 49 | disable(state); 50 | 51 | statusbar::show_msg( 52 | state, 53 | &format!("Searching for {}...", nix_store_path.display()), 54 | ); 55 | 56 | let nix_store_path_buf = nix_store_path.to_path_buf(); 57 | thread::spawn(clone!(@strong state.sender as sender => move || { 58 | let exec_nix_store_res = 59 | super::nix_query_tree::exec_nix_store::run(&nix_store_path_buf); 60 | 61 | sender 62 | .send(Message::Display(exec_nix_store_res)) 63 | .expect("sender is already closed. This should never happen"); 64 | })); 65 | } 66 | 67 | fn set_sort_order(state: &State, new_sort_order: SortOrder) { 68 | state.write_sort_order(new_sort_order); 69 | 70 | stack::change_sort_order(state); 71 | } 72 | 73 | pub fn set_view_style(state: &State, new_view_style: ViewStyle) { 74 | state.write_view_style(new_view_style); 75 | 76 | stack::change_view_style(state); 77 | } 78 | 79 | fn redisplay_data(state: &State) { 80 | statusbar::clear(state); 81 | stack::redisplay_data(state); 82 | } 83 | 84 | fn disable(state: &State) { 85 | stack::disable(state); 86 | toolbar::disable(state); 87 | } 88 | 89 | fn enable(state: &State) { 90 | stack::enable(state); 91 | toolbar::enable(state); 92 | } 93 | 94 | fn handle_msg_recv(state: &State, msg: Message) { 95 | enable(state); 96 | 97 | match msg { 98 | Message::Display(exec_nix_store_res) => match exec_nix_store_res.res { 99 | Err(nix_store_err) => { 100 | render_nix_store_err( 101 | state, 102 | &exec_nix_store_res.nix_store_path, 103 | &nix_store_err, 104 | ); 105 | } 106 | Ok(nix_store_res) => { 107 | state.write_nix_store_res(nix_store_res); 108 | redisplay_data(state); 109 | } 110 | }, 111 | } 112 | } 113 | 114 | fn app_activate(app: gtk::Application) { 115 | let (sender, receiver) = 116 | glib::MainContext::channel(glib::source::PRIORITY_DEFAULT); 117 | 118 | let state = State::new(app, sender); 119 | 120 | let window: gtk::ApplicationWindow = state.get_app_win(); 121 | window.set_application(Some(&state.app)); 122 | 123 | css::setup(window.upcast_ref()); 124 | menu::setup(&state); 125 | toolbar::setup(&state); 126 | stack::setup(&state); 127 | 128 | window.show_all(); 129 | 130 | receiver.attach( 131 | None, 132 | clone!(@strong state => move |msg| { 133 | handle_msg_recv(&state, msg); 134 | glib::source::Continue(true) 135 | }), 136 | ); 137 | 138 | // Do the initial search and display the results. 139 | let opts = crate::opts::Opts::parse_from_args(); 140 | search_for(&state, &opts.nix_store_path); 141 | } 142 | 143 | pub fn run() { 144 | let uiapp = gtk::Application::new( 145 | Some("com.github.cdepillabout.nix-query-tree-viewer"), 146 | gio::ApplicationFlags::FLAGS_NONE, 147 | ) 148 | .expect("Application::new failed"); 149 | 150 | uiapp.connect_activate(|app| app_activate(app.clone())); 151 | 152 | // uiapp.run(&env::args().collect::>()); 153 | uiapp.run(&[]); 154 | } 155 | -------------------------------------------------------------------------------- /src/ui/builder.rs: -------------------------------------------------------------------------------- 1 | pub trait BuilderExtManualGetObjectExpect { 2 | fn get_object_expect>( 3 | &self, 4 | name: &str, 5 | ) -> T; 6 | } 7 | 8 | impl BuilderExtManualGetObjectExpect for U 9 | where 10 | U: gtk::prelude::BuilderExtManual, 11 | { 12 | fn get_object_expect>( 13 | &self, 14 | name: &str, 15 | ) -> T { 16 | self.get_object(name).expect(&format!( 17 | "Expected to get \"{}\" from the builder, but failed.", 18 | name 19 | )) 20 | } 21 | } 22 | 23 | pub fn create() -> gtk::Builder { 24 | let glade_src = include_str!("../../glade/ui.glade"); 25 | gtk::Builder::new_from_string(glade_src) 26 | } 27 | -------------------------------------------------------------------------------- /src/ui/css.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | pub fn setup(window: >k::Window) { 4 | let screen: gdk::Screen = match window.get_screen() { 5 | Some(screen) => screen, 6 | None => { 7 | println!("Failed to get the screen for window."); 8 | return; 9 | } 10 | }; 11 | let css_provider = gtk::CssProvider::new(); 12 | let css_src = include_str!("../../style/style.css"); 13 | match css_provider.load_from_data(css_src.as_bytes()) { 14 | Err(err) => println!("Failed to load css provider from data: {}", err), 15 | Ok(_) => { 16 | gtk::StyleContext::add_provider_for_screen( 17 | &screen, 18 | &css_provider, 19 | gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, 20 | ); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/ui/menu.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | use glib::clone; 3 | 4 | use super::super::ui; 5 | 6 | fn connect_signals(state: &ui::State) { 7 | let about_menu_item: gtk::MenuItem = state.get_about_menu_item(); 8 | let about_dialog: gtk::AboutDialog = state.get_about_dialog(); 9 | 10 | about_menu_item.connect_activate(move |_| { 11 | about_dialog.run(); 12 | about_dialog.hide(); 13 | }); 14 | 15 | let quit_menu_item: gtk::MenuItem = state.get_quit_menu_item(); 16 | 17 | quit_menu_item.connect_activate( 18 | clone!(@weak state.app as app => move |_| { 19 | app.quit(); 20 | }), 21 | ); 22 | } 23 | 24 | pub fn setup(state: &ui::State) { 25 | connect_signals(state); 26 | } 27 | -------------------------------------------------------------------------------- /src/ui/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use gdk::prelude::*; 2 | pub use gio::prelude::*; 3 | pub use glib::prelude::*; 4 | pub use gtk::prelude::*; 5 | pub use std::convert::TryFrom; 6 | 7 | pub use super::builder::BuilderExtManualGetObjectExpect; 8 | -------------------------------------------------------------------------------- /src/ui/stack.rs: -------------------------------------------------------------------------------- 1 | mod raw; 2 | mod tree; 3 | 4 | use super::super::ui; 5 | 6 | pub fn setup(state: &ui::State) { 7 | tree::setup(&state); 8 | raw::setup(&state); 9 | } 10 | 11 | pub fn disable(state: &ui::State) { 12 | tree::disable(state); 13 | raw::disable(state); 14 | } 15 | 16 | pub fn enable(state: &ui::State) { 17 | tree::enable(state); 18 | raw::enable(state); 19 | } 20 | 21 | pub fn change_sort_order(state: &ui::State) { 22 | tree::change_sort_order(state); 23 | } 24 | 25 | pub fn change_view_style(state: &ui::State) { 26 | tree::change_view_style(state); 27 | } 28 | 29 | pub fn redisplay_data(state: &ui::State) { 30 | tree::redisplay_data(&state); 31 | raw::redisplay_data(&state); 32 | } 33 | -------------------------------------------------------------------------------- /src/ui/stack/raw.rs: -------------------------------------------------------------------------------- 1 | use super::super::super::ui; 2 | use super::super::prelude::*; 3 | 4 | pub fn setup(_state: &ui::State) {} 5 | 6 | pub fn disable(_state: &ui::State) {} 7 | 8 | pub fn enable(_state: &ui::State) {} 9 | 10 | pub fn redisplay_data(state: &ui::State) { 11 | enable(state); 12 | 13 | if let Some(nix_store_res) = &*state.read_nix_store_res() { 14 | let text_buffer: gtk::TextBuffer = state.get_raw_text_buffer(); 15 | text_buffer.set_text(&nix_store_res.raw); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ui/stack/tree.rs: -------------------------------------------------------------------------------- 1 | mod columns; 2 | mod path; 3 | mod signals; 4 | mod store; 5 | 6 | use core::cmp::Ordering; 7 | use glib::clone; 8 | use glib::translate::ToGlibPtr; 9 | 10 | use super::super::super::ui; 11 | use super::super::prelude::*; 12 | 13 | fn clear(state: &ui::State) { 14 | let tree_store = state.get_tree_store(); 15 | tree_store.clear(); 16 | } 17 | 18 | pub fn disable(state: &ui::State) { 19 | let tree_view: gtk::TreeView = state.get_tree_view(); 20 | tree_view.set_sensitive(false); 21 | } 22 | 23 | pub fn enable(state: &ui::State) { 24 | let tree_view: gtk::TreeView = state.get_tree_view(); 25 | tree_view.set_sensitive(true); 26 | } 27 | 28 | fn render_nix_store_res(state: &ui::State) { 29 | if let Some(res) = &*state.read_nix_store_res() { 30 | let tree_store = state.get_tree_store(); 31 | store::insert(&tree_store, res); 32 | } 33 | } 34 | 35 | pub fn setup(state: &ui::State) { 36 | signals::connect(state); 37 | } 38 | 39 | /// Low-level (unsafe) function for setting the sorting function. 40 | #[allow(unsafe_code)] 41 | fn set_sort_func>( 42 | tree_model_sort: &O, 43 | sort_func: Box< 44 | dyn Fn(gtk::TreeModel, gtk::TreeIter, gtk::TreeIter) -> Ordering 45 | + 'static, 46 | >, 47 | ) { 48 | let sort_func_data: Box< 49 | Box< 50 | dyn Fn(gtk::TreeModel, gtk::TreeIter, gtk::TreeIter) -> Ordering 51 | + 'static, 52 | >, 53 | > = Box::new(sort_func); 54 | 55 | unsafe extern "C" fn sort_func_func( 56 | tree_model: *mut gtk_sys::GtkTreeModel, 57 | tree_iter_a: *mut gtk_sys::GtkTreeIter, 58 | tree_iter_b: *mut gtk_sys::GtkTreeIter, 59 | user_data: glib_sys::gpointer, 60 | ) -> i32 { 61 | let tree_model: gtk::TreeModel = 62 | glib::translate::from_glib_borrow(tree_model); 63 | let tree_iter_a: gtk::TreeIter = 64 | glib::translate::from_glib_borrow(tree_iter_a); 65 | let tree_iter_b: gtk::TreeIter = 66 | glib::translate::from_glib_borrow(tree_iter_b); 67 | let callback: &Box< 68 | dyn Fn(gtk::TreeModel, gtk::TreeIter, gtk::TreeIter) -> Ordering 69 | + 'static, 70 | > = &*(user_data as *mut _); 71 | 72 | let res = callback(tree_model, tree_iter_a, tree_iter_b); 73 | 74 | match res { 75 | Ordering::Less => -1, 76 | Ordering::Equal => 0, 77 | Ordering::Greater => 1, 78 | } 79 | } 80 | 81 | let tree_sortable: >k::TreeSortable = tree_model_sort.as_ref(); 82 | let gtk_tree_sortable: *mut gtk_sys::GtkTreeSortable = 83 | tree_sortable.to_glib_none().0; 84 | 85 | unsafe extern "C" fn destroy_func(data: glib_sys::gpointer) { 86 | let _callback: Box< 87 | Box< 88 | dyn Fn(gtk::TreeModel, gtk::TreeIter, gtk::TreeIter) -> Ordering 89 | + 'static, 90 | >, 91 | > = Box::from_raw(data as *mut _); 92 | } 93 | 94 | unsafe { 95 | gtk_sys::gtk_tree_sortable_set_sort_func( 96 | gtk_tree_sortable, 97 | 0, 98 | Some(sort_func_func as _), 99 | Box::into_raw(sort_func_data) as *mut std::ffi::c_void, 100 | Some( 101 | destroy_func as unsafe extern "C" fn(_: *mut std::ffi::c_void), 102 | ), 103 | ); 104 | } 105 | } 106 | 107 | pub fn change_sort_order(state: &ui::State) { 108 | let tree_model_sort = state.get_tree_model_sort(); 109 | 110 | match *state.read_sort_order() { 111 | ui::SortOrder::NixStoreOrigOutput => { 112 | tree_model_sort.set_sort_column_id( 113 | gtk::SortColumn::Default, 114 | gtk::SortType::Ascending, 115 | ); 116 | } 117 | ui::SortOrder::AlphabeticalHash => { 118 | set_sort_function(state); 119 | tree_model_sort.set_sort_column_id( 120 | gtk::SortColumn::Index(0), 121 | gtk::SortType::Ascending, 122 | ); 123 | } 124 | ui::SortOrder::AlphabeticalDrvName => { 125 | set_sort_function(state); 126 | tree_model_sort.set_sort_column_id( 127 | gtk::SortColumn::Index(0), 128 | gtk::SortType::Ascending, 129 | ); 130 | } 131 | } 132 | } 133 | 134 | pub fn change_view_style(state: &ui::State) { 135 | columns::change_view_style(state); 136 | } 137 | 138 | fn set_sort_func_callback( 139 | state: &ui::State, 140 | tree_model: gtk::TreeModel, 141 | tree_model_sort_iter_a: gtk::TreeIter, 142 | tree_model_sort_iter_b: gtk::TreeIter, 143 | ) -> Ordering { 144 | let sort_order = *state.read_sort_order(); 145 | if let Some(nix_store_res) = &*state.read_nix_store_res() { 146 | let tree_store: >k::TreeStore = tree_model 147 | .downcast_ref() 148 | .expect("tree_model is not a tree_store"); 149 | 150 | let child_iter_a = path::GtkChildTreeIter::new(tree_model_sort_iter_a); 151 | let child_iter_b = path::GtkChildTreeIter::new(tree_model_sort_iter_b); 152 | 153 | let option_nix_query_entry_a: Option< 154 | &crate::nix_query_tree::NixQueryEntry, 155 | > = child_iter_a.nix_store_res_lookup(tree_store, &nix_store_res); 156 | let option_nix_query_entry_b: Option< 157 | &crate::nix_query_tree::NixQueryEntry, 158 | > = child_iter_b.nix_store_res_lookup(tree_store, &nix_store_res); 159 | 160 | match (option_nix_query_entry_a, option_nix_query_entry_b) { 161 | (Some(nix_query_entry_a), Some(nix_query_entry_b)) => { 162 | match sort_order { 163 | ui::SortOrder::NixStoreOrigOutput => { 164 | println!("The sort function should never be called when the sort order is NixStoreOrigOutput!!!"); 165 | Ordering::Equal 166 | } 167 | ui::SortOrder::AlphabeticalHash => { 168 | nix_query_entry_a.cmp_hash(&nix_query_entry_b) 169 | } 170 | ui::SortOrder::AlphabeticalDrvName => { 171 | nix_query_entry_a.cmp_drv_name(&nix_query_entry_b) 172 | } 173 | } 174 | } 175 | _ => panic!("Not able to get an ordering for one of the nix_query_entries. This should never happen."), 176 | } 177 | } else { 178 | panic!("The nix_store_res in state hasn't been set yet. This should never happen."); 179 | } 180 | } 181 | 182 | pub fn set_sort_function(state: &ui::State) { 183 | let tree_model_sort = state.get_tree_model_sort(); 184 | 185 | let sort_callback = clone!(@strong state => move|tree_model, tree_model_sort_iter_a, tree_model_sort_iter_b| { 186 | set_sort_func_callback(&state, tree_model, tree_model_sort_iter_a, tree_model_sort_iter_b) 187 | }); 188 | 189 | set_sort_func(&tree_model_sort, Box::new(sort_callback)); 190 | } 191 | 192 | pub fn redisplay_data(state: &ui::State) { 193 | clear(state); 194 | enable(state); 195 | 196 | render_nix_store_res(state); 197 | 198 | // expand the first row of the tree view 199 | state 200 | .get_tree_view() 201 | .expand_row(>k::TreePath::new_first(), false); 202 | } 203 | -------------------------------------------------------------------------------- /src/ui/stack/tree/columns.rs: -------------------------------------------------------------------------------- 1 | pub use std::convert::TryFrom; 2 | 3 | use super::super::super::super::ui; 4 | use super::super::super::prelude::*; 5 | 6 | /// These correspond to actual columns in our data model. 7 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 8 | #[repr(i32)] 9 | pub enum Column { 10 | FullPath = 0, 11 | Recurse, 12 | HashAndDrvName, 13 | ShortHashAndDrvName, 14 | OnlyDrvName, 15 | } 16 | 17 | impl TryFrom for Column { 18 | type Error = usize; 19 | 20 | fn try_from(value: usize) -> Result { 21 | if value < Self::INDICIES.len() { 22 | Ok(Self::LIST[value]) 23 | } else { 24 | Err(value) 25 | } 26 | } 27 | } 28 | 29 | impl Column { 30 | // Is there some way to derive these types of things? 31 | const LIST: [Column; 5] = [ 32 | Column::FullPath, 33 | Column::Recurse, 34 | Column::HashAndDrvName, 35 | Column::ShortHashAndDrvName, 36 | Column::OnlyDrvName, 37 | ]; 38 | pub const INDICIES: [usize; 5] = [ 39 | Column::FullPath as usize, 40 | Column::Recurse as usize, 41 | Column::HashAndDrvName as usize, 42 | Column::ShortHashAndDrvName as usize, 43 | Column::OnlyDrvName as usize, 44 | ]; 45 | } 46 | 47 | pub fn change_view_style(state: &ui::State) { 48 | let item_renderer = state.get_cell_renderer_text_item(); 49 | let column = state.get_tree_view_column_item(); 50 | 51 | column.clear_attributes(&item_renderer); 52 | 53 | match *state.read_view_style() { 54 | ui::ViewStyle::FullPath => { 55 | column.add_attribute( 56 | &item_renderer, 57 | "text", 58 | Column::FullPath as i32, 59 | ); 60 | } 61 | ui::ViewStyle::HashAndDrvName => { 62 | column.add_attribute( 63 | &item_renderer, 64 | "text", 65 | Column::HashAndDrvName as i32, 66 | ); 67 | } 68 | 69 | ui::ViewStyle::ShortHashAndDrvName => { 70 | column.add_attribute( 71 | &item_renderer, 72 | "text", 73 | Column::ShortHashAndDrvName as i32, 74 | ); 75 | } 76 | ui::ViewStyle::OnlyDrvName => { 77 | column.add_attribute( 78 | &item_renderer, 79 | "text", 80 | Column::OnlyDrvName as i32, 81 | ); 82 | } 83 | } 84 | 85 | // Tree needs to be redrawn because changing the renderer on a column don't seem to cause a 86 | // redraw. 87 | state.get_tree_view().queue_draw(); 88 | } 89 | -------------------------------------------------------------------------------- /src/ui/stack/tree/path.rs: -------------------------------------------------------------------------------- 1 | use super::super::super::super::ui; 2 | use super::super::super::prelude::*; 3 | use super::columns::Column; 4 | use crate::nix_query_tree::exec_nix_store::NixStoreRes; 5 | use crate::nix_query_tree::{NixQueryEntry, NixQueryTree, Recurse}; 6 | use crate::tree; 7 | use std::collections::VecDeque; 8 | 9 | /// This is a `gtk::TreePath` for the underlying non-sorted data. This is the data that 10 | /// corresponds 1-to-1 to the actual `NixStoreRes` data. 11 | pub struct GtkChildTreePath(gtk::TreePath); 12 | 13 | impl std::fmt::Debug for GtkChildTreePath { 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 | write!(f, "GtkChildTreePath({:?})", self.to_path()) 16 | } 17 | } 18 | 19 | impl GtkChildTreePath { 20 | pub fn new(tree_path: gtk::TreePath) -> Self { 21 | GtkChildTreePath(tree_path) 22 | } 23 | 24 | fn get(&self) -> >k::TreePath { 25 | &self.0 26 | } 27 | 28 | pub fn into_parent( 29 | &self, 30 | tree_model_sort: >k::TreeModelSort, 31 | ) -> GtkParentTreePath { 32 | let parent_tree_path = tree_model_sort 33 | .convert_child_path_to_path(self.get()) 34 | .expect("child_tree_path should always be able to be converted to a parent tree_path"); 35 | GtkParentTreePath::new(parent_tree_path) 36 | } 37 | 38 | pub fn from_path(path: &tree::Path) -> Self { 39 | let mut vec_indices: Vec = 40 | path.0.iter().map(|&u| u as i32).collect(); 41 | vec_indices.insert(0, 0); 42 | let gtk_child_tree_path = 43 | gtk::TreePath::new_from_indicesv(&vec_indices); 44 | GtkChildTreePath::new(gtk_child_tree_path) 45 | } 46 | 47 | pub fn to_path(&self) -> tree::Path { 48 | tree::Path( 49 | self.get() 50 | .get_indices() 51 | .iter() 52 | .map(|i| *i as usize) 53 | // our tree::Path will only ever have one item at the root, so we can drop the first 54 | // item from the gtk::TreePath. 55 | .skip(1) 56 | .collect::>(), 57 | ) 58 | } 59 | 60 | pub fn nix_query_tree_lookup<'a>( 61 | &self, 62 | nix_query_tree: &'a NixQueryTree, 63 | ) -> Option<&'a NixQueryEntry> { 64 | nix_query_tree.lookup(self.to_path()) 65 | } 66 | 67 | pub fn nix_store_res_lookup<'a>( 68 | &self, 69 | nix_store_res: &'a NixStoreRes, 70 | ) -> Option<&'a NixQueryEntry> { 71 | self.nix_query_tree_lookup(&nix_store_res.tree) 72 | } 73 | } 74 | 75 | /// This is a `gtk::TreePath` for the sorted model actually shown to the user. 76 | /// 77 | /// This is just a "view" of the non-sorted data. 78 | pub struct GtkParentTreePath(gtk::TreePath); 79 | 80 | impl std::fmt::Debug for GtkParentTreePath { 81 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 82 | write!( 83 | f, 84 | "GtkParentTreePath(actual: {:?})", 85 | GtkChildTreePath::new(self.0.clone()).to_path() 86 | ) 87 | } 88 | } 89 | 90 | impl GtkParentTreePath { 91 | pub fn new(tree_path: gtk::TreePath) -> Self { 92 | GtkParentTreePath(tree_path) 93 | } 94 | 95 | fn get(&self) -> >k::TreePath { 96 | &self.0 97 | } 98 | 99 | pub fn into_child( 100 | &self, 101 | tree_model_sort: >k::TreeModelSort, 102 | ) -> GtkChildTreePath { 103 | let parent_tree_path = tree_model_sort 104 | .convert_path_to_child_path(self.get()) 105 | .expect("child_tree_path should always be able to be converted to a child_tree_path"); 106 | GtkChildTreePath::new(parent_tree_path) 107 | } 108 | 109 | #[allow(dead_code)] 110 | pub fn from_path( 111 | tree_model_sort: >k::TreeModelSort, 112 | path: &tree::Path, 113 | ) -> Self { 114 | GtkChildTreePath::from_path(path).into_parent(tree_model_sort) 115 | } 116 | 117 | #[allow(dead_code)] 118 | pub fn to_path(&self, tree_model_sort: >k::TreeModelSort) -> tree::Path { 119 | self.into_child(tree_model_sort).to_path() 120 | } 121 | 122 | #[allow(dead_code)] 123 | pub fn nix_query_tree_lookup<'a>( 124 | &self, 125 | tree_model_sort: >k::TreeModelSort, 126 | nix_query_tree: &'a NixQueryTree, 127 | ) -> Option<&'a NixQueryEntry> { 128 | self.into_child(tree_model_sort) 129 | .nix_query_tree_lookup(nix_query_tree) 130 | } 131 | 132 | #[allow(dead_code)] 133 | pub fn nix_store_res_lookup<'a>( 134 | &self, 135 | tree_model_sort: >k::TreeModelSort, 136 | nix_store_res: &'a NixStoreRes, 137 | ) -> Option<&'a NixQueryEntry> { 138 | self.into_child(tree_model_sort) 139 | .nix_store_res_lookup(nix_store_res) 140 | } 141 | } 142 | 143 | /// This is a `gtk::TreeIter` for the underlying non-sorted data. This is the data that 144 | /// corresponds 1-to-1 to the actual `NixStoreRes` data. 145 | pub struct GtkChildTreeIter(gtk::TreeIter); 146 | 147 | impl GtkChildTreeIter { 148 | pub fn new(tree_iter: gtk::TreeIter) -> Self { 149 | GtkChildTreeIter(tree_iter) 150 | } 151 | 152 | fn get(&self) -> >k::TreeIter { 153 | &self.0 154 | } 155 | 156 | pub fn nix_store_res_lookup<'a>( 157 | &self, 158 | tree_store: >k::TreeStore, 159 | nix_store_res: &'a NixStoreRes, 160 | ) -> Option<&'a NixQueryEntry> { 161 | let tree_path = GtkChildTreePath::new(tree_store.get_path(self.get())?); 162 | tree_path.nix_query_tree_lookup(&nix_store_res.tree) 163 | } 164 | } 165 | 166 | /// These are the columns that correspond to the actual columns in the GtkTreeView. 167 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 168 | #[repr(i32)] 169 | pub enum TreeViewCol { 170 | Item = 0, 171 | Recurse, 172 | } 173 | 174 | impl TryFrom for TreeViewCol { 175 | type Error = usize; 176 | 177 | fn try_from(value: usize) -> Result { 178 | if value < Self::INDICIES.len() { 179 | Ok(Self::LIST[value]) 180 | } else { 181 | Err(value) 182 | } 183 | } 184 | } 185 | 186 | fn get_tree_view_column_pos( 187 | tree_view: >k::TreeView, 188 | tree_view_column: >k::TreeViewColumn, 189 | ) -> usize { 190 | let all_tree_view_columns = tree_view.get_columns(); 191 | let option_pos = all_tree_view_columns 192 | .iter() 193 | .position(|col| *col == *tree_view_column); 194 | match option_pos { 195 | None => panic!("No column matching id. This should never happen."), 196 | Some(pos) => pos, 197 | } 198 | } 199 | 200 | impl TreeViewCol { 201 | // Is there some way to derive these types of things? 202 | const LIST: [TreeViewCol; 2] = [TreeViewCol::Item, TreeViewCol::Recurse]; 203 | const INDICIES: [usize; 2] = 204 | [TreeViewCol::Item as usize, TreeViewCol::Recurse as usize]; 205 | 206 | pub fn from_gtk( 207 | tree_view: >k::TreeView, 208 | tree_view_column: >k::TreeViewColumn, 209 | ) -> Option { 210 | let column_pos: usize = 211 | get_tree_view_column_pos(tree_view, tree_view_column); 212 | Column::try_from(column_pos).ok() 213 | } 214 | } 215 | 216 | pub fn goto(state: &ui::State, first_path: &tree::Path) { 217 | let tree_view = state.get_tree_view(); 218 | 219 | let tree_model_sort = state.get_tree_model_sort(); 220 | let child_tree_path = GtkChildTreePath::from_path(first_path); 221 | let parent_tree_path = child_tree_path.into_parent(&tree_model_sort); 222 | 223 | let col = tree_view.get_column(TreeViewCol::Item as i32); 224 | 225 | // Open recursively upward from this new path. 226 | tree_view.expand_to_path(&parent_tree_path.get()); 227 | 228 | // Scroll to the newly opened path. 229 | tree_view.scroll_to_cell( 230 | Some(&parent_tree_path.get()), 231 | col.as_ref(), 232 | true, 233 | 0.5, 234 | 0.5, 235 | ); 236 | 237 | let tree_selection: gtk::TreeSelection = tree_view.get_selection(); 238 | // Select the newly opened path. 239 | tree_selection.select_path(&parent_tree_path.get()); 240 | } 241 | 242 | fn event_button_to_parent_tree_path_column( 243 | state: &ui::State, 244 | event_button: &gdk::EventButton, 245 | ) -> Option<(GtkParentTreePath, gtk::TreeViewColumn)> { 246 | let tree_view = state.get_tree_view(); 247 | let (x, y) = event_button.get_position(); 248 | if let Some((Some(tree_path), Some(tree_view_column), _, _)) = 249 | tree_view.get_path_at_pos(x as i32, y as i32) 250 | { 251 | Some((GtkParentTreePath::new(tree_path), tree_view_column)) 252 | } else { 253 | None 254 | } 255 | } 256 | 257 | fn event_button_to_child_tree_path_column( 258 | state: &ui::State, 259 | event_button: &gdk::EventButton, 260 | ) -> Option<(GtkChildTreePath, gtk::TreeViewColumn)> { 261 | let tree_model_sort = state.get_tree_model_sort(); 262 | event_button_to_parent_tree_path_column(state, event_button).map( 263 | |(parent_tree_path, tree_view_column)| { 264 | ( 265 | parent_tree_path.into_child(&tree_model_sort), 266 | tree_view_column, 267 | ) 268 | }, 269 | ) 270 | } 271 | 272 | fn event_button_to_child_tree_path( 273 | state: &ui::State, 274 | event_button: &gdk::EventButton, 275 | ) -> Option { 276 | event_button_to_child_tree_path_column(state, &event_button) 277 | .map(|tuple| tuple.0) 278 | } 279 | 280 | fn is_for_recurse_column_child<'a>( 281 | state: &ui::State, 282 | tree_view_column: >k::TreeViewColumn, 283 | child_tree_path: GtkChildTreePath, 284 | nix_store_res: &'a NixStoreRes, 285 | ) -> Option<&'a NixQueryEntry> { 286 | let tree_view = state.get_tree_view(); 287 | let option_column = TreeViewCol::from_gtk(&tree_view, tree_view_column); 288 | let option_nix_query_entry_is_recurse = child_tree_path 289 | .nix_store_res_lookup(nix_store_res) 290 | .filter(|nix_query_entry| nix_query_entry.1 == Recurse::Yes); 291 | 292 | match (option_column, option_nix_query_entry_is_recurse) { 293 | (Some(Column::Recurse), Some(nix_query_entry)) => Some(nix_query_entry), 294 | _ => None, 295 | } 296 | } 297 | 298 | pub fn is_for_recurse_column_parent<'a>( 299 | state: &ui::State, 300 | tree_view_column: >k::TreeViewColumn, 301 | parent_tree_path: &GtkParentTreePath, 302 | nix_store_res: &'a NixStoreRes, 303 | ) -> Option<&'a NixQueryEntry> { 304 | let tree_model_sort = state.get_tree_model_sort(); 305 | let child_tree_path = parent_tree_path.into_child(&tree_model_sort); 306 | is_for_recurse_column_child( 307 | state, 308 | tree_view_column, 309 | child_tree_path, 310 | nix_store_res, 311 | ) 312 | } 313 | 314 | pub fn is_event_button_for_recurse_column<'a>( 315 | state: &ui::State, 316 | event_button: &gdk::EventButton, 317 | nix_store_res: &'a NixStoreRes, 318 | ) -> Option<&'a NixQueryEntry> { 319 | event_button_to_parent_tree_path_column(state, event_button).and_then( 320 | |(parent_tree_path, tree_view_column)| { 321 | is_for_recurse_column_parent( 322 | state, 323 | &tree_view_column, 324 | &parent_tree_path, 325 | nix_store_res, 326 | ) 327 | }, 328 | ) 329 | } 330 | 331 | pub fn nix_query_entry_for_event_button<'a>( 332 | state: &ui::State, 333 | event_button: &gdk::EventButton, 334 | nix_store_res: &'a NixStoreRes, 335 | ) -> Option<&'a NixQueryEntry> { 336 | let option_child_tree_path = 337 | event_button_to_child_tree_path(state, event_button); 338 | 339 | option_child_tree_path.and_then(|x| x.nix_store_res_lookup(nix_store_res)) 340 | } 341 | -------------------------------------------------------------------------------- /src/ui/stack/tree/signals.rs: -------------------------------------------------------------------------------- 1 | use glib::clone; 2 | 3 | use super::super::super::super::ui; 4 | use super::super::super::prelude::*; 5 | use super::path; 6 | use crate::nix_query_tree::exec_nix_store::NixStoreRes; 7 | use crate::nix_query_tree::NixQueryEntry; 8 | 9 | fn toggle_row_expanded( 10 | state: &ui::State, 11 | tree_path: >k::TreePath, 12 | recurse: bool, 13 | ) { 14 | let tree_view = state.get_tree_view(); 15 | if tree_view.row_expanded(tree_path) { 16 | tree_view.collapse_row(tree_path); 17 | } else { 18 | tree_view.expand_row(tree_path, recurse); 19 | } 20 | } 21 | 22 | // Warning: This function assumes that nix_query_entry actually exists in NixStoreRes 23 | fn go_to_path_for_query_entry( 24 | state: &ui::State, 25 | nix_store_res: &NixStoreRes, 26 | nix_query_entry: &NixQueryEntry, 27 | ) { 28 | let option_first_path = 29 | nix_store_res.lookup_first_query_entry(&nix_query_entry); 30 | match option_first_path { 31 | None => panic!( 32 | "Nothing in our map for this drv. This should hever happen." 33 | ), 34 | Some(first_path) => { 35 | path::goto(state, &first_path); 36 | } 37 | } 38 | } 39 | 40 | fn go_to_curr_path_for_query_entry( 41 | state: &ui::State, 42 | nix_query_entry: &NixQueryEntry, 43 | ) { 44 | if let Some(nix_store_res) = &*state.read_nix_store_res() { 45 | go_to_path_for_query_entry(state, nix_store_res, nix_query_entry); 46 | } 47 | } 48 | 49 | fn handle_row_activated( 50 | state: &ui::State, 51 | tree_path: gtk::TreePath, 52 | tree_view_column: >k::TreeViewColumn, 53 | ) { 54 | if let Some(nix_store_res) = &*state.read_nix_store_res() { 55 | let parent_tree_path = path::GtkParentTreePath::new(tree_path.clone()); 56 | match path::is_for_recurse_column_parent( 57 | state, 58 | tree_view_column, 59 | &parent_tree_path, 60 | nix_store_res, 61 | ) { 62 | Some(nix_query_entry) => go_to_path_for_query_entry( 63 | state, 64 | nix_store_res, 65 | &nix_query_entry, 66 | ), 67 | _ => toggle_row_expanded(state, &tree_path, false), 68 | } 69 | } 70 | } 71 | 72 | fn handle_copy_drv_path_menu_item_activated( 73 | state: &ui::State, 74 | nix_query_entry: &NixQueryEntry, 75 | ) { 76 | let tree_view = state.get_tree_view(); 77 | if let Some(display) = tree_view.get_display() { 78 | if let Some(clipboard) = gtk::Clipboard::get_default(&display) { 79 | clipboard.set_text(&nix_query_entry.to_string_lossy()); 80 | clipboard.store(); 81 | } 82 | } 83 | } 84 | 85 | fn create_copy_drv_path_menu_item( 86 | state: &ui::State, 87 | menu: >k::Menu, 88 | event_button: &gdk::EventButton, 89 | nix_store_res: &NixStoreRes, 90 | ) { 91 | if let Some(nix_query_entry) = path::nix_query_entry_for_event_button( 92 | state, 93 | event_button, 94 | nix_store_res, 95 | ) { 96 | let copy_drv_path_menu_item = 97 | gtk::MenuItem::new_with_label("Copy drv path"); 98 | 99 | copy_drv_path_menu_item.connect_activate( 100 | clone!(@strong state, @strong nix_query_entry => move |_| { 101 | handle_copy_drv_path_menu_item_activated(&state, &nix_query_entry); 102 | }), 103 | ); 104 | 105 | menu.append(©_drv_path_menu_item); 106 | } 107 | } 108 | 109 | fn handle_search_for_this_menu_item_activated( 110 | state: &ui::State, 111 | nix_query_entry: &NixQueryEntry, 112 | ) { 113 | ui::search_for(state, &nix_query_entry); 114 | } 115 | 116 | fn create_search_for_this_menu_item( 117 | state: &ui::State, 118 | menu: >k::Menu, 119 | event_button: &gdk::EventButton, 120 | nix_store_res: &NixStoreRes, 121 | ) { 122 | if let Some(nix_query_entry) = path::nix_query_entry_for_event_button( 123 | state, 124 | event_button, 125 | nix_store_res, 126 | ) { 127 | let search_for_this_menu_item = 128 | gtk::MenuItem::new_with_label("Search for this"); 129 | 130 | search_for_this_menu_item.connect_activate( 131 | clone!(@strong state, @strong nix_query_entry => move |_| { 132 | handle_search_for_this_menu_item_activated(&state, &nix_query_entry); 133 | }), 134 | ); 135 | 136 | menu.append(&search_for_this_menu_item); 137 | } 138 | } 139 | 140 | fn create_goto_first_instance_menu_item( 141 | state: &ui::State, 142 | menu: >k::Menu, 143 | event_button: &gdk::EventButton, 144 | nix_store_res: &NixStoreRes, 145 | ) { 146 | if let Some(nix_query_entry) = path::is_event_button_for_recurse_column( 147 | state, 148 | event_button, 149 | nix_store_res, 150 | ) { 151 | let goto_first_instance_menu_item = 152 | gtk::MenuItem::new_with_label("Go to tree instance"); 153 | 154 | goto_first_instance_menu_item.connect_activate( 155 | clone!(@strong state, @strong nix_query_entry => 156 | move |_| 157 | go_to_curr_path_for_query_entry(&state, &nix_query_entry) 158 | ), 159 | ); 160 | 161 | menu.append(&goto_first_instance_menu_item); 162 | } 163 | } 164 | 165 | fn handle_button_press_event( 166 | state: &ui::State, 167 | tree_view: >k::TreeView, 168 | event_button: &gdk::EventButton, 169 | ) -> Inhibit { 170 | if let Some(nix_store_res) = &*state.read_nix_store_res() { 171 | if event_button.get_event_type() == gdk::EventType::ButtonPress 172 | && event_button.get_button() == 3 173 | { 174 | let menu: gtk::Menu = gtk::Menu::new(); 175 | 176 | create_copy_drv_path_menu_item( 177 | state, 178 | &menu, 179 | event_button, 180 | nix_store_res, 181 | ); 182 | 183 | create_search_for_this_menu_item( 184 | state, 185 | &menu, 186 | event_button, 187 | nix_store_res, 188 | ); 189 | 190 | create_goto_first_instance_menu_item( 191 | state, 192 | &menu, 193 | event_button, 194 | nix_store_res, 195 | ); 196 | 197 | // only show the menu if there is at least one child 198 | if menu.get_children().len() >= 1 { 199 | menu.set_property_attach_widget(Some(&tree_view.clone())); 200 | menu.show_all(); 201 | menu.popup_at_pointer(Some(&event_button)); 202 | } 203 | } 204 | } 205 | 206 | Inhibit(false) 207 | } 208 | 209 | pub fn connect(state: &ui::State) { 210 | state.get_tree_view().connect_row_activated( 211 | clone!(@strong state => move |_, tree_path, tree_view_column| { 212 | handle_row_activated( 213 | &state, 214 | tree_path.clone(), 215 | tree_view_column, 216 | ); 217 | }), 218 | ); 219 | 220 | state.get_tree_view().connect_button_press_event( 221 | clone!(@strong state => move |tree_view_ref, event_button| { 222 | handle_button_press_event( 223 | &state, 224 | tree_view_ref, 225 | event_button, 226 | ) 227 | }), 228 | ); 229 | } 230 | -------------------------------------------------------------------------------- /src/ui/stack/tree/store.rs: -------------------------------------------------------------------------------- 1 | use crate::nix_query_tree::exec_nix_store::NixStoreRes; 2 | use crate::nix_query_tree::{ 3 | NixQueryDrv, NixQueryEntry, NixQueryTree, Recurse, 4 | }; 5 | use crate::tree::Tree; 6 | 7 | use super::super::super::prelude::*; 8 | use super::columns; 9 | 10 | fn insert_child( 11 | tree_store: >k::TreeStore, 12 | parent: Option<>k::TreeIter>, 13 | child: &Tree, 14 | ) { 15 | let Tree { item, children }: &Tree = child; 16 | let drv: &NixQueryDrv = &item.0; 17 | let drv_str = drv.to_string(); 18 | let hash_and_drv_name = drv.hash_and_drv_name(); 19 | let short_hash_and_drv_name = drv.short_hash_and_drv_name(); 20 | let only_drv_name = drv.drv_name(); 21 | let recurse_str = if item.1 == Recurse::Yes { 22 | "go to tree instance" 23 | } else { 24 | "" 25 | }; 26 | let this_iter: gtk::TreeIter = tree_store.insert_with_values( 27 | parent, 28 | None, 29 | &columns::Column::INDICIES 30 | .iter() 31 | .map(|&i| i as u32) 32 | .collect::>(), 33 | &[ 34 | &drv_str, 35 | &recurse_str, 36 | &hash_and_drv_name, 37 | &short_hash_and_drv_name, 38 | &only_drv_name, 39 | ], 40 | ); 41 | insert_children(tree_store, &this_iter, children); 42 | } 43 | 44 | fn insert_children( 45 | tree_store: >k::TreeStore, 46 | parent: >k::TreeIter, 47 | children: &[Tree], 48 | ) { 49 | for child in children { 50 | let _: &Tree = child; 51 | insert_child(tree_store, Some(parent), child) 52 | } 53 | } 54 | 55 | pub fn insert(tree_store: >k::TreeStore, nix_store_res: &NixStoreRes) { 56 | let nix_query_tree: &NixQueryTree = &nix_store_res.tree; 57 | let tree: &Tree = &nix_query_tree.0; 58 | insert_child(tree_store, None, tree); 59 | } 60 | -------------------------------------------------------------------------------- /src/ui/state.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLock, RwLockReadGuard}; 2 | 3 | use super::super::nix_query_tree::exec_nix_store::{ 4 | ExecNixStoreRes, NixStoreRes, 5 | }; 6 | use super::builder; 7 | use super::prelude::*; 8 | 9 | /// Sort order for the tree of nix store paths. 10 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 11 | #[repr(i32)] 12 | pub enum SortOrder { 13 | NixStoreOrigOutput = 0, 14 | AlphabeticalHash, 15 | AlphabeticalDrvName, 16 | } 17 | 18 | impl Default for SortOrder { 19 | fn default() -> Self { 20 | SortOrder::NixStoreOrigOutput 21 | } 22 | } 23 | 24 | impl TryFrom for SortOrder { 25 | type Error = u32; 26 | 27 | fn try_from(value: u32) -> Result { 28 | match value { 29 | 0 => Ok(SortOrder::NixStoreOrigOutput), 30 | 1 => Ok(SortOrder::AlphabeticalHash), 31 | 2 => Ok(SortOrder::AlphabeticalDrvName), 32 | n => Err(n), 33 | } 34 | } 35 | } 36 | 37 | /// View style for an individual nix store path. 38 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 39 | #[repr(i32)] 40 | pub enum ViewStyle { 41 | FullPath = 0, 42 | HashAndDrvName, 43 | ShortHashAndDrvName, 44 | OnlyDrvName, 45 | } 46 | 47 | impl Default for ViewStyle { 48 | fn default() -> Self { 49 | ViewStyle::FullPath 50 | } 51 | } 52 | 53 | impl TryFrom for ViewStyle { 54 | type Error = u32; 55 | 56 | fn try_from(value: u32) -> Result { 57 | match value { 58 | 0 => Ok(ViewStyle::FullPath), 59 | 1 => Ok(ViewStyle::HashAndDrvName), 60 | 2 => Ok(ViewStyle::ShortHashAndDrvName), 61 | 3 => Ok(ViewStyle::OnlyDrvName), 62 | n => Err(n), 63 | } 64 | } 65 | } 66 | 67 | #[derive(Clone, Debug, Eq, PartialEq)] 68 | pub enum Message { 69 | Display(ExecNixStoreRes), 70 | } 71 | 72 | #[derive(Clone, Debug)] 73 | pub struct State { 74 | pub app: gtk::Application, 75 | pub builder: gtk::Builder, 76 | pub sender: glib::Sender, 77 | pub nix_store_res: Arc>>, 78 | pub sort_order: Arc>, 79 | pub view_style: Arc>, 80 | } 81 | 82 | impl State { 83 | pub fn new(app: gtk::Application, sender: glib::Sender) -> Self { 84 | State { 85 | app, 86 | builder: builder::create(), 87 | sender, 88 | nix_store_res: Arc::new(RwLock::new(None)), 89 | sort_order: Default::default(), 90 | view_style: Default::default(), 91 | } 92 | } 93 | 94 | pub fn read_nix_store_res(&self) -> RwLockReadGuard> { 95 | self.nix_store_res.read().unwrap() 96 | } 97 | 98 | pub fn read_sort_order(&self) -> RwLockReadGuard { 99 | self.sort_order.read().unwrap() 100 | } 101 | 102 | pub fn read_view_style(&self) -> RwLockReadGuard { 103 | self.view_style.read().unwrap() 104 | } 105 | 106 | pub fn write_nix_store_res(&self, new_nix_store_res: NixStoreRes) { 107 | let state_option_nix_store_res: &mut Option = 108 | &mut *self.nix_store_res.write().unwrap(); 109 | *state_option_nix_store_res = Some(new_nix_store_res); 110 | } 111 | 112 | pub fn write_sort_order(&self, new_sort_order: SortOrder) { 113 | let state_sort_order: &mut SortOrder = 114 | &mut *self.sort_order.write().unwrap(); 115 | *state_sort_order = new_sort_order; 116 | } 117 | 118 | pub fn write_view_style(&self, new_view_style: ViewStyle) { 119 | let state_view_style: &mut ViewStyle = 120 | &mut *self.view_style.write().unwrap(); 121 | *state_view_style = new_view_style; 122 | } 123 | 124 | pub fn get_app_win(&self) -> gtk::ApplicationWindow { 125 | self.builder.get_object_expect("appWindow") 126 | } 127 | 128 | pub fn get_about_menu_item(&self) -> gtk::MenuItem { 129 | self.builder.get_object_expect("aboutMenuItem") 130 | } 131 | 132 | pub fn get_quit_menu_item(&self) -> gtk::MenuItem { 133 | self.builder.get_object_expect("quitMenuItem") 134 | } 135 | 136 | pub fn get_about_dialog(&self) -> gtk::AboutDialog { 137 | self.builder.get_object_expect("aboutDialog") 138 | } 139 | 140 | pub fn get_error_dialog(&self) -> gtk::MessageDialog { 141 | self.builder.get_object_expect("errorDialog") 142 | } 143 | 144 | pub fn get_raw_text_buffer(&self) -> gtk::TextBuffer { 145 | self.builder.get_object_expect("rawTextBuffer") 146 | } 147 | 148 | pub fn get_statusbar(&self) -> gtk::Statusbar { 149 | self.builder.get_object_expect("statusbar") 150 | } 151 | 152 | pub fn get_tree_view(&self) -> gtk::TreeView { 153 | self.builder.get_object_expect("treeView") 154 | } 155 | 156 | pub fn get_tree_view_column_item(&self) -> gtk::TreeViewColumn { 157 | self.builder.get_object_expect("treeViewColumnItem") 158 | } 159 | 160 | #[allow(dead_code)] 161 | pub fn get_tree_view_column_repeat(&self) -> gtk::TreeViewColumn { 162 | self.builder.get_object_expect("treeViewColumnRepeat") 163 | } 164 | 165 | pub fn get_cell_renderer_text_item(&self) -> gtk::CellRendererText { 166 | self.builder.get_object_expect("cellRendererTextItem") 167 | } 168 | 169 | #[allow(dead_code)] 170 | pub fn get_cell_renderer_text_repeat(&self) -> gtk::CellRendererText { 171 | self.builder.get_object_expect("cellRendererTextRepeat") 172 | } 173 | 174 | pub fn get_search_entry(&self) -> gtk::SearchEntry { 175 | self.builder.get_object_expect("searchEntry") 176 | } 177 | 178 | pub fn get_search_button(&self) -> gtk::Button { 179 | self.builder.get_object_expect("searchButton") 180 | } 181 | 182 | pub fn get_tree_store(&self) -> gtk::TreeStore { 183 | self.builder.get_object_expect("treeStore") 184 | } 185 | 186 | pub fn get_tree_model_sort(&self) -> gtk::TreeModelSort { 187 | self.builder.get_object_expect("treeModelSort") 188 | } 189 | 190 | pub fn get_sort_combo_box(&self) -> gtk::ComboBoxText { 191 | self.builder.get_object_expect("sortComboBox") 192 | } 193 | 194 | pub fn get_view_combo_box(&self) -> gtk::ComboBoxText { 195 | self.builder.get_object_expect("viewComboBox") 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/ui/statusbar.rs: -------------------------------------------------------------------------------- 1 | use super::super::ui; 2 | use super::prelude::*; 3 | 4 | pub fn clear(state: &ui::State) { 5 | show_msg(state, ""); 6 | } 7 | 8 | pub fn show_msg(state: &ui::State, msg: &str) { 9 | let statusbar: gtk::Statusbar = state.get_statusbar(); 10 | statusbar.remove_all(0); 11 | statusbar.push(0, msg); 12 | } 13 | -------------------------------------------------------------------------------- /src/ui/toolbar.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | use glib::clone; 3 | 4 | use super::super::ui; 5 | 6 | fn handle_search(state: &ui::State) { 7 | let search_entry = state.get_search_entry(); 8 | let search_text = search_entry.get_buffer().get_text(); 9 | 10 | ui::search_for(state, std::path::Path::new(&search_text)); 11 | } 12 | 13 | fn handle_select_sort_order(state: &ui::State) { 14 | let combo_box = state.get_sort_combo_box(); 15 | let active_id: u32 = combo_box.get_active().expect( 16 | "There should always be something active in the sort order combo box.", 17 | ); 18 | let sort_order = ui::SortOrder::try_from(active_id) 19 | .expect("active id is not a valid value for SortOrder"); 20 | ui::set_sort_order(state, sort_order); 21 | } 22 | 23 | fn handle_select_view_style(state: &ui::State) { 24 | let combo_box = state.get_view_combo_box(); 25 | let active_id: u32 = combo_box.get_active().expect( 26 | "There should always be something active in the view style combo box.", 27 | ); 28 | let view_style = ui::ViewStyle::try_from(active_id) 29 | .expect("active id is not a valid value for ViewStyle"); 30 | ui::set_view_style(state, view_style); 31 | } 32 | 33 | pub fn connect_signals(state: &ui::State) { 34 | state.get_search_entry().connect_activate( 35 | clone!(@strong state => move |_| { 36 | handle_search(&state); 37 | }), 38 | ); 39 | 40 | state.get_search_button().connect_button_press_event( 41 | clone!(@strong state => move |_, _| { 42 | handle_search(&state); 43 | Inhibit(false) 44 | }), 45 | ); 46 | 47 | state.get_sort_combo_box().connect_changed( 48 | clone!(@strong state => move |_| { 49 | handle_select_sort_order(&state); 50 | }), 51 | ); 52 | 53 | state.get_view_combo_box().connect_changed( 54 | clone!(@strong state => move |_| { 55 | handle_select_view_style(&state); 56 | }), 57 | ); 58 | } 59 | 60 | pub fn disable(state: &ui::State) { 61 | state.get_search_entry().set_sensitive(false); 62 | state.get_search_button().set_sensitive(false); 63 | state.get_sort_combo_box().set_sensitive(false); 64 | } 65 | 66 | pub fn enable(state: &ui::State) { 67 | state.get_search_entry().set_sensitive(true); 68 | state.get_search_button().set_sensitive(true); 69 | state.get_sort_combo_box().set_sensitive(true); 70 | } 71 | 72 | pub fn setup(state: &ui::State) { 73 | connect_signals(state); 74 | } 75 | -------------------------------------------------------------------------------- /style/style.css: -------------------------------------------------------------------------------- 1 | 2 | .large-font { 3 | font-size: 16px; 4 | font-family: monospace; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /tests/parsing.rs: -------------------------------------------------------------------------------- 1 | extern crate nix_query_tree_viewer; 2 | 3 | use indoc::indoc; 4 | 5 | use nix_query_tree_viewer::nix_query_tree::parsing::*; 6 | use nix_query_tree_viewer::nix_query_tree::*; 7 | use nix_query_tree_viewer::tree::*; 8 | 9 | #[test] 10 | fn test_parse_nix_query_tree_complicated() { 11 | let raw_input = indoc!( 12 | "/nix/store/jymg0kanmlgbcv35wxd8d660rw0fawhv-hello-2.10.drv 13 | +---/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh 14 | +---/nix/store/m3dzp25n0g4fwlygdhvak1kk8xz906n9-bash-4.4-p23.drv 15 | | +---/nix/store/58y89v7rl254dc2cygcfd5wzhv0kjm4m-bash44-013.drv 16 | | +---/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh [...] 17 | | +---/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv 18 | | | +---/nix/store/b7irlwi2wjlx5aj1dghx4c8k3ax6m56q-busybox.drv 19 | | | +---/nix/store/c0sr4qdy8halrdrh5dpm7hj05c6hyssa-unpack-bootstrap-tools.sh 20 | | | +---/nix/store/drsdq2ca1q1dj1hd0r1w2hl4s0fak1vh-bootstrap-tools.tar.xz.drv 21 | | +---/nix/store/64si0sfawzz464jj6qljxn1brpqw20pi-bison-3.4.2.drv 22 | | | +---/nix/store/7c0yirypq720qgj2clyanqp3b18h1lj0-bison-3.4.2.tar.gz.drv 23 | | | +---/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh [...] 24 | | | +---/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv [...] 25 | | | +---/nix/store/xd31j9jh72b8gz4gl1h0x9fzhmr52y8c-bootstrap-stage1-stdenv-linux.drv 26 | | | | +---/nix/store/33sl3bqjcqzrdd9clgaad3ljlwyl1pkb-patch-shebangs.sh 27 | | | | +---/nix/store/81ikflgpwzgjk8b5vmvg9gaw9mbkc86k-compress-man-pages.sh 28 | | | | +---/nix/store/9ny6szla9dg61jv8q22qbnqsz37465n0-multiple-outputs.sh 29 | | | | +---/nix/store/a92kz10cwkpa91k5239inl3fd61zp5dh-move-lib64.sh 30 | | | | +---/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv [...] 31 | | | | +---/nix/store/dis04j4z66kv6w4snapg45zwq0afcpyv-prune-libtool-files.sh 32 | | | | +---/nix/store/dlqbw00k0w0c00iw1jkhkbpzgm3pkncw-audit-tmpdir.sh 33 | | | | +---/nix/store/dsyj1sp3h8q2wwi8m6z548rvn3bmm3vc-builder.sh 34 | | | | +---/nix/store/jw961avfhaq38h828wnawqsqniasqfwz-strip.sh 35 | | | | +---/nix/store/mchwn5gbcm4wc8344bm37lismhjagr4n-setup.sh 36 | | | | +---/nix/store/mjjy30kxz775bhhi6j9phw81qh6dsbrf-move-docs.sh 37 | | | | +---/nix/store/ngg1cv31c8c7bcm2n8ww4g06nq7s4zhm-set-source-date-epoch-to-latest.sh 38 | | | | +---/nix/store/pdiysv9ph2da935zpmrvc2qc0qajpqss-bootstrap-stage1-gcc-wrapper.drv 39 | | | | | +---/nix/store/20ayqp8yqqyk7q0n1q9gs5flksphhiz1-utils.bash 40 | "); 41 | let actual_tree = 42 | NixQueryTree 43 | ( Tree 44 | { item: NixQueryEntry 45 | ( "/nix/store/jymg0kanmlgbcv35wxd8d660rw0fawhv-hello-2.10.drv".into() 46 | , Recurse::No 47 | ) 48 | , children: 49 | vec![ Tree 50 | { item: NixQueryEntry 51 | ( "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh".into() 52 | , Recurse::No 53 | ) 54 | , children: vec![] 55 | } 56 | , Tree 57 | { item: NixQueryEntry 58 | ( "/nix/store/m3dzp25n0g4fwlygdhvak1kk8xz906n9-bash-4.4-p23.drv".into() 59 | , Recurse::No 60 | ) 61 | , children: 62 | vec![ Tree 63 | { item: NixQueryEntry 64 | ( "/nix/store/58y89v7rl254dc2cygcfd5wzhv0kjm4m-bash44-013.drv".into() 65 | , Recurse::No 66 | ) 67 | , children: vec![] 68 | } 69 | , Tree 70 | { item: NixQueryEntry 71 | ( "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh".into() 72 | , Recurse::Yes 73 | ) 74 | , children: vec![] 75 | } 76 | , Tree 77 | { item: NixQueryEntry 78 | ( "/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv".into() 79 | , Recurse::No 80 | ) 81 | , children: 82 | vec![ Tree 83 | { item: NixQueryEntry 84 | ( "/nix/store/b7irlwi2wjlx5aj1dghx4c8k3ax6m56q-busybox.drv".into() 85 | , Recurse::No 86 | ) 87 | , children: vec![] 88 | } 89 | , Tree 90 | { item: NixQueryEntry 91 | ( "/nix/store/c0sr4qdy8halrdrh5dpm7hj05c6hyssa-unpack-bootstrap-tools.sh".into() 92 | , Recurse::No 93 | ) 94 | , children: vec![] 95 | } 96 | , Tree 97 | { item: NixQueryEntry 98 | ( "/nix/store/drsdq2ca1q1dj1hd0r1w2hl4s0fak1vh-bootstrap-tools.tar.xz.drv".into() 99 | , Recurse::No 100 | ) 101 | , children: vec![] 102 | } 103 | ] 104 | } 105 | , Tree 106 | { item: NixQueryEntry 107 | ( "/nix/store/64si0sfawzz464jj6qljxn1brpqw20pi-bison-3.4.2.drv".into() 108 | , Recurse::No 109 | ) 110 | , children: 111 | vec![ Tree 112 | { item: NixQueryEntry 113 | ( "/nix/store/7c0yirypq720qgj2clyanqp3b18h1lj0-bison-3.4.2.tar.gz.drv".into() 114 | , Recurse::No 115 | ) 116 | , children: vec![] 117 | } 118 | , Tree 119 | { item: NixQueryEntry 120 | ( "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh".into() 121 | , Recurse::Yes 122 | ) 123 | , children: vec![] 124 | } 125 | , Tree 126 | { item: NixQueryEntry 127 | ( "/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv".into() 128 | , Recurse::Yes 129 | ) 130 | , children: vec![] 131 | } 132 | , Tree 133 | { item: NixQueryEntry 134 | ( "/nix/store/xd31j9jh72b8gz4gl1h0x9fzhmr52y8c-bootstrap-stage1-stdenv-linux.drv".into() 135 | , Recurse::No 136 | ) 137 | , children: 138 | vec![ Tree 139 | { item: NixQueryEntry 140 | ( "/nix/store/33sl3bqjcqzrdd9clgaad3ljlwyl1pkb-patch-shebangs.sh".into() 141 | , Recurse::No 142 | ) 143 | , children: vec![] 144 | } 145 | , Tree 146 | { item: NixQueryEntry 147 | ( "/nix/store/81ikflgpwzgjk8b5vmvg9gaw9mbkc86k-compress-man-pages.sh".into() 148 | , Recurse::No 149 | ) 150 | , children: vec![] 151 | } 152 | , Tree 153 | { item: NixQueryEntry 154 | ( "/nix/store/9ny6szla9dg61jv8q22qbnqsz37465n0-multiple-outputs.sh".into() 155 | , Recurse::No 156 | ) 157 | , children: vec![] 158 | } 159 | , Tree 160 | { item: NixQueryEntry 161 | ( "/nix/store/a92kz10cwkpa91k5239inl3fd61zp5dh-move-lib64.sh".into() 162 | , Recurse::No 163 | ) 164 | , children: vec![] 165 | } 166 | , Tree 167 | { item: NixQueryEntry 168 | ( "/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv".into() 169 | , Recurse::Yes 170 | ) 171 | , children: vec![] 172 | } 173 | , Tree 174 | { item: NixQueryEntry 175 | ( "/nix/store/dis04j4z66kv6w4snapg45zwq0afcpyv-prune-libtool-files.sh".into() 176 | , Recurse::No 177 | ) 178 | , children: vec![] 179 | } 180 | , Tree 181 | { item: NixQueryEntry 182 | ( "/nix/store/dlqbw00k0w0c00iw1jkhkbpzgm3pkncw-audit-tmpdir.sh".into() 183 | , Recurse::No 184 | ) 185 | , children: vec![] 186 | } 187 | , Tree 188 | { item: NixQueryEntry 189 | ( "/nix/store/dsyj1sp3h8q2wwi8m6z548rvn3bmm3vc-builder.sh".into() 190 | , Recurse::No 191 | ) 192 | , children: vec![] 193 | } 194 | , Tree 195 | { item: NixQueryEntry 196 | ( "/nix/store/jw961avfhaq38h828wnawqsqniasqfwz-strip.sh".into() 197 | , Recurse::No 198 | ) 199 | , children: vec![] 200 | } 201 | , Tree 202 | { item: NixQueryEntry 203 | ( "/nix/store/mchwn5gbcm4wc8344bm37lismhjagr4n-setup.sh".into() 204 | , Recurse::No 205 | ) 206 | , children: vec![] 207 | } 208 | , Tree 209 | { item: NixQueryEntry 210 | ( "/nix/store/mjjy30kxz775bhhi6j9phw81qh6dsbrf-move-docs.sh".into() 211 | , Recurse::No 212 | ) 213 | , children: vec![] 214 | } 215 | , Tree 216 | { item: NixQueryEntry 217 | ( "/nix/store/ngg1cv31c8c7bcm2n8ww4g06nq7s4zhm-set-source-date-epoch-to-latest.sh".into() 218 | , Recurse::No 219 | ) 220 | , children: vec![] 221 | } 222 | , Tree 223 | { item: NixQueryEntry 224 | ( "/nix/store/pdiysv9ph2da935zpmrvc2qc0qajpqss-bootstrap-stage1-gcc-wrapper.drv".into() 225 | , Recurse::No 226 | ) 227 | , children: 228 | vec![ Tree 229 | { item: NixQueryEntry 230 | ( "/nix/store/20ayqp8yqqyk7q0n1q9gs5flksphhiz1-utils.bash".into() 231 | , Recurse::No 232 | ) 233 | , children: vec![] 234 | } 235 | ] 236 | } 237 | ] 238 | } 239 | ] 240 | } 241 | ] 242 | } 243 | ] 244 | } 245 | ); 246 | let r = nix_query_tree_parser(raw_input); 247 | assert_eq!(r, Ok(actual_tree)); 248 | } 249 | 250 | #[test] 251 | fn test_parse_nix_query_tree_complicated_unicode() { 252 | let raw_input = indoc!( 253 | "/nix/store/jymg0kanmlgbcv35wxd8d660rw0fawhv-hello-2.10.drv 254 | ├───/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh 255 | ├───/nix/store/m3dzp25n0g4fwlygdhvak1kk8xz906n9-bash-4.4-p23.drv 256 | │ ├───/nix/store/58y89v7rl254dc2cygcfd5wzhv0kjm4m-bash44-013.drv 257 | │ ├───/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh [...] 258 | │ ├───/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv 259 | │ │ ├───/nix/store/b7irlwi2wjlx5aj1dghx4c8k3ax6m56q-busybox.drv 260 | │ │ ├───/nix/store/c0sr4qdy8halrdrh5dpm7hj05c6hyssa-unpack-bootstrap-tools.sh 261 | │ │ └───/nix/store/drsdq2ca1q1dj1hd0r1w2hl4s0fak1vh-bootstrap-tools.tar.xz.drv 262 | │ ├───/nix/store/64si0sfawzz464jj6qljxn1brpqw20pi-bison-3.4.2.drv 263 | │ │ ├───/nix/store/7c0yirypq720qgj2clyanqp3b18h1lj0-bison-3.4.2.tar.gz.drv 264 | │ │ ├───/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh [...] 265 | │ │ ├───/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv [...] 266 | │ │ ├───/nix/store/xd31j9jh72b8gz4gl1h0x9fzhmr52y8c-bootstrap-stage1-stdenv-linux.drv 267 | │ │ │ ├───/nix/store/33sl3bqjcqzrdd9clgaad3ljlwyl1pkb-patch-shebangs.sh 268 | │ │ │ ├───/nix/store/81ikflgpwzgjk8b5vmvg9gaw9mbkc86k-compress-man-pages.sh 269 | │ │ │ ├───/nix/store/9ny6szla9dg61jv8q22qbnqsz37465n0-multiple-outputs.sh 270 | │ │ │ ├───/nix/store/a92kz10cwkpa91k5239inl3fd61zp5dh-move-lib64.sh 271 | │ │ │ ├───/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv [...] 272 | │ │ │ ├───/nix/store/dis04j4z66kv6w4snapg45zwq0afcpyv-prune-libtool-files.sh 273 | │ │ │ ├───/nix/store/dlqbw00k0w0c00iw1jkhkbpzgm3pkncw-audit-tmpdir.sh 274 | │ │ │ ├───/nix/store/dsyj1sp3h8q2wwi8m6z548rvn3bmm3vc-builder.sh 275 | │ │ │ ├───/nix/store/jw961avfhaq38h828wnawqsqniasqfwz-strip.sh 276 | │ │ │ ├───/nix/store/mchwn5gbcm4wc8344bm37lismhjagr4n-setup.sh 277 | │ │ │ ├───/nix/store/mjjy30kxz775bhhi6j9phw81qh6dsbrf-move-docs.sh 278 | │ │ │ ├───/nix/store/ngg1cv31c8c7bcm2n8ww4g06nq7s4zhm-set-source-date-epoch-to-latest.sh 279 | │ │ │ ├───/nix/store/pdiysv9ph2da935zpmrvc2qc0qajpqss-bootstrap-stage1-gcc-wrapper.drv 280 | │ │ │ │ ├───/nix/store/20ayqp8yqqyk7q0n1q9gs5flksphhiz1-utils.bash 281 | "); 282 | let actual_tree = 283 | NixQueryTree 284 | ( Tree 285 | { item: NixQueryEntry 286 | ( "/nix/store/jymg0kanmlgbcv35wxd8d660rw0fawhv-hello-2.10.drv".into() 287 | , Recurse::No 288 | ) 289 | , children: 290 | vec![ Tree 291 | { item: NixQueryEntry 292 | ( "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh".into() 293 | , Recurse::No 294 | ) 295 | , children: vec![] 296 | } 297 | , Tree 298 | { item: NixQueryEntry 299 | ( "/nix/store/m3dzp25n0g4fwlygdhvak1kk8xz906n9-bash-4.4-p23.drv".into() 300 | , Recurse::No 301 | ) 302 | , children: 303 | vec![ Tree 304 | { item: NixQueryEntry 305 | ( "/nix/store/58y89v7rl254dc2cygcfd5wzhv0kjm4m-bash44-013.drv".into() 306 | , Recurse::No 307 | ) 308 | , children: vec![] 309 | } 310 | , Tree 311 | { item: NixQueryEntry 312 | ( "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh".into() 313 | , Recurse::Yes 314 | ) 315 | , children: vec![] 316 | } 317 | , Tree 318 | { item: NixQueryEntry 319 | ( "/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv".into() 320 | , Recurse::No 321 | ) 322 | , children: 323 | vec![ Tree 324 | { item: NixQueryEntry 325 | ( "/nix/store/b7irlwi2wjlx5aj1dghx4c8k3ax6m56q-busybox.drv".into() 326 | , Recurse::No 327 | ) 328 | , children: vec![] 329 | } 330 | , Tree 331 | { item: NixQueryEntry 332 | ( "/nix/store/c0sr4qdy8halrdrh5dpm7hj05c6hyssa-unpack-bootstrap-tools.sh".into() 333 | , Recurse::No 334 | ) 335 | , children: vec![] 336 | } 337 | , Tree 338 | { item: NixQueryEntry 339 | ( "/nix/store/drsdq2ca1q1dj1hd0r1w2hl4s0fak1vh-bootstrap-tools.tar.xz.drv".into() 340 | , Recurse::No 341 | ) 342 | , children: vec![] 343 | } 344 | ] 345 | } 346 | , Tree 347 | { item: NixQueryEntry 348 | ( "/nix/store/64si0sfawzz464jj6qljxn1brpqw20pi-bison-3.4.2.drv".into() 349 | , Recurse::No 350 | ) 351 | , children: 352 | vec![ Tree 353 | { item: NixQueryEntry 354 | ( "/nix/store/7c0yirypq720qgj2clyanqp3b18h1lj0-bison-3.4.2.tar.gz.drv".into() 355 | , Recurse::No 356 | ) 357 | , children: vec![] 358 | } 359 | , Tree 360 | { item: NixQueryEntry 361 | ( "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh".into() 362 | , Recurse::Yes 363 | ) 364 | , children: vec![] 365 | } 366 | , Tree 367 | { item: NixQueryEntry 368 | ( "/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv".into() 369 | , Recurse::Yes 370 | ) 371 | , children: vec![] 372 | } 373 | , Tree 374 | { item: NixQueryEntry 375 | ( "/nix/store/xd31j9jh72b8gz4gl1h0x9fzhmr52y8c-bootstrap-stage1-stdenv-linux.drv".into() 376 | , Recurse::No 377 | ) 378 | , children: 379 | vec![ Tree 380 | { item: NixQueryEntry 381 | ( "/nix/store/33sl3bqjcqzrdd9clgaad3ljlwyl1pkb-patch-shebangs.sh".into() 382 | , Recurse::No 383 | ) 384 | , children: vec![] 385 | } 386 | , Tree 387 | { item: NixQueryEntry 388 | ( "/nix/store/81ikflgpwzgjk8b5vmvg9gaw9mbkc86k-compress-man-pages.sh".into() 389 | , Recurse::No 390 | ) 391 | , children: vec![] 392 | } 393 | , Tree 394 | { item: NixQueryEntry 395 | ( "/nix/store/9ny6szla9dg61jv8q22qbnqsz37465n0-multiple-outputs.sh".into() 396 | , Recurse::No 397 | ) 398 | , children: vec![] 399 | } 400 | , Tree 401 | { item: NixQueryEntry 402 | ( "/nix/store/a92kz10cwkpa91k5239inl3fd61zp5dh-move-lib64.sh".into() 403 | , Recurse::No 404 | ) 405 | , children: vec![] 406 | } 407 | , Tree 408 | { item: NixQueryEntry 409 | ( "/nix/store/bfil786fxmnjcwc7mqpm0mk4xnm2cphg-bootstrap-tools.drv".into() 410 | , Recurse::Yes 411 | ) 412 | , children: vec![] 413 | } 414 | , Tree 415 | { item: NixQueryEntry 416 | ( "/nix/store/dis04j4z66kv6w4snapg45zwq0afcpyv-prune-libtool-files.sh".into() 417 | , Recurse::No 418 | ) 419 | , children: vec![] 420 | } 421 | , Tree 422 | { item: NixQueryEntry 423 | ( "/nix/store/dlqbw00k0w0c00iw1jkhkbpzgm3pkncw-audit-tmpdir.sh".into() 424 | , Recurse::No 425 | ) 426 | , children: vec![] 427 | } 428 | , Tree 429 | { item: NixQueryEntry 430 | ( "/nix/store/dsyj1sp3h8q2wwi8m6z548rvn3bmm3vc-builder.sh".into() 431 | , Recurse::No 432 | ) 433 | , children: vec![] 434 | } 435 | , Tree 436 | { item: NixQueryEntry 437 | ( "/nix/store/jw961avfhaq38h828wnawqsqniasqfwz-strip.sh".into() 438 | , Recurse::No 439 | ) 440 | , children: vec![] 441 | } 442 | , Tree 443 | { item: NixQueryEntry 444 | ( "/nix/store/mchwn5gbcm4wc8344bm37lismhjagr4n-setup.sh".into() 445 | , Recurse::No 446 | ) 447 | , children: vec![] 448 | } 449 | , Tree 450 | { item: NixQueryEntry 451 | ( "/nix/store/mjjy30kxz775bhhi6j9phw81qh6dsbrf-move-docs.sh".into() 452 | , Recurse::No 453 | ) 454 | , children: vec![] 455 | } 456 | , Tree 457 | { item: NixQueryEntry 458 | ( "/nix/store/ngg1cv31c8c7bcm2n8ww4g06nq7s4zhm-set-source-date-epoch-to-latest.sh".into() 459 | , Recurse::No 460 | ) 461 | , children: vec![] 462 | } 463 | , Tree 464 | { item: NixQueryEntry 465 | ( "/nix/store/pdiysv9ph2da935zpmrvc2qc0qajpqss-bootstrap-stage1-gcc-wrapper.drv".into() 466 | , Recurse::No 467 | ) 468 | , children: 469 | vec![ Tree 470 | { item: NixQueryEntry 471 | ( "/nix/store/20ayqp8yqqyk7q0n1q9gs5flksphhiz1-utils.bash".into() 472 | , Recurse::No 473 | ) 474 | , children: vec![] 475 | } 476 | ] 477 | } 478 | ] 479 | } 480 | ] 481 | } 482 | ] 483 | } 484 | ] 485 | } 486 | ); 487 | let r = nix_query_tree_parser(raw_input); 488 | assert_eq!(r, Ok(actual_tree)); 489 | } 490 | --------------------------------------------------------------------------------