[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [elkowar]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: elkowar\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report\ndescription: Report a bug you have encountered\ntitle: \"[BUG] \"\nlabels: bug\nbody:\n  - type: checkboxes\n    attributes:\n      label: Checklist before submitting an issue\n      options:\n        - label: I have searched through the existing [closed and open issues](https://github.com/elkowar/eww/issues?q=is%3Aissue) for eww and made sure this is not a duplicate\n          required: true\n        - label: I have specifically verified that this bug is not a common [user error](https://github.com/elkowar/eww/issues?q=is%3Aissue+label%3Ano-actual-bug+is%3Aclosed)\n          required: true\n        - label: I am providing as much relevant information as I am able to in this bug report (Minimal config to reproduce the issue for example, if applicable)\n          required: true\n  - type: textarea\n    attributes:\n      label: \"Description of the bug\"\n      description: \"A clear an concise description of what the bug is.\"\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: \"Reproducing the issue\"\n      description: \"Please provide a clear and and minimal description of how to reproduce the bug. If possible, provide a simple example configuration that shows the issue.\"\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: \"Expected behaviour\"\n      description: \"A clear and concise description of what you expected to happen.\"\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: \"Additional context\"\n      description: \"If applicable, provide additional context or screenshots here.\"\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: \"Platform and environment\"\n      description: \"Does this happen on wayland, X11, or on both? What WM/Compositor are you using? Which version of eww are you using? (when using a git version, optimally provide the exact commit ref).\"\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature request\ndescription: Suggest an idea for this project\ntitle: \"[FEATURE] \"\nlabels: [enhancement]\nbody:\n  - type: textarea\n    attributes:\n      label: \"Description of the requested feature\"\n      description: \"Give a clear and concise description of the feature you are proposing, including examples for when it would be useful.\"\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: \"Proposed configuration syntax\"\n      description: \"If the feature you are requesting would add or change something to the Eww configuration, please provide an example for how the feature could be used.\"\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: \"Additional context\"\n      description: \"If applicable, provide additional context or screenshots here.\"\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/widget-request.yml",
    "content": "name: Widget request\ndescription: Suggest an new Widget\ntitle: \"[WIDGET] \"\nlabels: [widget-request]\nbody:\n  - type: textarea\n    attributes:\n      label: \"Description of the widget\"\n      description: \"Provide an explanation of the widget.\"\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: \"Implementation proposal\"\n      description: \"If applicable, describe which GTK-widgets this widget would be based on. A gallery of GTK widgets can be found at https://docs.gtk.org/gtk3. Please include links to the respective GTK documentation pages.\"\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: \"Example usage\"\n      description: \"Provide an example snippet of configuration showcasing how the widget could be used, including the attributes the widget should support. For anything non-obvious, include an explanation of how the properties should behave.\"\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: \"Additional context\"\n      description: \"Provide any additional context if applicable.\"\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "Please follow this template, if applicable.\n\n## Description\n\nProvide a short description of the changes your PR introduces.\nThis includes the actual feature you are adding,\nas well as any other relevant additions that were necessary to implement your feature.\n\n## Usage\n\nWhen adding a widget or anything else that affects the configuration,\nplease provide a minimal example configuration snippet showcasing how to use it and\n\n### Showcase\n\nWhen adding widgets, please provide screenshots showcasing how your widget looks.\nThis is not strictly required, but strongly appreciated.\n\n## Additional Notes\n\nAnything else you want to add, such as remaining questions or explanations.\n\n## Checklist\n\nPlease make sure you can check all the boxes that apply to this PR.\n\n- [ ] All widgets I've added are correctly documented.\n- [ ] I added my changes to CHANGELOG.md, if appropriate.\n- [ ] The documentation in the `docs/content/main` directory has been adjusted to reflect my changes.\n- [ ] I used `cargo fmt` to automatically format all code before committing\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\n\non:\n   push:\n      branches: [master]\n   pull_request:\n      branches: [master]\n\nenv:\n   CARGO_TERM_COLOR: always\n\njobs:\n   build:\n      runs-on: ubuntu-latest\n      steps:\n         - name: Install dependencies\n           run: sudo apt-get update && sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libdbusmenu-gtk3-dev\n\n         - uses: actions/checkout@v4\n\n         - name: Setup rust\n           uses: dtolnay/rust-toolchain@stable\n           with:\n               components: clippy,rustfmt\n\n         - name: Load rust cache\n           uses: Swatinem/rust-cache@v2\n\n         - name: Setup problem matchers\n           uses: r7kamura/rust-problem-matchers@v1\n\n         - name: Check formatting\n           run: cargo fmt -- --check\n         - name: Check with default features\n           run: cargo check\n\n         - name: Run tests\n           run: cargo test\n\n         - name: Check x11 only\n           run: cargo check --no-default-features --features=x11\n         - name: Check wayland only\n           run: cargo check --no-default-features --features=wayland\n         - name: Check no-backend\n           run: cargo check --no-default-features\n"
  },
  {
    "path": ".github/workflows/gh-pages.yml",
    "content": "name: Build and deploy Github pages\non:\n    push:\n        branches:\n            - master\n        paths:\n            - \"docs/**\"\n            - \"gen-docs.ts\"\n            - \"crates/eww/src/widgets/widget_definitions.rs\"\n            - \"crates/eww/src/config/inbuilt.rs\"\n            - \".github/workflows/**\"\njobs:\n    build:\n        name: Build mdBook\n        runs-on: ubuntu-latest\n        steps:\n            # Checkout\n            - uses: actions/checkout@master\n\n            # Build widget documentation\n            - name: Use deno to build widget documentation\n              uses: denoland/setup-deno@main\n              with:\n                  deno-version: \"v1.x\"\n            - run: deno run --allow-read --allow-write gen-docs.ts ./crates/eww/src/widgets/widget_definitions.rs ./crates/eww/src/config/inbuilt.rs\n\n            # Build & deploy\n            - name: build mdBook page\n              uses: peaceiris/actions-mdbook@v1\n              with:\n                  mdbook-version: '0.4.8'\n            - run: mdbook build docs\n\n            - name: Deploy\n              uses: peaceiris/actions-gh-pages@v3\n              with:\n                  github_token: ${{ secrets.GITHUB_TOKEN }}\n                  publish_dir: ./docs/book/\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n/**/target\n/result\n/result-*\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to eww will be listed here, starting at changes since version 0.2.0.\n\n## Unreleased\n\n### BREAKING CHANGES\n- [#1176](https://github.com/elkowar/eww/pull/1176) changed safe access (`?.`) behavior:\n  Attempting to index in an empty JSON string (`'\"\"'`) is now an error.\n\n### Fixes\n- Fix crash on invalid `formattime` format string (By: luca3s)\n- Fix crash on NaN or infinite graph value (By: luca3s)\n- Re-enable some scss features (By: w-lfchen)\n- Fix and refactor nix flake (By: w-lfchen)\n- Fix remove items from systray (By: vnva)\n- Fix the gtk `stack` widget (By: ovalkonia)\n- Fix values in the `EWW_NET` variable (By: mario-kr)\n- Fix the gtk `expander` widget (By: ovalkonia)\n- Fix wayland monitor names support (By: dragonnn)\n- Load systray items that are registered without a path (By: Kage-Yami)\n- `get_locale` now follows POSIX standard for locale selection (By: mirhahn, w-lfchen)\n- Improve multi-monitor handling under wayland (By: bkueng)\n\n### Features\n- Add warning and docs for incompatible `:anchor` and `:exclusive` options\n- Add `eww poll` subcommand to force-poll a variable (By: kiana-S)\n- Add OnDemand support for focusable on wayland (By: GallowsDove)\n- Add jq `raw-output` support (By: RomanHargrave)\n- Update rust toolchain to 1.81.0 (By: w-lfchen)\n- Add `:fill-svg` and `:preserve-aspect-ratio` properties to images (By: hypernova7, w-lfchen)\n- Add `:truncate` property to labels, disabled by default (except in cases where truncation would be enabled in version `0.5.0` and before) (By: Rayzeq).\n- Add support for `:hover` css selectors for tray items (By: zeapoz)\n- Add scss support for the `:style` widget property (By: ovalkonia)\n- Add `min` and `max` function calls to simplexpr (By: ovalkonia)\n- Add `flip-x`, `flip-y`, `vertical` options to the graph widget to determine its direction\n- Add `transform-origin-x`/`transform-origin-y` properties to transform widget (By: mario-kr)\n- Add keyboard support for button presses (By: julianschuler)\n- Support empty string for safe access operator (By: ModProg)\n- Add `log` function calls to simplexpr (By: topongo)\n- Add `:lines` and `:wrap-mode` properties to label widget (By: vaporii)\n- Add `value-pos` to scale widget (By: ipsvn)\n- Add `floor` and `ceil` function calls to simplexpr (By: wsbankenstein)\n- Add `formatbytes` function calls to simplexpr (By: topongo)\n\n## [0.6.0] (21.04.2024)\n\n### Fixes\n- The `shell-completions` subcommand is now run before anything is set up\n- Fix nix flake\n- Fix `jq` (By: w-lfchen)\n- Labels now use gtk's truncation system (By: Rayzeq).\n\n### Features\n- Add `systray` widget (By: ralismark)\n- Add `:checked` property to checkbox (By: levnikmyskin)\n\n## [0.5.0] (17.02.2024)\n\n### BREAKING CHANGES\n- Remove `eww windows` command, replace with `eww active-windows` and `eww list-windows`\n\n### Features\n- Add `:icon` and `:icon-size` to the image widget (By: Adrian Perez de Castro)\n- Add `get_env` function (By: RegenJacob)\n- Add `:namespace` window option\n- Default to building with x11 and wayland support simultaneously\n- Add `truncate-left` property on `label` widgets (By: kawaki-san)\n- Add `gravity` property on `label` widgets (By: Elekrisk)\n- Add support for safe access (`?.`) in simplexpr (By: oldwomanjosiah)\n- Allow floating-point numbers in percentages for window-geometry\n- Add support for safe access with index (`?.[n]`) (By: ModProg)\n- Made `and`, `or` and `?:` lazily evaluated in simplexpr (By: ModProg)\n- Add Vanilla CSS support (By: Ezequiel Ramis)\n- Add `jq` function, offering jq-style json processing\n- Add support for the `EWW_BATTERY` magic variable in FreeBSD, OpenBSD, and NetBSD (By: dangerdyke)\n- Add `justify` property to the label widget, allowing text justification (By: n3oney)\n- Add `EWW_TIME` magic variable (By: Erenoit)\n- Add trigonometric functions (`sin`, `cos`, `tan`, `cot`) and degree/radian conversions (`degtorad`, `radtodeg`) (By: end-4)\n- Add `substring` function to simplexpr\n- Add `--duration` flag to `eww open`\n- Add support for referring to monitor with `<primary>`\n- Add support for multiple matchers in `monitor` field\n- Add `stack` widget (By: vladaviedov)\n- Add `unindent` property to the label widget, allowing to disable removal of leading spaces (By: nrv)\n- Switch to stable rust toolchain (1.76)\n- Add `tooltip` widget, which allows setting a custom tooltip (not only text), to a widget (By: Rayzeq)\n- Add `eww shell-completions` command, generating completion scripts for different shells\n\n### Fixes\n- Fix wrong values in `EWW_NET`\n- Fix logfiles growing indefinitely\n\n## [0.4.0] (04.09.2022)\n\n### BREAKING CHANGES\n- Change `calendar`-widget to index months starting at 1 rather than indexed from 0\n\n### Features\n- Add support for output names in X11 to select `:monitor`.\n- Add support for `:active`-pseudoselector on eventbox (By: viandoxdev)\n- Add support for `:password` on input (By: viandoxdev)\n\n### Notable fixes and other changes\n- Scale now only runs the onchange command on changes caused by user-interaction\n- Improve CSS error reporting\n- Fix deflisten scripts not always getting cleaned up properly\n- Add `:round-digits` to scale widget (By: gavynriebau)\n- Fix cirular-progress not properly displaying 100% values when clockwise is false\n- Fix temperatures inside `EWW_TEMPS` not being accessible if at least one value is `NaN`\n\n\n## 0.3.0 (26.05.2022)\n\n### BREAKING CHANGES\n- Change the onclick command API to support multiple placeholders.\n  This changes. the behaviour of the calendar widget's onclick as well as the onhover and onhoverlost\n  events. Instead of providing the entire date (or, respecively, the x and y mouse coordinates) in\n  a single value (`day.month.year`, `x y`), the values are now provided as separate placeholders.\n  The day can now be accessed with `{0}`, the month with `{1}`, and the year with `{2}`, and\n  similarly x and y are accessed with `{0}` and `{1}`.\n\n### Features\n- Add `eww inspector` command\n- Add `--no-daemonize` flag\n- Add support for displaying marks on `scale`-widget (By: druskus20)\n- Add `children`-widget that allows custom widgets to make use of children\n- Add support for `:hover` css selectors for eventbox (By: druskus20)\n- Add `eww get` subcommand (By: druskus20)\n- Add circular progress widget (By: druskus20)\n- Add `:xalign` and `:yalign` to labels (By: alecsferra)\n- Add `graph` widget (By: druskus20)\n- Add `>=` and `<=` operators to simplexpr (By: viandoxdev)\n- Add `desktop` window type (By: Alvaro Lopez)\n- Add `scroll` widget (By: viandoxdev)\n- Add `notification` window type\n- Add drag and drop functionality to eventbox\n- Add `search`, `captures`, `stringlength`, `arraylength` and `objectlength` functions for expressions (By: MartinJM, ElKowar)\n- Add `matches` function\n- Add `transform` widget (By: druskus20)\n- Add `:onaccept` to input field, add `:onclick` to eventbox\n- Add `EWW_CMD`, `EWW_CONFIG_DIR`, `EWW_EXECUTABLE` magic variables\n- Add `overlay` widget (By: viandoxdev)\n- Add arguments option to `defwindow` (By: WilfSilver)\n\n### Notable Internal changes\n- Rework state management completely, now making local state and dynamic widget hierarchy changes possible.\n\n### Notable fixes and other changes\n- Fix `onscroll` gtk-bug where the first event is emitted incorrectly (By: druskus20)\n- Allow windows to get moved when windowtype is `normal`\n- Added more examples\n- List system-level dependencies in documentation\n- Document structure of magic variables (By: legendofmiracles)\n- Updated dependencies\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nmembers = [\"crates/*\"]\nresolver = \"2\"\n\n[workspace.dependencies]\n\nsimplexpr = { version = \"0.1.0\", path = \"crates/simplexpr\" }\neww_shared_util = { version = \"0.1.0\", path = \"crates/eww_shared_util\" }\nyuck = { version = \"0.1.0\", path = \"crates/yuck\", default-features = false }\nnotifier_host = { version = \"0.1.0\", path = \"crates/notifier_host\" }\n\nanyhow = \"1.0.86\"\nbincode = \"1.3.3\"\nbytesize = \"2.0.1\"\ncached = \"0.53.1\"\nchrono = \"0.4.38\"\nchrono-tz = \"0.10.0\"\nclap = { version = \"4.5.1\", features = [\"derive\"] }\nclap_complete = \"4.5.12\"\ncodespan-reporting = \"0.11\"\nderive_more = { version = \"1\", features = [\n    \"as_ref\",\n    \"debug\",\n    \"display\",\n    \"from\",\n    \"from_str\",\n] }\nextend = \"1.2\"\nfutures = \"0.3.30\"\ngrass = \"0.13.4\"\ngtk = \"0.18.1\"\ninsta = \"1.7\"\nitertools = \"0.13.0\"\njaq-core = \"1.5.1\"\njaq-parse = \"1.0.3\"\njaq-std = \"1.6.0\"\njaq-interpret = \"1.5.0\"\njaq-syn = \"1.6.0\"\nlalrpop = { version = \"0.21\", features = [\"unicode\"] }\nlalrpop-util = { version = \"0.21\", features = [\"unicode\"] }\nlibc = \"0.2\"\nlog = \"0.4\"\nmaplit = \"1\"\nnix = \"0.29.0\"\nnotify = \"6.1.1\"\nonce_cell = \"1.19\"\npretty_assertions = \"1.4.0\"\npretty_env_logger = \"0.5.0\"\nref-cast = \"1.0.22\"\nregex = \"1.10.5\"\nserde_json = \"1.0\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nsimple-signal = \"1.1\"\nsmart-default = \"0.7.1\"\nstatic_assertions = \"1.1.0\"\nstrsim = \"0.11\"\nstrum = { version = \"0.26\", features = [\"derive\"] }\nsysinfo = \"0.31.2\"\nthiserror = \"1.0\"\ntokio-util = \"0.7.11\"\ntokio = { version = \"1.39.2\", features = [\"full\"] }\nunescape = \"0.1\"\nwait-timeout = \"0.2\"\nzbus = { version = \"3.15.2\", default-features = false, features = [\"tokio\"] }\n\n[profile.dev]\nsplit-debuginfo = \"unpacked\"\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2020 ElKowar\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![dependency status](https://deps.rs/repo/github/elkowar/eww/status.svg)](https://deps.rs/repo/github/elkowar/eww)\n\n# Eww\n\n<img src=\"./.github/EwwLogo.svg\" height=\"100\" align=\"left\"/>\n\nElkowars Wacky Widgets is a standalone widget system made in Rust that allows you to implement\nyour own, custom widgets in any window manager.\n\nDocumentation **and instructions on how to install** can be found [here](https://elkowar.github.io/eww).\n\nDharmx also wrote a nice, beginner friendly introductory guide for eww [here](https://dharmx.is-a.dev/eww-powermenu/).\n\n## Check out another cool project by me\n\n<img src=\"https://raw.githubusercontent.com/elkowar/yolk/refs/heads/main/.github/images/yolk_logo.svg\" height=\"100\" align=\"right\"/>\n\nI'm currently busy working [yolk](https://github.com/elkowar/yolk),\nwhich is a dotfile management solution that supports a unique spin on templating: *templating without template files*.\n\nTo find out more, check out the [website and documentation](https://elkowar.github.io/yolk)!\n\n## Examples\n\n(Note that some of these still make use of the old configuration syntax.)\n\n* A basic bar, see [examples](./examples/eww-bar)\n![Example 1](./examples/eww-bar/eww-bar.png)\n\n* [Some setups by Druskus20](https://github.com/druskus20/eugh)\n![Druskus20-bar](https://raw.githubusercontent.com/druskus20/eugh/master/polybar-replacement/.github/preview.png)\n\n* [My own vertical bar](https://github.com/elkowar/dots-of-war/tree/master/eww-bar/.config/eww-bar)\n\n<img src=\"https://raw.githubusercontent.com/elkowar/dots-of-war/master/eww-bar/.config/eww-bar/showcase.png\" height=\"400\" width=\"auto\"/>\n\n* [Vertical Bar by Rxyhn](https://github.com/rxyhn/bspdots)\n\n<div align=\"left\">\n\n![Rxyhn-rice](https://user-images.githubusercontent.com/93292023/152228869-d618335a-7a1e-40f7-95f9-b1cf401be89e.gif)\n\n</div>\n\n* [Setup by Axarva](https://github.com/Axarva/dotfiles-2.0)\n![Axarva-rice](https://raw.githubusercontent.com/Axarva/dotfiles-2.0/main/screenshots/center.png)\n\n* [Setup by adi1090x](https://github.com/adi1090x/widgets)\n![Nordic](https://raw.githubusercontent.com/adi1090x/widgets/main/previews/dashboard.png)\n\n* [i3 Bar replacement by owenrumney](https://github.com/owenrumney/eww-bar)\n![Top bar](https://raw.githubusercontent.com/owenrumney/eww-bar/master/.github/topbar.gif)\n![Bottom bar](https://raw.githubusercontent.com/owenrumney/eww-bar/master/.github/bottombar.gif)\n\n* [Setups by iSparsh](https://github.com/iSparsh/gross)\n![iSparsh-gross](https://user-images.githubusercontent.com/57213270/140309158-e65cbc1d-f3a8-4aec-848c-eef800de3364.png)\n\n* [topbar by saimoomedits](https://github.com/Saimoomedits/eww-widgets)\n\n<div align=\"center\">\n\n![eww-top-bar](https://user-images.githubusercontent.com/72156551/153045183-227b62b2-223a-4a5b-a499-3f31044b5b65.gif)\n\n</div>\n\n* [Activate Linux by Nycta](https://github.com/Nycta-b424b3c7/eww_activate-linux)\n\n<div align=\"left\">\n\n![Activate Linux](https://raw.githubusercontent.com/Nycta-b424b3c7/eww_activate-linux/refs/heads/master/activate-linux.png)\n\n</div>\n\n## Contribewwting\n\nIf you want to contribute anything, like adding new widgets, features, or subcommands (including sample configs), you should definitely do so.\n\n### Steps\n\n1. Fork this repository\n2. Install dependencies\n3. Smash your head against the keyboard from frustration (coding is hard)\n4. Write down your changes in CHANGELOG.md\n5. Open a pull request once you're finished\n\n## Widget\n\nhttps://en.wikipedia.org/wiki/Wikipedia:Widget\n"
  },
  {
    "path": "YUCK_MIGRATION.md",
    "content": "# Migrating to yuck\n\nYuck is the new configuration syntax used by eww.\nWhile the syntax has changed dramatically, the general structure of the configuration\nhas stayed mostly the same.\n\nMost notably, the top-level blocks are now gone.\nThis means that `defvar`, `defwidget`, etc blocks no longer need to be in separate\nsections of the file, but instead can be put wherever you need them.\n\nExplaining the exact syntax of yuck would be significantly less effective than just\nlooking at an example, as the general syntax is very simple.\n\nThus, to get a feel for yuck, read through the [example configuration](./examples/eww-bar/eww.yuck).\n\n\nAdditionally, a couple smaller things have been changed.\nThe fields and structure of the `defwindow` block as been adjusted to better reflect\nthe options provided by the displayserver that is being used.\nThe major changes are:\n- The `screen` field is now called `monitor`\n- `reserve` and `geometry` are now structured slightly differently (see [here](./docs/src/configuration.md#creating-your-first-window))\nTo see how exactly the configuration now looks, check the [respective documentation](./docs/src/configuration.md#creating-your-first-window)\n\n\n## Automatically converting your configuration\n\nA couple _amazing_ people have started to work on an [automatic converter](https://github.com/undefinedDarkness/ewwxml) that can turn your\nold eww.xml into the new yuck format!\n"
  },
  {
    "path": "crates/eww/Cargo.toml",
    "content": "[package]\nname = \"eww\"\nversion = \"0.6.0\"\nauthors = [\"elkowar <5300871+elkowar@users.noreply.github.com>\"]\ndescription = \"Widgets for everyone!\"\nlicense = \"MIT\"\nrepository = \"https://github.com/elkowar/eww\"\nhomepage = \"https://github.com/elkowar/eww\"\nedition = \"2021\"\n\n\n[features]\ndefault = [\"x11\", \"wayland\"]\nx11 = [\"gdkx11\", \"x11rb\"]\nwayland = [\"gtk-layer-shell\"]\n\n[dependencies]\nsimplexpr.workspace = true\neww_shared_util.workspace = true\nyuck.workspace = true\nnotifier_host.workspace = true\n\ngtk-layer-shell = { version = \"0.8.1\", optional = true, features=[\"v0_6\"] }\ngdkx11 = { version = \"0.18\", optional = true }\nx11rb = { version = \"0.13.1\", features = [\"randr\"], optional = true }\ngdk-sys = \"0.18.0\"\n\nordered-stream = \"0.2.0\"\n\n\ngrass.workspace = true\nanyhow.workspace = true\nbincode.workspace = true\nchrono.workspace = true\nclap = { workspace = true, features = [\"derive\"] }\nclap_complete.workspace = true\ncodespan-reporting.workspace = true\nderive_more.workspace = true\nextend.workspace = true\nfutures.workspace = true\ngtk.workspace = true\nitertools.workspace = true\nlibc.workspace = true\nlog.workspace = true\nmaplit.workspace = true\nnix = { workspace = true, features = [\"process\", \"fs\", \"signal\"] }\nnotify.workspace = true\nonce_cell.workspace = true\npretty_env_logger.workspace = true\nregex.workspace = true\nserde_json.workspace = true\nserde = { workspace = true, features = [\"derive\"] }\nsimple-signal.workspace = true\nsysinfo = { workspace = true }\ntokio-util.workspace = true\ntokio = { workspace = true, features = [\"full\"] }\nunescape.workspace = true\nwait-timeout.workspace = true\nzbus = { workspace = true, default-features = false, features = [\"tokio\"] }\n"
  },
  {
    "path": "crates/eww/build.rs",
    "content": "use std::process::Command;\nfn main() {\n    let output = Command::new(\"git\").args([\"rev-parse\", \"HEAD\"]).output();\n    if let Ok(output) = output {\n        if let Ok(hash) = String::from_utf8(output.stdout) {\n            println!(\"cargo:rustc-env=GIT_HASH={}\", hash);\n            println!(\"cargo:rustc-env=CARGO_PKG_VERSION={} {}\", env!(\"CARGO_PKG_VERSION\"), hash);\n        }\n    }\n    let output = Command::new(\"git\").args([\"show\", \"-s\", \"--format=%ci\"]).output();\n    if let Ok(output) = output {\n        if let Ok(date) = String::from_utf8(output.stdout) {\n            println!(\"cargo:rustc-env=GIT_COMMIT_DATE={}\", date);\n        }\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/app.rs",
    "content": "use crate::{\n    daemon_response::DaemonResponseSender,\n    display_backend::DisplayBackend,\n    error_handling_ctx,\n    gtk::prelude::{ContainerExt, CssProviderExt, GtkWindowExt, MonitorExt, StyleContextExt, WidgetExt},\n    paths::EwwPaths,\n    script_var_handler::ScriptVarHandlerHandle,\n    state::scope_graph::{ScopeGraph, ScopeIndex},\n    widgets::window::Window,\n    window_arguments::WindowArguments,\n    window_initiator::WindowInitiator,\n    *,\n};\nuse anyhow::anyhow;\nuse codespan_reporting::files::Files;\nuse eww_shared_util::{Span, VarName};\nuse gdk::Monitor;\nuse glib::ObjectExt;\nuse gtk::{gdk, glib};\nuse itertools::Itertools;\nuse once_cell::sync::Lazy;\nuse simplexpr::{dynval::DynVal, SimplExpr};\nuse std::{\n    cell::RefCell,\n    collections::{HashMap, HashSet},\n    marker::PhantomData,\n    rc::Rc,\n};\nuse tokio::sync::mpsc::UnboundedSender;\nuse yuck::{\n    config::{\n        monitor::MonitorIdentifier,\n        script_var_definition::ScriptVarDefinition,\n        window_geometry::{AnchorPoint, WindowGeometry},\n    },\n    error::DiagError,\n    gen_diagnostic,\n    value::Coords,\n};\n\n/// A command for the eww daemon.\n/// While these are mostly generated from eww CLI commands (see [`opts::ActionWithServer`]),\n/// they may also be generated from other places internally.\n#[derive(Debug)]\npub enum DaemonCommand {\n    NoOp,\n    UpdateVars(Vec<(VarName, DynVal)>),\n    PollVars(Vec<VarName>),\n    ReloadConfigAndCss(DaemonResponseSender),\n    OpenInspector,\n    OpenMany {\n        windows: Vec<(String, String)>,\n        args: Vec<(String, VarName, DynVal)>,\n        should_toggle: bool,\n        sender: DaemonResponseSender,\n    },\n    OpenWindow {\n        window_name: String,\n        instance_id: Option<String>,\n        pos: Option<Coords>,\n        size: Option<Coords>,\n        anchor: Option<AnchorPoint>,\n        screen: Option<MonitorIdentifier>,\n        should_toggle: bool,\n        duration: Option<std::time::Duration>,\n        sender: DaemonResponseSender,\n        args: Option<Vec<(VarName, DynVal)>>,\n    },\n    CloseWindows {\n        windows: Vec<String>,\n        auto_reopen: bool,\n        sender: DaemonResponseSender,\n    },\n    KillServer,\n    CloseAll,\n    PrintState {\n        all: bool,\n        sender: DaemonResponseSender,\n    },\n    GetVar {\n        name: String,\n        sender: DaemonResponseSender,\n    },\n    PrintDebug(DaemonResponseSender),\n    PrintGraph(DaemonResponseSender),\n    ListWindows(DaemonResponseSender),\n    ListActiveWindows(DaemonResponseSender),\n}\n\n/// An opened window.\n#[derive(Debug)]\npub struct EwwWindow {\n    pub name: String,\n    pub scope_index: ScopeIndex,\n    pub gtk_window: Window,\n    pub destroy_event_handler_id: Option<glib::SignalHandlerId>,\n}\n\nimpl EwwWindow {\n    /// Close the GTK window and disconnect the destroy event-handler.\n    ///\n    /// You need to make sure that the scope get's properly cleaned from the state graph\n    /// and that script-vars get cleaned up properly\n    pub fn close(self) {\n        log::info!(\"Closing gtk window {}\", self.name);\n        self.gtk_window.close();\n        if let Some(handler_id) = self.destroy_event_handler_id {\n            self.gtk_window.disconnect(handler_id);\n        }\n    }\n}\n\npub struct App<B: DisplayBackend> {\n    pub scope_graph: Rc<RefCell<ScopeGraph>>,\n    pub eww_config: config::EwwConfig,\n    /// Map of all currently open windows to their unique IDs\n    /// If no specific ID was specified whilst starting the window,\n    /// it will be the same as the window name.\n    /// Therefore, only one window of a given name can exist when not using IDs.\n    pub open_windows: HashMap<String, EwwWindow>,\n    pub instance_id_to_args: HashMap<String, WindowArguments>,\n    /// Window names that are supposed to be open, but failed.\n    /// When reloading the config, these should be opened again.\n    pub failed_windows: HashSet<String>,\n    pub css_provider: gtk::CssProvider,\n\n    /// Sender to send [`DaemonCommand`]s\n    pub app_evt_send: UnboundedSender<DaemonCommand>,\n    pub script_var_handler: ScriptVarHandlerHandle,\n\n    /// Senders that will cancel a windows auto-close timer when started with --duration.\n    pub window_close_timer_abort_senders: HashMap<String, futures::channel::oneshot::Sender<()>>,\n\n    pub paths: EwwPaths,\n    pub phantom: PhantomData<B>,\n}\n\nimpl<B: DisplayBackend> std::fmt::Debug for App<B> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"App\")\n            .field(\"scope_graph\", &*self.scope_graph.borrow())\n            .field(\"eww_config\", &self.eww_config)\n            .field(\"open_windows\", &self.open_windows)\n            .field(\"failed_windows\", &self.failed_windows)\n            .field(\"window_arguments\", &self.instance_id_to_args)\n            .field(\"paths\", &self.paths)\n            .finish()\n    }\n}\n\n/// Wait until the .model() is available for all monitors (or there is a timeout)\nasync fn wait_for_monitor_model() {\n    let display = gdk::Display::default().expect(\"could not get default display\");\n    let start = std::time::Instant::now();\n    loop {\n        let all_monitors_set =\n            (0..display.n_monitors()).all(|i| display.monitor(i).and_then(|monitor| monitor.model()).is_some());\n        if all_monitors_set {\n            break;\n        }\n        tokio::time::sleep(Duration::from_millis(10)).await;\n        if std::time::Instant::now() - start > Duration::from_millis(500) {\n            log::warn!(\"Timed out waiting for monitor model to be set\");\n            break;\n        }\n    }\n}\n\nimpl<B: DisplayBackend> App<B> {\n    /// Handle a [`DaemonCommand`] event, logging any errors that occur.\n    pub async fn handle_command(&mut self, event: DaemonCommand) {\n        if let Err(err) = self.try_handle_command(event).await {\n            error_handling_ctx::print_error(err);\n        }\n    }\n\n    /// Try to handle a [`DaemonCommand`] event.\n    async fn try_handle_command(&mut self, event: DaemonCommand) -> Result<()> {\n        log::debug!(\"Handling event: {:?}\", &event);\n        match event {\n            DaemonCommand::NoOp => {}\n            DaemonCommand::OpenInspector => {\n                gtk::Window::set_interactive_debugging(true);\n            }\n            DaemonCommand::UpdateVars(mappings) => {\n                for (var_name, new_value) in mappings {\n                    self.update_global_variable(var_name, new_value);\n                }\n            }\n            DaemonCommand::PollVars(names) => {\n                for var_name in names {\n                    self.force_poll_variable(var_name);\n                }\n            }\n            DaemonCommand::ReloadConfigAndCss(sender) => {\n                // Wait for all monitor models to be set. When a new monitor gets added, this\n                // might not immediately be the case. And if we were to wait inside the\n                // connect_monitor_added callback, model() never gets set. So instead we wait here.\n                wait_for_monitor_model().await;\n                let mut errors = Vec::new();\n\n                let config_result = config::read_from_eww_paths(&self.paths);\n                if let Err(e) = config_result.and_then(|new_config| self.load_config(new_config)) {\n                    errors.push(e)\n                }\n                match crate::config::scss::parse_scss_from_config(self.paths.get_config_dir()) {\n                    Ok((file_id, css)) => {\n                        if let Err(e) = self.load_css(file_id, &css) {\n                            errors.push(anyhow!(e));\n                        }\n                    }\n                    Err(e) => {\n                        errors.push(e);\n                    }\n                }\n\n                sender.respond_with_error_list(errors)?;\n            }\n            DaemonCommand::KillServer => {\n                log::info!(\"Received kill command, stopping server!\");\n                self.stop_application();\n            }\n            DaemonCommand::CloseAll => {\n                log::info!(\"Received close command, closing all windows\");\n                for window_name in self.open_windows.keys().cloned().collect::<Vec<String>>() {\n                    self.close_window(&window_name, false)?;\n                }\n            }\n            DaemonCommand::OpenMany { windows, args, should_toggle, sender } => {\n                let errors = windows\n                    .iter()\n                    .map(|w| {\n                        let (config_name, id) = w;\n                        if should_toggle && self.open_windows.contains_key(id) {\n                            self.close_window(id, false)\n                        } else {\n                            log::debug!(\"Config: {}, id: {}\", config_name, id);\n                            let window_args = args\n                                .iter()\n                                .filter(|(win_id, ..)| win_id.is_empty() || win_id == id)\n                                .map(|(_, n, v)| (n.clone(), v.clone()))\n                                .collect();\n                            self.open_window(&WindowArguments::new_from_args(id.to_string(), config_name.clone(), window_args)?)\n                        }\n                    })\n                    .filter_map(Result::err);\n                sender.respond_with_error_list(errors)?;\n            }\n            DaemonCommand::OpenWindow {\n                window_name,\n                instance_id,\n                pos,\n                size,\n                anchor,\n                screen: monitor,\n                should_toggle,\n                duration,\n                sender,\n                args,\n            } => {\n                let instance_id = instance_id.unwrap_or_else(|| window_name.clone());\n\n                let is_open = self.open_windows.contains_key(&instance_id);\n\n                let result = if should_toggle && is_open {\n                    self.close_window(&instance_id, false)\n                } else {\n                    self.open_window(&WindowArguments {\n                        instance_id,\n                        window_name,\n                        pos,\n                        size,\n                        monitor,\n                        anchor,\n                        duration,\n                        args: args.unwrap_or_default().into_iter().collect(),\n                    })\n                };\n\n                sender.respond_with_result(result)?;\n            }\n            DaemonCommand::CloseWindows { windows, auto_reopen, sender } => {\n                let errors = windows.iter().map(|window| self.close_window(window, auto_reopen)).filter_map(Result::err);\n                // Ignore sending errors, as the channel might already be closed\n                let _ = sender.respond_with_error_list(errors);\n            }\n            DaemonCommand::PrintState { all, sender } => {\n                let scope_graph = self.scope_graph.borrow();\n                let used_globals_names = scope_graph.currently_used_globals();\n                let output = scope_graph\n                    .global_scope()\n                    .data\n                    .iter()\n                    .filter(|(key, _)| all || used_globals_names.contains(*key))\n                    .map(|(key, value)| format!(\"{}: {}\", key, value))\n                    .join(\"\\n\");\n                sender.send_success(output)?\n            }\n            DaemonCommand::GetVar { name, sender } => {\n                let scope_graph = &*self.scope_graph.borrow();\n                let vars = &scope_graph.global_scope().data;\n                match vars.get(name.as_str()) {\n                    Some(x) => sender.send_success(x.to_string())?,\n                    None => sender.send_failure(format!(\"Variable not found \\\"{}\\\"\", name))?,\n                }\n            }\n            DaemonCommand::ListWindows(sender) => {\n                let output = self.eww_config.get_windows().keys().join(\"\\n\");\n                sender.send_success(output)?\n            }\n            DaemonCommand::ListActiveWindows(sender) => {\n                let output = self.open_windows.iter().map(|(id, window)| format!(\"{id}: {}\", window.name)).join(\"\\n\");\n                sender.send_success(output)?\n            }\n            DaemonCommand::PrintDebug(sender) => {\n                let output = format!(\"{:#?}\", &self);\n                sender.send_success(output)?\n            }\n            DaemonCommand::PrintGraph(sender) => sender.send_success(self.scope_graph.borrow().visualize())?,\n        }\n        Ok(())\n    }\n\n    /// Fully stop eww:\n    /// close all windows, stop the script_var_handler, quit the gtk appliaction and send the exit instruction to the lifecycle manager\n    fn stop_application(&mut self) {\n        self.script_var_handler.stop_all();\n        for (_, window) in self.open_windows.drain() {\n            window.close();\n        }\n        gtk::main_quit();\n        let _ = crate::application_lifecycle::send_exit();\n    }\n\n    fn update_global_variable(&mut self, name: VarName, value: DynVal) {\n        let result = self.scope_graph.borrow_mut().update_global_value(&name, value);\n        if let Err(err) = result {\n            error_handling_ctx::print_error(err);\n        }\n\n        self.apply_run_while_expressions_mentioning(&name);\n    }\n\n    /// Variables may be referenced in defpoll :run-while expressions.\n    /// Thus, when a variable changes, the run-while conditions of all variables\n    /// that mention the changed variable need to be reevaluated and reapplied.\n    fn apply_run_while_expressions_mentioning(&mut self, name: &VarName) {\n        let mentioning_vars = match self.eww_config.get_run_while_mentions_of(name) {\n            Some(x) => x,\n            None => return,\n        };\n        let mentioning_vars = mentioning_vars.iter().filter_map(|name| self.eww_config.get_script_var(name).ok());\n        for var in mentioning_vars {\n            if let ScriptVarDefinition::Poll(poll_var) = var {\n                let scope_graph = self.scope_graph.borrow();\n                let run_while_result = scope_graph\n                    .evaluate_simplexpr_in_scope(scope_graph.root_index, &poll_var.run_while_expr)\n                    .map(|v| v.as_bool());\n                match run_while_result {\n                    Ok(Ok(true)) => self.script_var_handler.add(var.clone()),\n                    Ok(Ok(false)) => self.script_var_handler.stop_for_variable(poll_var.name.clone()),\n                    Ok(Err(err)) => error_handling_ctx::print_error(anyhow!(err)),\n                    Err(err) => error_handling_ctx::print_error(anyhow!(err)),\n                };\n            }\n        }\n    }\n\n    fn force_poll_variable(&mut self, name: VarName) {\n        match self.eww_config.get_script_var(&name) {\n            Err(err) => error_handling_ctx::print_error(err),\n            Ok(var) => {\n                if let ScriptVarDefinition::Poll(poll_var) = var {\n                    log::debug!(\"force-polling var {}\", &name);\n                    match script_var_handler::run_poll_once(&poll_var) {\n                        Err(err) => error_handling_ctx::print_error(err),\n                        Ok(value) => self.update_global_variable(name, value),\n                    }\n                } else {\n                    error_handling_ctx::print_error(anyhow!(\"Script var '{}' is not polling\", name))\n                }\n            }\n        }\n    }\n\n    /// Close a window and do all the required cleanups in the scope_graph and script_var_handler\n    fn close_window(&mut self, instance_id: &str, auto_reopen: bool) -> Result<()> {\n        if let Some(old_abort_send) = self.window_close_timer_abort_senders.remove(instance_id) {\n            _ = old_abort_send.send(());\n        }\n        let eww_window = self\n            .open_windows\n            .remove(instance_id)\n            .with_context(|| format!(\"Tried to close window with id '{instance_id}', but no such window was open\"))?;\n\n        let scope_index = eww_window.scope_index;\n        eww_window.close();\n\n        self.scope_graph.borrow_mut().remove_scope(scope_index);\n\n        let unused_variables = self.scope_graph.borrow().currently_unused_globals();\n        for unused_var in unused_variables {\n            log::debug!(\"stopping script-var {}\", &unused_var);\n            self.script_var_handler.stop_for_variable(unused_var.clone());\n        }\n\n        if auto_reopen {\n            self.failed_windows.insert(instance_id.to_string());\n            // There might be an alternative monitor available already, so try to re-open it immediately.\n            // This can happen for example when a monitor gets disconnected and another connected,\n            // and the connection event happens before the disconnect.\n            if let Some(window_arguments) = self.instance_id_to_args.get(instance_id) {\n                let _ = self.open_window(&window_arguments.clone());\n            }\n        } else {\n            self.instance_id_to_args.remove(instance_id);\n        }\n\n        Ok(())\n    }\n\n    fn open_window(&mut self, window_args: &WindowArguments) -> Result<()> {\n        let instance_id = &window_args.instance_id;\n        self.failed_windows.remove(instance_id);\n        log::info!(\"Opening window {} as '{}'\", window_args.window_name, instance_id);\n\n        // if an instance of this is already running, close it\n        if self.open_windows.contains_key(instance_id) {\n            self.close_window(instance_id, false)?;\n        }\n\n        self.instance_id_to_args.insert(instance_id.to_string(), window_args.clone());\n\n        let open_result: Result<_> = (|| {\n            let window_name: &str = &window_args.window_name;\n\n            let window_def = self.eww_config.get_window(window_name)?.clone();\n            assert_eq!(window_def.name, window_name, \"window definition name did not equal the called window\");\n\n            let initiator = WindowInitiator::new(&window_def, window_args)?;\n\n            let root_index = self.scope_graph.borrow().root_index;\n\n            let scoped_vars_literal = initiator.get_scoped_vars().into_iter().map(|(k, v)| (k, SimplExpr::Literal(v))).collect();\n\n            let window_scope = self.scope_graph.borrow_mut().register_new_scope(\n                instance_id.to_string(),\n                Some(root_index),\n                root_index,\n                scoped_vars_literal,\n            )?;\n\n            let root_widget = crate::widgets::build_widget::build_gtk_widget(\n                &mut self.scope_graph.borrow_mut(),\n                Rc::new(self.eww_config.get_widget_definitions().clone()),\n                window_scope,\n                window_def.widget,\n                None,\n            )?;\n\n            root_widget.style_context().add_class(window_name);\n\n            let monitor = get_gdk_monitor(initiator.monitor.clone())?;\n            let mut eww_window = initialize_window::<B>(&initiator, monitor, root_widget, window_scope)?;\n            eww_window.gtk_window.style_context().add_class(window_name);\n\n            // initialize script var handlers for variables. As starting a scriptvar with the script_var_handler is idempodent,\n            // we can just start script vars that are already running without causing issues\n            // TODO maybe this could be handled by having a track_newly_used_variables function in the scope tree?\n            for used_var in self.scope_graph.borrow().variables_used_in_self_or_subscopes_of(eww_window.scope_index) {\n                if let Ok(script_var) = self.eww_config.get_script_var(&used_var) {\n                    self.script_var_handler.add(script_var.clone());\n                }\n            }\n\n            eww_window.destroy_event_handler_id = Some(eww_window.gtk_window.connect_destroy({\n                let app_evt_sender = self.app_evt_send.clone();\n                let instance_id = instance_id.to_string();\n                move |_| {\n                    // we don't care about the actual error response from the daemon as this is mostly just a fallback.\n                    // Generally, this should get disconnected before the gtk window gets destroyed.\n                    // This callback is triggered in 2 cases:\n                    // - When the monitor of this window gets disconnected\n                    // - When the window is closed manually.\n                    // We don't distinguish here and assume the window should be reopened once a monitor\n                    // becomes available again\n                    let (response_sender, _) = daemon_response::create_pair();\n                    let command = DaemonCommand::CloseWindows {\n                        windows: vec![instance_id.clone()],\n                        auto_reopen: true,\n                        sender: response_sender,\n                    };\n                    if let Err(err) = app_evt_sender.send(command) {\n                        log::error!(\"Error sending close window command to daemon after gtk window destroy event: {}\", err);\n                    }\n                }\n            }));\n\n            let duration = window_args.duration;\n            if let Some(duration) = duration {\n                let app_evt_sender = self.app_evt_send.clone();\n\n                let (abort_send, abort_recv) = futures::channel::oneshot::channel();\n\n                glib::MainContext::default().spawn_local({\n                    let instance_id = instance_id.to_string();\n                    async move {\n                        tokio::select! {\n                            _ = glib::timeout_future(duration) => {\n                                let (response_sender, mut response_recv) = daemon_response::create_pair();\n                                let command = DaemonCommand::CloseWindows { windows: vec![instance_id.clone()], auto_reopen: false, sender: response_sender };\n                                if let Err(err) = app_evt_sender.send(command) {\n                                    log::error!(\"Error sending close window command to daemon after gtk window destroy event: {}\", err);\n                                }\n                                _ = response_recv.recv().await;\n                            }\n                            _ = abort_recv => {}\n                        }\n                    }\n                });\n\n                if let Some(old_abort_send) = self.window_close_timer_abort_senders.insert(instance_id.to_string(), abort_send) {\n                    _ = old_abort_send.send(());\n                }\n            }\n\n            self.open_windows.insert(instance_id.to_string(), eww_window);\n            Ok(())\n        })();\n\n        if let Err(err) = open_result {\n            self.failed_windows.insert(instance_id.to_string());\n            Err(err).with_context(|| format!(\"failed to open window `{}`\", instance_id))\n        } else {\n            Ok(())\n        }\n    }\n\n    /// Load the given configuration, reloading all script-vars and attempting to reopen all windows that where opened.\n    pub fn load_config(&mut self, config: config::EwwConfig) -> Result<()> {\n        log::info!(\"Reloading windows\");\n\n        self.script_var_handler.stop_all();\n        let old_handler = std::mem::replace(&mut self.script_var_handler, script_var_handler::init(self.app_evt_send.clone()));\n        old_handler.join_thread();\n\n        log::trace!(\"loading config: {:#?}\", config);\n\n        self.eww_config = config;\n        self.scope_graph.borrow_mut().clear(self.eww_config.generate_initial_state()?);\n\n        let open_window_ids: Vec<String> =\n            self.open_windows.keys().cloned().chain(self.failed_windows.iter().cloned()).dedup().collect();\n        for instance_id in &open_window_ids {\n            let window_arguments = self.instance_id_to_args.get(instance_id).with_context(|| {\n                format!(\"Cannot reopen window, initial parameters were not saved correctly for {instance_id}\")\n            })?;\n            self.open_window(&window_arguments.clone())?;\n        }\n        Ok(())\n    }\n\n    /// Load a given CSS string into the gtk css provider, returning a nicely formatted [`DiagError`] when GTK errors out\n    pub fn load_css(&mut self, file_id: usize, css: &str) -> Result<()> {\n        if let Err(err) = self.css_provider.load_from_data(css.as_bytes()) {\n            static PATTERN: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(r\"[^:]*:(\\d+):(\\d+)(.*)$\").unwrap());\n            let nice_error_option: Option<_> = (|| {\n                let captures = PATTERN.captures(err.message())?;\n                let line = captures.get(1).unwrap().as_str().parse::<usize>().ok()?;\n                let msg = captures.get(3).unwrap().as_str();\n                let db = error_handling_ctx::FILE_DATABASE.read().ok()?;\n                let line_range = db.line_range(file_id, line - 1).ok()?;\n                let span = Span(line_range.start, line_range.end - 1, file_id);\n                Some(DiagError(gen_diagnostic!(msg, span)))\n            })();\n            match nice_error_option {\n                Some(error) => Err(anyhow!(error)),\n                None => Err(anyhow!(\"CSS error: {}\", err.message())),\n            }\n        } else {\n            Ok(())\n        }\n    }\n}\n\nfn initialize_window<B: DisplayBackend>(\n    window_init: &WindowInitiator,\n    monitor: Monitor,\n    root_widget: gtk::Widget,\n    window_scope: ScopeIndex,\n) -> Result<EwwWindow> {\n    let monitor_geometry = monitor.geometry();\n    let (actual_window_rect, x, y) = match window_init.geometry {\n        Some(geometry) => {\n            let rect = get_window_rectangle(geometry, monitor_geometry);\n            (Some(rect), rect.x(), rect.y())\n        }\n        _ => (None, 0, 0),\n    };\n    let window = B::initialize_window(window_init, monitor_geometry, x, y)\n        .with_context(|| format!(\"monitor {} is unavailable\", window_init.monitor.clone().unwrap()))?;\n\n    window.set_title(&format!(\"Eww - {}\", window_init.name));\n    window.set_position(gtk::WindowPosition::None);\n    window.set_gravity(gdk::Gravity::Center);\n\n    if let Some(actual_window_rect) = actual_window_rect {\n        window.set_size_request(actual_window_rect.width(), actual_window_rect.height());\n        window.set_default_size(actual_window_rect.width(), actual_window_rect.height());\n    }\n    window.set_decorated(false);\n    window.set_skip_taskbar_hint(true);\n    window.set_skip_pager_hint(true);\n\n    // run on_screen_changed to set the visual correctly initially.\n    on_screen_changed(&window, None);\n    window.connect_screen_changed(on_screen_changed);\n\n    window.add(&root_widget);\n\n    window.realize();\n\n    #[cfg(feature = \"x11\")]\n    if B::IS_X11 {\n        if let Some(geometry) = window_init.geometry {\n            let _ = apply_window_position(geometry, monitor_geometry, &window);\n            if window_init.backend_options.x11.window_type != yuck::config::backend_window_options::X11WindowType::Normal {\n                window.connect_configure_event(move |window, _| {\n                    let _ = apply_window_position(geometry, monitor_geometry, window);\n                    false\n                });\n            }\n        }\n        display_backend::set_xprops(&window, monitor, window_init)?;\n    }\n\n    window.show_all();\n\n    Ok(EwwWindow {\n        name: window_init.name.clone(),\n        gtk_window: window,\n        scope_index: window_scope,\n        destroy_event_handler_id: None,\n    })\n}\n\n/// Apply the provided window-positioning rules to the window.\n#[cfg(feature = \"x11\")]\nfn apply_window_position(mut window_geometry: WindowGeometry, monitor_geometry: gdk::Rectangle, window: &Window) -> Result<()> {\n    let gdk_window = window.window().context(\"Failed to get gdk window from gtk window\")?;\n    window_geometry.size = Coords::from_pixels(window.size());\n    let actual_window_rect = get_window_rectangle(window_geometry, monitor_geometry);\n\n    let gdk_origin = gdk_window.origin();\n\n    if actual_window_rect.x() != gdk_origin.1 || actual_window_rect.y() != gdk_origin.2 {\n        gdk_window.move_(actual_window_rect.x(), actual_window_rect.y());\n    }\n\n    Ok(())\n}\n\nfn on_screen_changed(window: &Window, _old_screen: Option<&gdk::Screen>) {\n    let visual = gtk::prelude::GtkWindowExt::screen(window)\n        .and_then(|screen| screen.rgba_visual().filter(|_| screen.is_composited()).or_else(|| screen.system_visual()));\n    window.set_visual(visual.as_ref());\n}\n\n/// Get the monitor geometry of a given monitor, or the default if none is given\nfn get_gdk_monitor(identifier: Option<MonitorIdentifier>) -> Result<Monitor> {\n    let display = gdk::Display::default().expect(\"could not get default display\");\n    let monitor = match identifier {\n        Some(ident) => {\n            let mon = get_monitor_from_display(&display, &ident);\n            mon.with_context(|| {\n                let head = format!(\"Failed to get monitor {}\\nThe available monitors are:\", ident);\n                let mut body = String::new();\n                for m in 0..display.n_monitors() {\n                    if let Some(model) = display.monitor(m).and_then(|x| x.model()) {\n                        body.push_str(format!(\"\\n\\t[{}] {}\", m, model).as_str());\n                    }\n                }\n                format!(\"{}{}\", head, body)\n            })?\n        }\n        None => display\n            .primary_monitor()\n            .context(\"Failed to get primary monitor from GTK. Try explicitly specifying the monitor on your window.\")?,\n    };\n    Ok(monitor)\n}\n\n/// Get the name of monitor plug for given monitor number\n/// workaround gdk not providing this information on wayland in regular calls\n/// gdk_screen_get_monitor_plug_name is deprecated but works fine for that case\nfn get_monitor_plug_name(display: &gdk::Display, monitor_num: i32) -> Option<&str> {\n    unsafe {\n        use glib::translate::ToGlibPtr;\n        let plug_name_pointer = gdk_sys::gdk_screen_get_monitor_plug_name(display.default_screen().to_glib_none().0, monitor_num);\n        use std::ffi::CStr;\n        CStr::from_ptr(plug_name_pointer).to_str().ok()\n    }\n}\n\n/// Returns the [Monitor][gdk::Monitor] structure corresponding to the identifer.\n/// Outside of x11, only [MonitorIdentifier::Numeric] is supported\npub fn get_monitor_from_display(display: &gdk::Display, identifier: &MonitorIdentifier) -> Option<gdk::Monitor> {\n    match identifier {\n        MonitorIdentifier::List(list) => {\n            for ident in list {\n                if let Some(monitor) = get_monitor_from_display(display, ident) {\n                    return Some(monitor);\n                }\n            }\n            None\n        }\n        MonitorIdentifier::Primary => display.primary_monitor(),\n        MonitorIdentifier::Numeric(num) => display.monitor(*num),\n        MonitorIdentifier::Name(name) => {\n            for m in 0..display.n_monitors() {\n                if let Some(model) = display.monitor(m).and_then(|x| x.model()) {\n                    if model == *name || Some(name.as_str()) == get_monitor_plug_name(display, m) {\n                        return display.monitor(m);\n                    }\n                }\n            }\n            None\n        }\n    }\n}\n\npub fn get_window_rectangle(geometry: WindowGeometry, screen_rect: gdk::Rectangle) -> gdk::Rectangle {\n    let (offset_x, offset_y) = geometry.offset.relative_to(screen_rect.width(), screen_rect.height());\n    let (width, height) = geometry.size.relative_to(screen_rect.width(), screen_rect.height());\n    let x = screen_rect.x() + offset_x + geometry.anchor_point.x.alignment_to_coordinate(width, screen_rect.width());\n    let y = screen_rect.y() + offset_y + geometry.anchor_point.y.alignment_to_coordinate(height, screen_rect.height());\n    gdk::Rectangle::new(x, y, width, height)\n}\n"
  },
  {
    "path": "crates/eww/src/application_lifecycle.rs",
    "content": "//! Module concerned with handling the global application lifecycle of eww.\n//! Currently, this only means handling application exit by providing a global\n//! `recv_exit()` function which can be awaited to receive an event in case of application termination.\n\nuse anyhow::{Context, Result};\nuse once_cell::sync::Lazy;\nuse tokio::sync::broadcast;\n\npub static APPLICATION_EXIT_SENDER: Lazy<broadcast::Sender<()>> = Lazy::new(|| broadcast::channel(2).0);\n\n/// Notify all listening tasks of the termination of the eww application process.\npub fn send_exit() -> Result<()> {\n    (APPLICATION_EXIT_SENDER).send(()).context(\"Failed to send exit lifecycle event\")?;\n    Ok(())\n}\n\n/// Yields Ok(()) on application termination. Await on this in all long-running tasks\n/// and perform any cleanup if necessary.\npub async fn recv_exit() -> Result<()> {\n    (APPLICATION_EXIT_SENDER).subscribe().recv().await.context(\"Failed to receive lifecycle event\")\n}\n\n/// Select in a loop, breaking once a application termination event (see `crate::application_lifecycle`) is received.\n#[macro_export]\nmacro_rules! loop_select_exiting {\n    ($($content:tt)*) => {\n        loop {\n            tokio::select! {\n                Ok(()) = $crate::application_lifecycle::recv_exit() => {\n                    break;\n                }\n                $($content)*\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "crates/eww/src/client.rs",
    "content": "use std::process::Stdio;\n\nuse crate::{\n    daemon_response::DaemonResponse,\n    opts::{self, ActionClientOnly},\n    paths::EwwPaths,\n};\nuse anyhow::{Context, Result};\nuse std::{\n    io::{Read, Write},\n    os::unix::net::UnixStream,\n};\n\npub fn handle_client_only_action(paths: &EwwPaths, action: ActionClientOnly) -> Result<()> {\n    match action {\n        ActionClientOnly::Logs => {\n            std::process::Command::new(\"tail\")\n                .args([\"-f\", paths.get_log_file().to_string_lossy().as_ref()].iter())\n                .stdin(Stdio::null())\n                .spawn()?\n                .wait()?;\n        }\n    }\n    Ok(())\n}\n\n/// Connect to the daemon and send the given request.\n/// Returns the response from the daemon, or None if the daemon did not provide any useful response. An Ok(None) response does _not_ indicate failure.\npub fn do_server_call(stream: &mut UnixStream, action: &opts::ActionWithServer) -> Result<Option<DaemonResponse>> {\n    log::debug!(\"Forwarding options to server\");\n    stream.set_nonblocking(false).context(\"Failed to set stream to non-blocking\")?;\n\n    let message_bytes = bincode::serialize(&action)?;\n\n    stream.write(&(message_bytes.len() as u32).to_be_bytes()).context(\"Failed to send command size header to IPC stream\")?;\n\n    stream.write_all(&message_bytes).context(\"Failed to write command to IPC stream\")?;\n\n    let mut buf = Vec::new();\n    stream.set_read_timeout(Some(std::time::Duration::from_millis(100))).context(\"Failed to set read timeout\")?;\n    stream.read_to_end(&mut buf).context(\"Error reading response from server\")?;\n\n    Ok(if buf.is_empty() {\n        None\n    } else {\n        let buf = bincode::deserialize(&buf)?;\n        Some(buf)\n    })\n}\n"
  },
  {
    "path": "crates/eww/src/config/eww_config.rs",
    "content": "use anyhow::{bail, Context, Result};\nuse eww_shared_util::VarName;\nuse std::collections::HashMap;\nuse yuck::{\n    config::{\n        script_var_definition::ScriptVarDefinition, validate::ValidationError, widget_definition::WidgetDefinition,\n        window_definition::WindowDefinition, Config,\n    },\n    error::DiagError,\n    format_diagnostic::ToDiagnostic,\n};\n\nuse simplexpr::dynval::DynVal;\n\nuse crate::{config::inbuilt, error_handling_ctx, file_database::FileDatabase, paths::EwwPaths, widgets::widget_definitions};\n\nuse super::script_var;\n\n/// Load an [`EwwConfig`] from the config dir of the given [`crate::EwwPaths`],\n/// resetting and applying the global YuckFiles object in [`crate::error_handling_ctx`].\npub fn read_from_eww_paths(eww_paths: &EwwPaths) -> Result<EwwConfig> {\n    error_handling_ctx::clear_files();\n    EwwConfig::read_from_dir(&mut error_handling_ctx::FILE_DATABASE.write().unwrap(), eww_paths)\n}\n\n/// Eww configuration structure.\n#[derive(Debug, Clone, Default)]\npub struct EwwConfig {\n    widgets: HashMap<String, WidgetDefinition>,\n    windows: HashMap<String, WindowDefinition>,\n    initial_variables: HashMap<VarName, DynVal>,\n    script_vars: HashMap<VarName, ScriptVarDefinition>,\n\n    // map of variables to all pollvars which refer to them in their run-while-expression\n    run_while_mentions: HashMap<VarName, Vec<VarName>>,\n}\n\nimpl EwwConfig {\n    /// Load an [`EwwConfig`] from the config dir of the given [`crate::EwwPaths`], reading the main config file.\n    pub fn read_from_dir(files: &mut FileDatabase, eww_paths: &EwwPaths) -> Result<Self> {\n        let yuck_path = eww_paths.get_yuck_path();\n        if !yuck_path.exists() {\n            bail!(\"The configuration file `{}` does not exist\", yuck_path.display());\n        }\n        let config = Config::generate_from_main_file(files, yuck_path)?;\n\n        // run some validations on the configuration\n        let magic_globals: Vec<_> =\n            inbuilt::INBUILT_VAR_NAMES.iter().chain(inbuilt::MAGIC_CONSTANT_NAMES).map(|x| VarName::from(*x)).collect();\n        yuck::config::validate::validate(&config, magic_globals)?;\n\n        for (name, def) in &config.widget_definitions {\n            if widget_definitions::BUILTIN_WIDGET_NAMES.contains(&name.as_str()) {\n                return Err(\n                    DiagError(ValidationError::AccidentalBuiltinOverride(def.span, name.to_string()).to_diagnostic()).into()\n                );\n            }\n        }\n\n        let Config { widget_definitions, window_definitions, mut var_definitions, mut script_vars } = config;\n        script_vars.extend(inbuilt::get_inbuilt_vars());\n        var_definitions.extend(inbuilt::get_magic_constants(eww_paths));\n\n        let mut run_while_mentions = HashMap::<VarName, Vec<VarName>>::new();\n        for var in script_vars.values() {\n            if let ScriptVarDefinition::Poll(var) = var {\n                for name in var.run_while_expr.collect_var_refs() {\n                    run_while_mentions.entry(name.clone()).or_default().push(var.name.clone())\n                }\n            }\n        }\n\n        Ok(EwwConfig {\n            windows: window_definitions,\n            widgets: widget_definitions,\n            initial_variables: var_definitions.into_iter().map(|(k, v)| (k, v.initial_value)).collect(),\n            script_vars,\n            run_while_mentions,\n        })\n    }\n\n    // TODO this is kinda ugly\n    pub fn generate_initial_state(&self) -> Result<HashMap<VarName, DynVal>> {\n        let mut vars = self\n            .script_vars\n            .iter()\n            .map(|(name, var)| Ok((name.clone(), script_var::initial_value(var)?)))\n            .collect::<Result<HashMap<_, _>>>()?;\n        vars.extend(self.initial_variables.clone());\n        Ok(vars)\n    }\n\n    pub fn get_windows(&self) -> &HashMap<String, WindowDefinition> {\n        &self.windows\n    }\n\n    pub fn get_window(&self, name: &str) -> Result<&WindowDefinition> {\n        self.windows.get(name).with_context(|| {\n            format!(\n                \"No window named '{}' exists in config.\\nThis may also be caused by your config failing to load properly, \\\n                 please check for any other errors in that case.\",\n                name\n            )\n        })\n    }\n\n    pub fn get_script_var(&self, name: &VarName) -> Result<&ScriptVarDefinition> {\n        self.script_vars.get(name).with_context(|| format!(\"No script var named '{}' exists\", name))\n    }\n\n    pub fn get_widget_definitions(&self) -> &HashMap<String, WidgetDefinition> {\n        &self.widgets\n    }\n\n    /// Given a variable name, get the names of all variables that reference that variable in their run-while (active/inactive) state\n    pub fn get_run_while_mentions_of(&self, name: &VarName) -> Option<&Vec<VarName>> {\n        self.run_while_mentions.get(name)\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/config/inbuilt.rs",
    "content": "use std::collections::HashMap;\n\nuse simplexpr::{dynval::DynVal, SimplExpr};\nuse yuck::config::{\n    script_var_definition::{PollScriptVar, ScriptVarDefinition, VarSource},\n    var_definition::VarDefinition,\n};\n\nuse crate::{config::system_stats::*, paths::EwwPaths};\nuse eww_shared_util::VarName;\n\nmacro_rules! define_builtin_vars {\n    ($($name:literal [$interval:literal] => $fun:expr),*$(,)?) => {\n        pub static INBUILT_VAR_NAMES: &[&'static str] = &[$($name),*];\n        pub fn get_inbuilt_vars() -> HashMap<VarName, ScriptVarDefinition> {\n            maplit::hashmap! {\n                $(\n                VarName::from($name) => ScriptVarDefinition::Poll(PollScriptVar {\n                    name: VarName::from($name),\n                    run_while_expr: SimplExpr::Literal(DynVal::from(true)),\n                    command: VarSource::Function($fun),\n                    initial_value: None,\n                    interval: std::time::Duration::from_secs($interval),\n                    name_span: eww_shared_util::span::Span::DUMMY,\n                })\n                ),*\n            }\n        }\n    }\n}\n\ndefine_builtin_vars! {\n    // @desc EWW_TEMPS - Heat of the components in degree Celsius\n    // @prop { <name>: temperature }\n    \"EWW_TEMPS\" [2] => || Ok(DynVal::from(get_temperatures())),\n\n    // @desc EWW_RAM - Information on ram and swap usage in bytes.\n    // @prop { total_mem, free_mem, total_swap, free_swap, available_mem, used_mem, used_mem_perc }\n    \"EWW_RAM\" [2] => || Ok(DynVal::from(get_ram())),\n\n    // @desc EWW_DISK - Information on on all mounted partitions (Might report inaccurately on some filesystems, like btrfs and zfs) Example: `{EWW_DISK[\"/\"]}`\n    // @prop { <mount_point>: { name, total, free, used, used_perc } }\n    \"EWW_DISK\" [2] => || Ok(DynVal::from(get_disks())),\n\n    // @desc EWW_BATTERY - Battery capacity in percent of the main battery\n    // @prop { <name>: { capacity, status } }\n    \"EWW_BATTERY\" [2] => || Ok(DynVal::from(\n        match get_battery_capacity() {\n            Err(e) => {\n                log::error!(\"Couldn't get the battery capacity: {:?}\", e);\n                \"Error: Check `eww log` for more details\".to_string()\n            }\n            Ok(o) => o,\n        }\n    )),\n\n    // @desc EWW_CPU - Information on the CPU cores: frequency and usage (No MacOS support)\n    // @prop { cores: [{ core, freq, usage }], avg }\n    \"EWW_CPU\" [2] => || Ok(DynVal::from(get_cpus())) ,\n\n    // @desc EWW_NET - Bytes up/down on all interfaces\n    // @prop { <name>: { up, down } }\n    \"EWW_NET\" [2] => || Ok(DynVal::from(net())) ,\n\n    // @desc EWW_TIME - the current UNIX timestamp\n    \"EWW_TIME\" [1] => || Ok(DynVal::from(get_time())) ,\n}\n\nmacro_rules! define_magic_constants {\n    ($eww_paths:ident, $($name:literal => $value:expr),*$(,)?) => {\n        pub static MAGIC_CONSTANT_NAMES: &[&'static str] = &[$($name),*];\n        pub fn get_magic_constants($eww_paths: &EwwPaths) -> HashMap<VarName, VarDefinition> {\n            maplit::hashmap! {\n                $(VarName::from($name) => VarDefinition {\n                    name: VarName::from($name),\n                    initial_value: $value,\n                    span: eww_shared_util::span::Span::DUMMY\n                }),*\n            }\n        }\n    }\n}\ndefine_magic_constants! { eww_paths,\n    // @desc EWW_CONFIG_DIR - Path to the eww configuration of the current process\n    \"EWW_CONFIG_DIR\" => DynVal::from_string(eww_paths.get_config_dir().to_string_lossy().into_owned()),\n\n    // @desc EWW_CMD - eww command running in the current configuration, useful in event handlers. I.e.: `:onclick \"${EWW_CMD} update foo=bar\"`\n    \"EWW_CMD\" => DynVal::from_string(\n        format!(\"\\\"{}\\\" --config \\\"{}\\\"\",\n            std::env::current_exe().map(|x| x.to_string_lossy().into_owned()).unwrap_or_else(|_| \"eww\".to_string()),\n            eww_paths.get_config_dir().to_string_lossy().into_owned()\n        )\n    ),\n    // @desc EWW_EXECUTABLE - Full path of the eww executable\n    \"EWW_EXECUTABLE\" => DynVal::from_string(\n        std::env::current_exe().map(|x| x.to_string_lossy().into_owned()).unwrap_or_else(|_| \"eww\".to_string()),\n    ),\n}\n"
  },
  {
    "path": "crates/eww/src/config/mod.rs",
    "content": "pub mod eww_config;\npub mod inbuilt;\npub mod script_var;\npub mod scss;\npub mod system_stats;\npub mod window_definition;\npub use eww_config::*;\npub use script_var::*;\n"
  },
  {
    "path": "crates/eww/src/config/script_var.rs",
    "content": "use std::process::Command;\n\nuse anyhow::{anyhow, bail, Context, Result};\nuse codespan_reporting::diagnostic::Severity;\nuse eww_shared_util::{Span, VarName};\nuse simplexpr::dynval::DynVal;\nuse yuck::{\n    config::script_var_definition::{ScriptVarDefinition, VarSource},\n    error::DiagError,\n    gen_diagnostic,\n};\n\npub fn create_script_var_failed_warn(span: Span, var_name: &VarName, error_output: &str) -> DiagError {\n    DiagError(gen_diagnostic! {\n        kind = Severity::Warning,\n        msg = format!(\"The script for the `{}`-variable exited unsuccessfully\", var_name),\n        label = span => \"Defined here\",\n        note = error_output,\n    })\n}\n\npub fn initial_value(var: &ScriptVarDefinition) -> Result<DynVal> {\n    match var {\n        ScriptVarDefinition::Poll(x) => match &x.initial_value {\n            Some(value) => Ok(value.clone()),\n            None => match &x.command {\n                VarSource::Function(f) => f()\n                    .map_err(|err| anyhow!(err))\n                    .with_context(|| format!(\"Failed to compute initial value for {}\", &var.name())),\n                VarSource::Shell(span, command) => {\n                    run_command(command).map_err(|e| anyhow!(create_script_var_failed_warn(*span, var.name(), &e.to_string())))\n                }\n            },\n        },\n\n        ScriptVarDefinition::Listen(var) => Ok(var.initial_value.clone()),\n    }\n}\n\n/// Run a command and get the output\npub fn run_command(cmd: &str) -> Result<DynVal> {\n    log::debug!(\"Running command: {}\", cmd);\n    let command = Command::new(\"/bin/sh\").arg(\"-c\").arg(cmd).output()?;\n    if !command.status.success() {\n        bail!(\"Failed with output:\\n{}\", String::from_utf8(command.stderr)?);\n    }\n    let output = String::from_utf8(command.stdout)?;\n    let output = output.trim_matches('\\n');\n    Ok(DynVal::from(output))\n}\n"
  },
  {
    "path": "crates/eww/src/config/scss.rs",
    "content": "use std::path::Path;\n\nuse anyhow::{anyhow, Context};\n\nuse crate::{error_handling_ctx, util::replace_env_var_references};\n\n/// read an (s)css file, replace all environment variable references within it and\n/// then parse it into css.\n/// Also adds the CSS to the [`crate::file_database::FileDatabase`]\npub fn parse_scss_from_config(path: &Path) -> anyhow::Result<(usize, String)> {\n    let css_file = path.join(\"eww.css\");\n    let scss_file = path.join(\"eww.scss\");\n    if css_file.exists() && scss_file.exists() {\n        return Err(anyhow!(\"Encountered both an SCSS and CSS file. Only one of these may exist at a time\"));\n    }\n\n    let (s_css_path, css) = if css_file.exists() {\n        let css_file_content = std::fs::read_to_string(&css_file)\n            .with_context(|| format!(\"Given CSS file doesn't exist: {}\", css_file.display()))?;\n        let css = replace_env_var_references(css_file_content);\n        (css_file, css)\n    } else {\n        let scss_file_content =\n            std::fs::read_to_string(&scss_file).with_context(|| format!(\"Given SCSS file doesn't exist! {}\", path.display()))?;\n        let file_content = replace_env_var_references(scss_file_content);\n        let grass_config = grass::Options::default().load_path(path);\n        let css = grass::from_string(file_content, &grass_config).map_err(|err| anyhow!(\"SCSS parsing error: {}\", err))?;\n        (scss_file, css)\n    };\n\n    let mut file_db = error_handling_ctx::FILE_DATABASE.write().unwrap();\n    let file_id = file_db.insert_string(s_css_path.display().to_string(), css.clone())?;\n    Ok((file_id, css))\n}\n"
  },
  {
    "path": "crates/eww/src/config/system_stats.rs",
    "content": "use crate::util::IterAverage;\nuse anyhow::{Context, Result};\nuse once_cell::sync::Lazy;\nuse std::{fs::read_to_string, sync::Mutex};\nuse sysinfo::System;\n\nstruct RefreshTime(std::time::Instant);\nimpl RefreshTime {\n    pub fn new() -> Self {\n        Self(std::time::Instant::now())\n    }\n\n    pub fn next_refresh(&mut self) -> std::time::Duration {\n        let now = std::time::Instant::now();\n        let duration = now.duration_since(self.0);\n        self.0 = now;\n        duration\n    }\n}\n\nstatic SYSTEM: Lazy<Mutex<System>> = Lazy::new(|| Mutex::new(System::new()));\nstatic DISKS: Lazy<Mutex<sysinfo::Disks>> = Lazy::new(|| Mutex::new(sysinfo::Disks::new_with_refreshed_list()));\nstatic COMPONENTS: Lazy<Mutex<sysinfo::Components>> = Lazy::new(|| Mutex::new(sysinfo::Components::new_with_refreshed_list()));\nstatic NETWORKS: Lazy<Mutex<(RefreshTime, sysinfo::Networks)>> =\n    Lazy::new(|| Mutex::new((RefreshTime::new(), sysinfo::Networks::new_with_refreshed_list())));\n\npub fn get_disks() -> String {\n    let mut disks = DISKS.lock().unwrap();\n    disks.refresh_list();\n    disks.refresh();\n\n    disks\n        .iter()\n        .map(|c| {\n            let total_space = c.total_space();\n            let available_space = c.available_space();\n            let used_space = total_space - available_space;\n\n            (\n                c.mount_point().display().to_string(),\n                serde_json::json!({\n                    \"name\": c.name(),\n                    \"total\": total_space,\n                    \"free\": available_space,\n                    \"used\": used_space,\n                    \"used_perc\": (used_space as f32 / total_space as f32) * 100f32\n                }),\n            )\n        })\n        .collect::<serde_json::Value>()\n        .to_string()\n}\n\npub fn get_ram() -> String {\n    let mut system = SYSTEM.lock().unwrap();\n    system.refresh_memory();\n\n    let total_memory = system.total_memory();\n    let available_memory = system.available_memory();\n    let used_memory = total_memory as f32 - available_memory as f32;\n    serde_json::json!({\n        \"total_mem\": total_memory,\n        \"free_mem\": system.free_memory(),\n        \"total_swap\": system.total_swap(),\n        \"free_swap\": system.free_swap(),\n        \"available_mem\": available_memory,\n        \"used_mem\": used_memory,\n        \"used_mem_perc\": (used_memory / total_memory as f32) * 100f32,\n    })\n    .to_string()\n}\n\npub fn get_temperatures() -> String {\n    let mut components = COMPONENTS.lock().unwrap();\n    components.refresh_list();\n    components.refresh();\n    components\n        .iter()\n        .map(|c| {\n            (\n                c.label().to_uppercase().replace(' ', \"_\"),\n                // It is common for temperatures to report a non-numeric value.\n                // Tolerate it by serializing it as the string \"null\"\n                c.temperature().to_string().replace(\"NaN\", \"\\\"null\\\"\"),\n            )\n        })\n        .collect::<serde_json::Value>()\n        .to_string()\n}\n\npub fn get_cpus() -> String {\n    let mut system = SYSTEM.lock().unwrap();\n    system.refresh_cpu_specifics(sysinfo::CpuRefreshKind::everything());\n    let cpus = system.cpus();\n    serde_json::json!({\n        \"cores\": cpus.iter()\n            .map(|a| {\n                serde_json::json!({\n                    \"core\": a.name(),\n                    \"freq\": a.frequency(),\n                    \"usage\": a.cpu_usage() as i64\n                })\n            }).collect::<Vec<_>>(),\n        \"avg\": cpus.iter().map(|a| a.cpu_usage()).avg()\n    })\n    .to_string()\n}\n\n#[cfg(target_os = \"macos\")]\npub fn get_battery_capacity() -> Result<String> {\n    let capacity = String::from_utf8(\n        std::process::Command::new(\"pmset\")\n            .args(&[\"-g\", \"batt\"])\n            .output()\n            .context(\"\\nError while getting the battery value on macos, with `pmset`: \")?\n            .stdout,\n    )?;\n\n    // Example output of that command:\n    // Now drawing from 'Battery Power'\n    //-InternalBattery-0 (id=11403363)\t100%; discharging; (no estimate) present: true\n    let regex = regex!(r\"[0-9]*%\");\n    let mut number = regex.captures(&capacity).unwrap().get(0).unwrap().as_str().to_string();\n\n    // Removes the % at the end\n    number.pop();\n    Ok(format!(\n        \"{{ \\\"BAT0\\\": {{ \\\"capacity\\\": \\\"{}\\\", \\\"status\\\": \\\"{}\\\" }}}}\",\n        number,\n        capacity.split(\";\").collect::<Vec<&str>>()[1]\n    ))\n}\n\n#[cfg(target_os = \"linux\")]\npub fn get_battery_capacity() -> Result<String> {\n    use std::{collections::HashMap, sync::atomic::AtomicBool};\n\n    #[derive(serde::Serialize)]\n    struct BatteryData {\n        capacity: i64,\n        status: String,\n    }\n\n    #[derive(serde::Serialize)]\n    struct Data {\n        #[serde(flatten)]\n        batteries: HashMap<String, BatteryData>,\n        total_avg: f64,\n    }\n\n    let mut current = 0_f64;\n    let mut total = 0_f64;\n    let mut batteries = HashMap::new();\n    let power_supply_dir = std::path::Path::new(\"/sys/class/power_supply\");\n    let power_supply_entries = power_supply_dir.read_dir().context(\"Couldn't read /sys/class/power_supply directory\")?;\n    for entry in power_supply_entries {\n        let entry = entry?.path();\n        if !entry.is_dir() {\n            continue;\n        }\n        if let (Ok(capacity), Ok(status)) = (read_to_string(entry.join(\"capacity\")), read_to_string(entry.join(\"status\"))) {\n            batteries.insert(\n                entry.file_name().context(\"Couldn't get filename\")?.to_string_lossy().to_string(),\n                BatteryData {\n                    status: status.trim_end_matches('\\n').to_string(),\n                    capacity: capacity.trim_end_matches('\\n').parse::<f64>()?.round() as i64,\n                },\n            );\n            if let (Ok(charge_full), Ok(charge_now), Ok(voltage_now)) = (\n                read_to_string(entry.join(\"charge_full\")),\n                read_to_string(entry.join(\"charge_now\")),\n                read_to_string(entry.join(\"voltage_now\")),\n            ) {\n                // (uAh / 1000000) * U = p and that / one million so that we have microwatt\n                current += ((charge_now.trim_end_matches('\\n').parse::<f64>()? / 1000000_f64)\n                    * voltage_now.trim_end_matches('\\n').parse::<f64>()?)\n                    / 1000000_f64;\n                total += ((charge_full.trim_end_matches('\\n').parse::<f64>()? / 1000000_f64)\n                    * voltage_now.trim_end_matches('\\n').parse::<f64>()?)\n                    / 1000000_f64;\n            } else if let (Ok(energy_full), Ok(energy_now)) =\n                (read_to_string(entry.join(\"energy_full\")), read_to_string(entry.join(\"energy_now\")))\n            {\n                current += energy_now.trim_end_matches('\\n').parse::<f64>()?;\n                total += energy_full.trim_end_matches('\\n').parse::<f64>()?;\n            } else {\n                static WARNED: AtomicBool = AtomicBool::new(false);\n                if !WARNED.load(std::sync::atomic::Ordering::Relaxed) {\n                    WARNED.store(true, std::sync::atomic::Ordering::Relaxed);\n                    log::warn!(\n                        \"Failed to get/calculate uWh: the total_avg value of the battery magic var will probably be a garbage \\\n                         value that can not be trusted.\"\n                    );\n                }\n            }\n        }\n    }\n    if total == 0_f64 {\n        return Ok(String::from(\"\"));\n    }\n\n    Ok(serde_json::to_string(&(Data { batteries, total_avg: (current / total) * 100_f64 })).unwrap())\n}\n\n#[cfg(any(target_os = \"netbsd\", target_os = \"freebsd\", target_os = \"openbsd\"))]\npub fn get_battery_capacity() -> Result<String> {\n    let batteries = String::from_utf8(\n        // I have only tested `apm` on FreeBSD, but it *should* work on all of the listed targets,\n        // based on what I can tell from their online man pages.\n        std::process::Command::new(\"apm\")\n            .output()\n            .context(\"\\nError while getting the battery values on bsd, with `apm`: \")?\n            .stdout,\n    )?;\n\n    // `apm` output should look something like this:\n    // $ apm\n    // ...\n    // Remaining battery life: 87%\n    // Remaining battery time: unknown\n    // Number of batteries: 1\n    // Battery 0\n    //         Battery Status: charging\n    //         Remaining battery life: 87%\n    //         Remaining battery time: unknown\n    // ...\n    // last 4 lines are repeated for each battery.\n    // see also:\n    // https://www.freebsd.org/cgi/man.cgi?query=apm&manpath=FreeBSD+13.1-RELEASE+and+Ports\n    // https://man.openbsd.org/amd64/apm.8\n    // https://man.netbsd.org/apm.8\n    let mut json = String::from('{');\n    let re_total = regex!(r\"(?m)^Remaining battery life: (\\d+)%\");\n    let re_single = regex!(r\"(?sm)^Battery (\\d+):.*?Status: (\\w+).*?(\\d+)%\");\n    for bat in re_single.captures_iter(&batteries) {\n        json.push_str(&format!(\n            r#\"\"BAT{}\": {{ \"status\": \"{}\", \"capacity\": {} }}, \"#,\n            bat.get(1).unwrap().as_str(),\n            bat.get(2).unwrap().as_str(),\n            bat.get(3).unwrap().as_str(),\n        ))\n    }\n\n    json.push_str(&format!(r#\"\"total_avg\": {}}}\"#, re_total.captures(&batteries).unwrap().get(1).unwrap().as_str()));\n    Ok(json)\n}\n\n#[cfg(not(target_os = \"macos\"))]\n#[cfg(not(target_os = \"linux\"))]\n#[cfg(not(target_os = \"netbsd\"))]\n#[cfg(not(target_os = \"freebsd\"))]\n#[cfg(not(target_os = \"openbsd\"))]\npub fn get_battery_capacity() -> Result<String> {\n    Err(anyhow::anyhow!(\"Eww doesn't support your OS for getting the battery capacity\"))\n}\n\npub fn net() -> String {\n    let (ref mut last_refresh, ref mut networks) = &mut *NETWORKS.lock().unwrap();\n\n    networks.refresh_list();\n    let elapsed = last_refresh.next_refresh();\n\n    networks\n        .iter()\n        .map(|(name, data)| {\n            let transmitted = data.transmitted() as f64 / elapsed.as_secs_f64();\n            let received = data.received() as f64 / elapsed.as_secs_f64();\n            (name, serde_json::json!({ \"NET_UP\": transmitted, \"NET_DOWN\": received }))\n        })\n        .collect::<serde_json::Value>()\n        .to_string()\n}\n\npub fn get_time() -> String {\n    chrono::offset::Utc::now().timestamp().to_string()\n}\n"
  },
  {
    "path": "crates/eww/src/config/window_definition.rs",
    "content": "\n"
  },
  {
    "path": "crates/eww/src/daemon_response.rs",
    "content": "//! Types to manage messages that notify the eww client over the result of a command\n//!\n//! Communcation between the daemon and eww client happens via IPC.\n//! If the daemon needs to send messages back to the client as a response to a command (mostly for CLI output),\n//! this happens via the DaemonResponse types\n\nuse anyhow::{Context, Result};\nuse itertools::Itertools;\nuse tokio::sync::mpsc;\n\nuse crate::error_handling_ctx;\n\n/// Response that the app may send as a response to a event.\n/// This is used in `DaemonCommand`s that contain a response sender.\n#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, derive_more::Display)]\npub enum DaemonResponse {\n    Success(String),\n    Failure(String),\n}\n\n#[derive(Debug)]\npub struct DaemonResponseSender(mpsc::UnboundedSender<DaemonResponse>);\n\npub fn create_pair() -> (DaemonResponseSender, mpsc::UnboundedReceiver<DaemonResponse>) {\n    let (sender, recv) = mpsc::unbounded_channel();\n    (DaemonResponseSender(sender), recv)\n}\n\nimpl DaemonResponseSender {\n    pub fn send_success(&self, s: String) -> Result<()> {\n        self.0.send(DaemonResponse::Success(s)).context(\"Failed to send success response from application thread\")\n    }\n\n    pub fn send_failure(&self, s: String) -> Result<()> {\n        self.0.send(DaemonResponse::Failure(s)).context(\"Failed to send failure response from application thread\")\n    }\n\n    /// Given a list of errors, respond with an error value if there are any errors, and respond with success otherwise.\n    pub fn respond_with_error_list(&self, errors: impl IntoIterator<Item = anyhow::Error>) -> Result<()> {\n        let errors = errors.into_iter().map(|e| error_handling_ctx::format_error(&e)).join(\"\\n\");\n        if errors.is_empty() {\n            self.send_success(String::new())\n        } else {\n            self.respond_with_error_msg(errors)\n        }\n    }\n\n    /// In case of an Err, send the error message to a sender.\n    pub fn respond_with_result<T>(&self, result: Result<T>) -> Result<()> {\n        match result {\n            Ok(_) => self.send_success(String::new()),\n            Err(e) => {\n                let formatted = error_handling_ctx::format_error(&e);\n                self.respond_with_error_msg(formatted)\n            }\n        }\n        .context(\"sending response from main thread\")\n    }\n\n    fn respond_with_error_msg(&self, msg: String) -> Result<()> {\n        println!(\"Action failed with error: {}\", msg);\n        self.send_failure(msg)\n    }\n}\n\npub type DaemonResponseReceiver = mpsc::UnboundedReceiver<DaemonResponse>;\n"
  },
  {
    "path": "crates/eww/src/display_backend.rs",
    "content": "use crate::{widgets::window::Window, window_initiator::WindowInitiator};\n\nuse gtk::gdk;\n\n#[cfg(feature = \"wayland\")]\npub use platform_wayland::WaylandBackend;\n\n#[cfg(feature = \"x11\")]\npub use platform_x11::{set_xprops, X11Backend};\n\npub trait DisplayBackend: Send + Sync + 'static {\n    const IS_X11: bool;\n    const IS_WAYLAND: bool;\n\n    fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window>;\n}\n\npub struct NoBackend;\n\nimpl DisplayBackend for NoBackend {\n    const IS_X11: bool = false;\n    const IS_WAYLAND: bool = false;\n\n    fn initialize_window(_window_init: &WindowInitiator, _monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {\n        Some(Window::new(gtk::WindowType::Toplevel, x, y))\n    }\n}\n\n#[cfg(feature = \"wayland\")]\nmod platform_wayland {\n    use super::DisplayBackend;\n    use crate::{widgets::window::Window, window_initiator::WindowInitiator};\n    use gtk::gdk;\n    use gtk::prelude::*;\n    use gtk_layer_shell::{KeyboardMode, LayerShell};\n    use yuck::config::backend_window_options::WlWindowFocusable;\n    use yuck::config::{window_definition::WindowStacking, window_geometry::AnchorAlignment};\n\n    pub struct WaylandBackend;\n\n    impl DisplayBackend for WaylandBackend {\n        const IS_X11: bool = false;\n        const IS_WAYLAND: bool = true;\n\n        fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {\n            let window = Window::new(gtk::WindowType::Toplevel, x, y);\n            // Initialising a layer shell surface\n            window.init_layer_shell();\n            // Sets the monitor where the surface is shown\n            if let Some(ident) = window_init.monitor.clone() {\n                let display = gdk::Display::default().expect(\"could not get default display\");\n                if let Some(monitor) = crate::app::get_monitor_from_display(&display, &ident) {\n                    window.set_monitor(&monitor);\n                } else {\n                    return None;\n                }\n            };\n            window.set_resizable(window_init.resizable);\n\n            // Sets the layer where the layer shell surface will spawn\n            match window_init.stacking {\n                WindowStacking::Foreground => window.set_layer(gtk_layer_shell::Layer::Top),\n                WindowStacking::Background => window.set_layer(gtk_layer_shell::Layer::Background),\n                WindowStacking::Bottom => window.set_layer(gtk_layer_shell::Layer::Bottom),\n                WindowStacking::Overlay => window.set_layer(gtk_layer_shell::Layer::Overlay),\n            }\n\n            if let Some(namespace) = &window_init.backend_options.wayland.namespace {\n                window.set_namespace(namespace);\n            }\n\n            // Sets the keyboard interactivity\n            match window_init.backend_options.wayland.focusable {\n                WlWindowFocusable::None => window.set_keyboard_mode(KeyboardMode::None),\n                WlWindowFocusable::Exclusive => window.set_keyboard_mode(KeyboardMode::Exclusive),\n                WlWindowFocusable::OnDemand => window.set_keyboard_mode(KeyboardMode::OnDemand),\n            }\n\n            if let Some(geometry) = window_init.geometry {\n                // Positioning surface\n                let mut top = false;\n                let mut left = false;\n                let mut right = false;\n                let mut bottom = false;\n\n                match geometry.anchor_point.x {\n                    AnchorAlignment::START => left = true,\n                    AnchorAlignment::CENTER => {}\n                    AnchorAlignment::END => right = true,\n                }\n                match geometry.anchor_point.y {\n                    AnchorAlignment::START => top = true,\n                    AnchorAlignment::CENTER => {}\n                    AnchorAlignment::END => bottom = true,\n                }\n\n                window.set_anchor(gtk_layer_shell::Edge::Left, left);\n                window.set_anchor(gtk_layer_shell::Edge::Right, right);\n                window.set_anchor(gtk_layer_shell::Edge::Top, top);\n                window.set_anchor(gtk_layer_shell::Edge::Bottom, bottom);\n\n                let xoffset = geometry.offset.x.pixels_relative_to(monitor.width());\n                let yoffset = geometry.offset.y.pixels_relative_to(monitor.height());\n\n                if left {\n                    window.set_layer_shell_margin(gtk_layer_shell::Edge::Left, xoffset);\n                } else {\n                    window.set_layer_shell_margin(gtk_layer_shell::Edge::Right, xoffset);\n                }\n                if bottom {\n                    window.set_layer_shell_margin(gtk_layer_shell::Edge::Bottom, yoffset);\n                } else {\n                    window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, yoffset);\n                }\n                // https://github.com/elkowar/eww/issues/296\n                if window_init.backend_options.wayland.exclusive\n                    && geometry.anchor_point.x != AnchorAlignment::CENTER\n                    && geometry.anchor_point.y != AnchorAlignment::CENTER\n                {\n                    log::warn!(\"When ':exclusive true' the anchor has to include 'center', otherwise exlcusive won't work\")\n                }\n            }\n            if window_init.backend_options.wayland.exclusive {\n                window.auto_exclusive_zone_enable();\n            }\n            Some(window)\n        }\n    }\n}\n\n#[cfg(feature = \"x11\")]\nmod platform_x11 {\n    use crate::{widgets::window::Window, window_initiator::WindowInitiator};\n    use anyhow::{Context, Result};\n    use gdk::Monitor;\n    use gtk::gdk;\n    use gtk::{self, prelude::*};\n    use x11rb::protocol::xproto::ConnectionExt;\n\n    use x11rb::{\n        self,\n        connection::Connection,\n        protocol::xproto::*,\n        rust_connection::{DefaultStream, RustConnection},\n    };\n    use yuck::config::{\n        backend_window_options::{Side, X11WindowType},\n        window_definition::WindowStacking,\n    };\n\n    use super::DisplayBackend;\n\n    pub struct X11Backend;\n    impl DisplayBackend for X11Backend {\n        const IS_X11: bool = true;\n        const IS_WAYLAND: bool = false;\n\n        fn initialize_window(window_init: &WindowInitiator, _monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {\n            let window_type =\n                if window_init.backend_options.x11.wm_ignore { gtk::WindowType::Popup } else { gtk::WindowType::Toplevel };\n            let window = Window::new(window_type, x, y);\n            window.set_resizable(window_init.resizable);\n            window.set_keep_above(window_init.stacking == WindowStacking::Foreground);\n            window.set_keep_below(window_init.stacking == WindowStacking::Background);\n            if window_init.backend_options.x11.sticky {\n                window.stick();\n            } else {\n                window.unstick();\n            }\n            Some(window)\n        }\n    }\n\n    pub fn set_xprops(window: &Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {\n        let backend = X11BackendConnection::new()?;\n        backend.set_xprops_for(window, monitor, window_init)?;\n        Ok(())\n    }\n\n    struct X11BackendConnection {\n        conn: RustConnection<DefaultStream>,\n        root_window: u32,\n        atoms: AtomCollection,\n    }\n\n    impl X11BackendConnection {\n        fn new() -> Result<Self> {\n            let (conn, screen_num) = RustConnection::connect(None)?;\n            let screen = conn.setup().roots[screen_num].clone();\n            let atoms = AtomCollection::new(&conn)?.reply()?;\n            Ok(X11BackendConnection { conn, root_window: screen.root, atoms })\n        }\n\n        fn set_xprops_for(&self, window: &Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {\n            let monitor_rect = monitor.geometry();\n            let scale_factor = monitor.scale_factor() as u32;\n            let gdk_window = window.window().context(\"Couldn't get gdk window from gtk window\")?;\n            let win_id =\n                gdk_window.downcast_ref::<gdkx11::X11Window>().context(\"Failed to get x11 window for gtk window\")?.xid() as u32;\n            let strut_def = window_init.backend_options.x11.struts;\n            let root_window_geometry = self.conn.get_geometry(self.root_window)?.reply()?;\n\n            let mon_x = scale_factor * monitor_rect.x() as u32;\n            let mon_y = scale_factor * monitor_rect.y() as u32;\n            let mon_end_x = scale_factor * (monitor_rect.x() + monitor_rect.width()) as u32 - 1u32;\n            let mon_end_y = scale_factor * (monitor_rect.y() + monitor_rect.height()) as u32 - 1u32;\n\n            let dist = match strut_def.side {\n                Side::Left | Side::Right => strut_def.distance.pixels_relative_to(monitor_rect.width()) as u32,\n                Side::Top | Side::Bottom => strut_def.distance.pixels_relative_to(monitor_rect.height()) as u32,\n            };\n\n            // don't question it,.....\n            // it's how the X gods want it to be.\n            // left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x\n            #[rustfmt::skip]\n            let strut_list: Vec<u8> = match strut_def.side {\n                Side::Left   => vec![dist + mon_x, 0,                                                    0,                   0,                                                     mon_x, mon_end_y, 0,     0,         0,     0,         0,             0],\n                Side::Right  => vec![0,            root_window_geometry.width as u32 - mon_end_x + dist, 0,                   0,                                                     0,     0,         mon_x, mon_end_y, 0,     0,         0,             0],\n                Side::Top    => vec![0,            0,                                                    dist + mon_y, 0,                                                     0,     0,         0,     0,         mon_x, mon_end_x, 0,             0],\n                Side::Bottom => vec![0,            0,                                                    0,                   root_window_geometry.height as u32 - mon_end_y + dist, 0,     0,         0,     0,         0,     0,         mon_x,  mon_end_x],\n                // This should never happen but if it does the window will be anchored on the\n                // right of the screen\n            }.iter().flat_map(|x| x.to_le_bytes().to_vec()).collect();\n\n            self.conn\n                .change_property(\n                    PropMode::REPLACE,\n                    win_id,\n                    self.atoms._NET_WM_STRUT,\n                    self.atoms.CARDINAL,\n                    32,\n                    4,\n                    &strut_list[0..16],\n                )?\n                .check()?;\n            self.conn\n                .change_property(\n                    PropMode::REPLACE,\n                    win_id,\n                    self.atoms._NET_WM_STRUT_PARTIAL,\n                    self.atoms.CARDINAL,\n                    32,\n                    12,\n                    &strut_list,\n                )?\n                .check()?;\n\n            // TODO possibly support setting multiple window types\n            x11rb::wrapper::ConnectionExt::change_property32(\n                &self.conn,\n                PropMode::REPLACE,\n                win_id,\n                self.atoms._NET_WM_WINDOW_TYPE,\n                self.atoms.ATOM,\n                &[match window_init.backend_options.x11.window_type {\n                    X11WindowType::Dock => self.atoms._NET_WM_WINDOW_TYPE_DOCK,\n                    X11WindowType::Normal => self.atoms._NET_WM_WINDOW_TYPE_NORMAL,\n                    X11WindowType::Dialog => self.atoms._NET_WM_WINDOW_TYPE_DIALOG,\n                    X11WindowType::Toolbar => self.atoms._NET_WM_WINDOW_TYPE_TOOLBAR,\n                    X11WindowType::Utility => self.atoms._NET_WM_WINDOW_TYPE_UTILITY,\n                    X11WindowType::Desktop => self.atoms._NET_WM_WINDOW_TYPE_DESKTOP,\n                    X11WindowType::Notification => self.atoms._NET_WM_WINDOW_TYPE_NOTIFICATION,\n                }],\n            )?\n            .check()?;\n\n            self.conn.flush().context(\"Failed to send requests to X server\")\n        }\n    }\n\n    x11rb::atom_manager! {\n        pub AtomCollection: AtomCollectionCookie {\n            _NET_WM_WINDOW_TYPE,\n            _NET_WM_WINDOW_TYPE_NORMAL,\n            _NET_WM_WINDOW_TYPE_DOCK,\n            _NET_WM_WINDOW_TYPE_DIALOG,\n            _NET_WM_WINDOW_TYPE_TOOLBAR,\n            _NET_WM_WINDOW_TYPE_UTILITY,\n            _NET_WM_WINDOW_TYPE_DESKTOP,\n            _NET_WM_WINDOW_TYPE_NOTIFICATION,\n            _NET_WM_STATE,\n            _NET_WM_STATE_STICKY,\n            _NET_WM_STATE_ABOVE,\n            _NET_WM_STATE_BELOW,\n            _NET_WM_NAME,\n            _NET_WM_STRUT,\n            _NET_WM_STRUT_PARTIAL,\n            WM_NAME,\n            UTF8_STRING,\n            COMPOUND_TEXT,\n            CARDINAL,\n            ATOM,\n            WM_CLASS,\n            STRING,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/error_handling_ctx.rs",
    "content": "//! Disgusting global state.\n//! I hate this, but [buffet](https://github.com/buffet) told me that this is what I should do for peak maintainability!\n\nuse std::sync::{Arc, RwLock};\n\nuse codespan_reporting::{\n    diagnostic::Diagnostic,\n    term::{self, Chars},\n};\nuse eww_shared_util::Span;\nuse once_cell::sync::Lazy;\nuse simplexpr::{dynval::ConversionError, eval::EvalError};\nuse yuck::{config::validate::ValidationError, error::DiagError, format_diagnostic::ToDiagnostic};\n\nuse crate::file_database::FileDatabase;\n\npub static FILE_DATABASE: Lazy<Arc<RwLock<FileDatabase>>> = Lazy::new(|| Arc::new(RwLock::new(FileDatabase::new())));\n\npub fn clear_files() {\n    *FILE_DATABASE.write().unwrap() = FileDatabase::new();\n}\n\npub fn print_error(err: anyhow::Error) {\n    match anyhow_err_to_diagnostic(&err) {\n        Some(diag) => match stringify_diagnostic(diag) {\n            Ok(diag) => eprintln!(\"{}\", diag),\n            Err(_) => log::error!(\"{:?}\", err),\n        },\n        None => log::error!(\"{:?}\", err),\n    }\n}\n\npub fn format_error(err: &anyhow::Error) -> String {\n    anyhow_err_to_diagnostic(err).and_then(|diag| stringify_diagnostic(diag).ok()).unwrap_or_else(|| format!(\"{:?}\", err))\n}\n\npub fn anyhow_err_to_diagnostic(err: &anyhow::Error) -> Option<Diagnostic<usize>> {\n    #[allow(clippy::manual_map)]\n    if let Some(err) = err.downcast_ref::<DiagError>() {\n        Some(err.0.clone())\n    } else if let Some(err) = err.downcast_ref::<ConversionError>() {\n        Some(err.to_diagnostic())\n    } else if let Some(err) = err.downcast_ref::<ValidationError>() {\n        Some(err.to_diagnostic())\n    } else if let Some(err) = err.downcast_ref::<EvalError>() {\n        Some(err.to_diagnostic())\n    } else {\n        None\n    }\n}\n\npub fn stringify_diagnostic(mut diagnostic: codespan_reporting::diagnostic::Diagnostic<usize>) -> anyhow::Result<String> {\n    diagnostic.labels.retain(|label| !Span(label.range.start, label.range.end, label.file_id).is_dummy());\n\n    let mut config = term::Config::default();\n    let mut chars = Chars::box_drawing();\n    chars.single_primary_caret = '─';\n    config.chars = chars;\n    config.chars.note_bullet = '→';\n    let mut buf = Vec::new();\n    let mut writer = term::termcolor::Ansi::new(&mut buf);\n    let files = FILE_DATABASE.read().unwrap();\n    term::emit(&mut writer, &config, &*files, &diagnostic)?;\n    Ok(String::from_utf8(buf)?)\n}\n"
  },
  {
    "path": "crates/eww/src/file_database.rs",
    "content": "use std::collections::HashMap;\n\nuse codespan_reporting::files::Files;\nuse eww_shared_util::Span;\nuse yuck::{\n    config::file_provider::{FilesError, YuckFileProvider},\n    error::DiagError,\n    parser::ast::Ast,\n};\n\n#[derive(Debug, Clone, Default)]\npub struct FileDatabase {\n    files: HashMap<usize, CodeFile>,\n    latest_id: usize,\n}\n\nimpl FileDatabase {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    fn get_file(&self, id: usize) -> Result<&CodeFile, codespan_reporting::files::Error> {\n        self.files.get(&id).ok_or(codespan_reporting::files::Error::FileMissing)\n    }\n\n    fn insert_code_file(&mut self, file: CodeFile) -> usize {\n        let file_id = self.latest_id;\n        self.files.insert(file_id, file);\n        self.latest_id += 1;\n        file_id\n    }\n\n    pub fn insert_string(&mut self, name: String, content: String) -> Result<usize, DiagError> {\n        let line_starts = codespan_reporting::files::line_starts(&content).collect();\n        let code_file = CodeFile { name, line_starts, source_len_bytes: content.len(), source: CodeSource::Literal(content) };\n        let file_id = self.insert_code_file(code_file);\n        Ok(file_id)\n    }\n}\n\nimpl YuckFileProvider for FileDatabase {\n    fn load_yuck_file(&mut self, path: std::path::PathBuf) -> Result<(Span, Vec<Ast>), FilesError> {\n        let file_content = std::fs::read_to_string(&path)?;\n        let line_starts = codespan_reporting::files::line_starts(&file_content).collect();\n        let code_file = CodeFile {\n            name: path.display().to_string(),\n            line_starts,\n            source_len_bytes: file_content.len(),\n            source: CodeSource::File(path),\n        };\n        let file_id = self.insert_code_file(code_file);\n        Ok(yuck::parser::parse_toplevel(file_id, file_content)?)\n    }\n\n    fn load_yuck_str(&mut self, name: String, content: String) -> Result<(Span, Vec<Ast>), DiagError> {\n        let file_id = self.insert_string(name, content.clone())?;\n        yuck::parser::parse_toplevel(file_id, content)\n    }\n\n    fn unload(&mut self, id: usize) {\n        self.files.remove(&id);\n    }\n}\n\nimpl<'a> Files<'a> for FileDatabase {\n    type FileId = usize;\n    type Name = &'a str;\n    type Source = String;\n\n    fn name(&'a self, id: Self::FileId) -> Result<Self::Name, codespan_reporting::files::Error> {\n        Ok(&self.get_file(id)?.name)\n    }\n\n    fn source(&'a self, id: Self::FileId) -> Result<Self::Source, codespan_reporting::files::Error> {\n        self.get_file(id)?.source.read_content().map_err(codespan_reporting::files::Error::Io)\n    }\n\n    fn line_index(&self, id: Self::FileId, byte_index: usize) -> Result<usize, codespan_reporting::files::Error> {\n        Ok(self.get_file(id)?.line_starts.binary_search(&byte_index).unwrap_or_else(|next_line| next_line - 1))\n    }\n\n    fn line_range(\n        &self,\n        id: Self::FileId,\n        line_index: usize,\n    ) -> Result<std::ops::Range<usize>, codespan_reporting::files::Error> {\n        let file = self.get_file(id)?;\n        let line_start = file.line_start(line_index)?;\n        let next_line_start = file.line_start(line_index + 1)?;\n        Ok(line_start..next_line_start)\n    }\n}\n\n#[derive(Clone, Debug)]\nstruct CodeFile {\n    name: String,\n    line_starts: Vec<usize>,\n    source: CodeSource,\n    source_len_bytes: usize,\n}\n\nimpl CodeFile {\n    /// Return the starting byte index of the line with the specified line index.\n    /// Convenience method that already generates errors if necessary.\n    fn line_start(&self, line_index: usize) -> Result<usize, codespan_reporting::files::Error> {\n        use std::cmp::Ordering;\n\n        match line_index.cmp(&self.line_starts.len()) {\n            Ordering::Less => Ok(self.line_starts.get(line_index).cloned().expect(\"failed despite previous check\")),\n            Ordering::Equal => Ok(self.source_len_bytes),\n            Ordering::Greater => {\n                Err(codespan_reporting::files::Error::LineTooLarge { given: line_index, max: self.line_starts.len() - 1 })\n            }\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\nenum CodeSource {\n    File(std::path::PathBuf),\n    Literal(String),\n}\n\nimpl CodeSource {\n    fn read_content(&self) -> std::io::Result<String> {\n        match self {\n            CodeSource::File(path) => Ok(std::fs::read_to_string(path)?),\n            CodeSource::Literal(x) => Ok(x.to_string()),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/geometry.rs",
    "content": "use derive_more::{Debug, *};\n\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Display)]\n#[display(\".x*.y:.width*.height\")]\npub struct Rect {\n    pub x: i32,\n    pub y: i32,\n    pub width: i32,\n    pub height: i32,\n}\n"
  },
  {
    "path": "crates/eww/src/ipc_server.rs",
    "content": "use crate::{app, opts};\nuse anyhow::{Context, Result};\nuse std::time::Duration;\nuse tokio::{\n    io::{AsyncReadExt, AsyncWriteExt},\n    sync::mpsc::*,\n};\n\npub async fn run_server<P: AsRef<std::path::Path>>(evt_send: UnboundedSender<app::DaemonCommand>, socket_path: P) -> Result<()> {\n    let socket_path = socket_path.as_ref();\n    let listener = { tokio::net::UnixListener::bind(socket_path)? };\n    log::info!(\"IPC server initialized\");\n    crate::loop_select_exiting! {\n        connection = listener.accept() => match connection {\n            Ok((stream, _addr)) => {\n                let evt_send = evt_send.clone();\n                tokio::spawn(async move {\n                    let result = handle_connection(stream, evt_send.clone()).await;\n                    crate::print_result_err!(\"while handling IPC connection with client\", result);\n                });\n            },\n            Err(e) => eprintln!(\"Failed to connect to client: {:?}\", e),\n        }\n    }\n    Ok(())\n}\n\n/// Handle a single IPC connection from start to end.\nasync fn handle_connection(mut stream: tokio::net::UnixStream, evt_send: UnboundedSender<app::DaemonCommand>) -> Result<()> {\n    let (mut stream_read, mut stream_write) = stream.split();\n\n    let action: opts::ActionWithServer = read_action_from_stream(&mut stream_read).await?;\n\n    log::debug!(\"received command from IPC: {:?}\", &action);\n\n    let (command, maybe_response_recv) = action.into_daemon_command();\n\n    evt_send.send(command)?;\n\n    if let Some(mut response_recv) = maybe_response_recv {\n        log::debug!(\"Waiting for response for IPC client\");\n        if let Ok(Some(response)) = tokio::time::timeout(Duration::from_millis(100), response_recv.recv()).await {\n            let response = bincode::serialize(&response)?;\n            let result = &stream_write.write_all(&response).await;\n            crate::print_result_err!(\"sending text response to ipc client\", &result);\n        }\n    }\n    stream_write.shutdown().await?;\n    Ok(())\n}\n\n/// Read a single message from a unix stream, and parses it into a `ActionWithServer`\n/// The format here requires the first 4 bytes to be the size of the rest of the message (in big-endian), followed by the rest of the message.\nasync fn read_action_from_stream(stream_read: &'_ mut tokio::net::unix::ReadHalf<'_>) -> Result<opts::ActionWithServer> {\n    let mut message_byte_length = [0u8; 4];\n    stream_read.read_exact(&mut message_byte_length).await.context(\"Failed to read message size header in IPC message\")?;\n    let message_byte_length = u32::from_be_bytes(message_byte_length);\n    let mut raw_message = Vec::<u8>::with_capacity(message_byte_length as usize);\n    while raw_message.len() < message_byte_length as usize {\n        stream_read.read_buf(&mut raw_message).await.context(\"Failed to read actual IPC message\")?;\n    }\n\n    bincode::deserialize(&raw_message).context(\"Failed to parse client message\")\n}\n"
  },
  {
    "path": "crates/eww/src/main.rs",
    "content": "#![allow(rustdoc::private_intra_doc_links)]\n\nextern crate gtk;\n#[cfg(feature = \"wayland\")]\nextern crate gtk_layer_shell as gtk_layer_shell;\n\nuse anyhow::{Context, Result};\nuse clap::CommandFactory as _;\nuse daemon_response::{DaemonResponse, DaemonResponseReceiver};\nuse display_backend::DisplayBackend;\nuse opts::ActionWithServer;\nuse paths::EwwPaths;\nuse std::{os::unix::net, path::Path, time::Duration};\n\nuse crate::server::ForkResult;\n\nmod app;\nmod application_lifecycle;\nmod client;\nmod config;\nmod daemon_response;\nmod display_backend;\nmod error_handling_ctx;\nmod file_database;\nmod geometry;\nmod ipc_server;\nmod opts;\nmod paths;\nmod script_var_handler;\nmod server;\nmod state;\nmod util;\nmod widgets;\nmod window_arguments;\nmod window_initiator;\n\nfn main() {\n    let eww_binary_name = std::env::args().next().unwrap();\n    let opts: opts::Opt = opts::Opt::from_env();\n\n    let log_level_filter = if opts.log_debug { log::LevelFilter::Debug } else { log::LevelFilter::Info };\n    if std::env::var(\"RUST_LOG\").is_ok() {\n        pretty_env_logger::init_timed();\n    } else {\n        pretty_env_logger::formatted_timed_builder()\n            .filter(Some(\"eww\"), log_level_filter)\n            .filter(Some(\"notifier_host\"), log_level_filter)\n            .init();\n    }\n\n    if let opts::Action::ShellCompletions { shell } = opts.action {\n        clap_complete::generate(shell, &mut opts::RawOpt::command(), \"eww\", &mut std::io::stdout());\n        return;\n    }\n\n    let detected_wayland = detect_wayland();\n    #[allow(unused)]\n    let use_wayland = opts.force_wayland || detected_wayland;\n    #[cfg(all(feature = \"wayland\", feature = \"x11\"))]\n    let result = if use_wayland {\n        log::debug!(\"Running on wayland. force_wayland={}, detected_wayland={}\", opts.force_wayland, detected_wayland);\n        run::<display_backend::WaylandBackend>(opts, eww_binary_name)\n    } else {\n        log::debug!(\"Running on X11. force_wayland={}, detected_wayland={}\", opts.force_wayland, detected_wayland);\n        run::<display_backend::X11Backend>(opts, eww_binary_name)\n    };\n\n    #[cfg(all(not(feature = \"wayland\"), feature = \"x11\"))]\n    let result = {\n        if use_wayland {\n            log::warn!(\"Eww compiled without wayland support. Falling back to X11, eventhough wayland was requested.\");\n        }\n        run::<display_backend::X11Backend>(opts, eww_binary_name)\n    };\n\n    #[cfg(all(feature = \"wayland\", not(feature = \"x11\")))]\n    let result = run::<display_backend::WaylandBackend>(opts, eww_binary_name);\n\n    #[cfg(not(any(feature = \"wayland\", feature = \"x11\")))]\n    let result = run::<display_backend::NoBackend>(opts, eww_binary_name);\n\n    if let Err(err) = result {\n        error_handling_ctx::print_error(err);\n        std::process::exit(1);\n    }\n}\n\nfn detect_wayland() -> bool {\n    let session_type = std::env::var(\"XDG_SESSION_TYPE\").unwrap_or_default();\n    let wayland_display = std::env::var(\"WAYLAND_DISPLAY\").unwrap_or_default();\n    session_type.contains(\"wayland\") || (!wayland_display.is_empty() && !session_type.contains(\"x11\"))\n}\n\nfn run<B: DisplayBackend>(opts: opts::Opt, eww_binary_name: String) -> Result<()> {\n    let paths = opts\n        .config_path\n        .map(EwwPaths::from_config_dir)\n        .unwrap_or_else(EwwPaths::default)\n        .context(\"Failed to initialize eww paths\")?;\n\n    let should_restart = match &opts.action {\n        opts::Action::ShellCompletions { .. } => unreachable!(),\n        opts::Action::Daemon => opts.restart,\n        opts::Action::WithServer(action) => opts.restart && action.can_start_daemon(),\n        opts::Action::ClientOnly(_) => false,\n    };\n    if should_restart {\n        let response = handle_server_command(&paths, &ActionWithServer::KillServer, 1);\n        if let Ok(Some(response)) = response {\n            handle_daemon_response(response);\n        }\n        std::thread::sleep(std::time::Duration::from_millis(200));\n    }\n\n    let would_show_logs = match opts.action {\n        opts::Action::ShellCompletions { .. } => unreachable!(),\n        opts::Action::ClientOnly(action) => {\n            client::handle_client_only_action(&paths, action)?;\n            false\n        }\n\n        // make sure that there isn't already a Eww daemon running.\n        opts::Action::Daemon if check_server_running(paths.get_ipc_socket_file()) => {\n            eprintln!(\"Eww server already running.\");\n            true\n        }\n        opts::Action::Daemon => {\n            log::info!(\"Initializing Eww server. ({})\", paths.get_ipc_socket_file().display());\n            let _ = std::fs::remove_file(paths.get_ipc_socket_file());\n\n            if !opts.show_logs {\n                println!(\"Run `{} logs` to see any errors while editing your configuration.\", eww_binary_name);\n            }\n            let fork_result = server::initialize_server::<B>(paths.clone(), None, !opts.no_daemonize)?;\n            opts.no_daemonize || fork_result == ForkResult::Parent\n        }\n\n        opts::Action::WithServer(ActionWithServer::KillServer) => {\n            if let Some(response) = handle_server_command(&paths, &ActionWithServer::KillServer, 1)? {\n                handle_daemon_response(response);\n            }\n            false\n        }\n\n        // a running daemon is necessary for this command\n        opts::Action::WithServer(action) => {\n            // attempt to just send the command to a running daemon\n            match handle_server_command(&paths, &action, 5) {\n                Ok(Some(response)) => {\n                    handle_daemon_response(response);\n                    true\n                }\n                Ok(None) => true,\n\n                Err(err) if action.can_start_daemon() && !opts.no_daemonize => {\n                    // connecting to the daemon failed. Thus, start the daemon here!\n                    log::warn!(\"Failed to connect to daemon: {}\", err);\n                    log::info!(\"Initializing eww server. ({})\", paths.get_ipc_socket_file().display());\n                    let _ = std::fs::remove_file(paths.get_ipc_socket_file());\n                    if !opts.show_logs {\n                        println!(\"Run `{} logs` to see any errors while editing your configuration.\", eww_binary_name);\n                    }\n\n                    let (command, response_recv) = action.into_daemon_command();\n                    // start the daemon and give it the command\n                    let fork_result = server::initialize_server::<B>(paths.clone(), Some(command), true)?;\n                    let is_parent = fork_result == ForkResult::Parent;\n                    if let (Some(recv), true) = (response_recv, is_parent) {\n                        listen_for_daemon_response(recv);\n                    }\n                    is_parent\n                }\n                Err(err) => Err(err)?,\n            }\n        }\n    };\n\n    if would_show_logs && opts.show_logs {\n        client::handle_client_only_action(&paths, opts::ActionClientOnly::Logs)?;\n    }\n    Ok(())\n}\n\nfn listen_for_daemon_response(mut recv: DaemonResponseReceiver) {\n    let rt = tokio::runtime::Builder::new_current_thread()\n        .thread_name(\"listen-for-daemon-response\")\n        .enable_all()\n        .build()\n        .expect(\"Failed to initialize tokio runtime\");\n    rt.block_on(async {\n        if let Ok(Some(response)) = tokio::time::timeout(Duration::from_millis(100), recv.recv()).await {\n            println!(\"{}\", response);\n        }\n    })\n}\n\n/// attempt to send a command to the daemon and send it the given action repeatedly.\nfn handle_server_command(paths: &EwwPaths, action: &ActionWithServer, connect_attempts: usize) -> Result<Option<DaemonResponse>> {\n    log::debug!(\"Trying to find server process at socket {}\", paths.get_ipc_socket_file().display());\n    let mut stream = attempt_connect(paths.get_ipc_socket_file(), connect_attempts).context(\"Failed to connect to daemon\")?;\n    log::debug!(\"Connected to Eww server ({}).\", &paths.get_ipc_socket_file().display());\n    client::do_server_call(&mut stream, action).context(\"Error while forwarding command to server\")\n}\n\nfn handle_daemon_response(res: DaemonResponse) {\n    match res {\n        DaemonResponse::Success(x) => println!(\"{}\", x),\n        DaemonResponse::Failure(x) => {\n            eprintln!(\"{}\", x);\n            std::process::exit(1);\n        }\n    }\n}\n\nfn attempt_connect(socket_path: impl AsRef<Path>, attempts: usize) -> Option<net::UnixStream> {\n    for _ in 0..attempts {\n        if let Ok(mut con) = net::UnixStream::connect(&socket_path) {\n            if client::do_server_call(&mut con, &opts::ActionWithServer::Ping).is_ok() {\n                return net::UnixStream::connect(&socket_path).ok();\n            }\n        }\n        std::thread::sleep(Duration::from_millis(200));\n    }\n    None\n}\n\n/// Check if a eww server is currently running by trying to send a ping message to it.\nfn check_server_running(socket_path: impl AsRef<Path>) -> bool {\n    let response = net::UnixStream::connect(socket_path)\n        .ok()\n        .and_then(|mut stream| client::do_server_call(&mut stream, &opts::ActionWithServer::Ping).ok());\n    response.is_some()\n}\n"
  },
  {
    "path": "crates/eww/src/opts.rs",
    "content": "use anyhow::{Context, Result};\nuse clap::{Parser, Subcommand};\nuse eww_shared_util::VarName;\nuse serde::{Deserialize, Serialize};\nuse simplexpr::dynval::DynVal;\nuse yuck::{\n    config::{monitor::MonitorIdentifier, window_geometry::AnchorPoint},\n    value::Coords,\n};\n\nuse crate::{\n    app,\n    daemon_response::{self, DaemonResponse, DaemonResponseSender},\n};\n\n/// Struct that gets generated from `RawOpt`.\n#[derive(Debug, Serialize, Deserialize, PartialEq)]\npub struct Opt {\n    pub force_wayland: bool,\n    pub log_debug: bool,\n    pub show_logs: bool,\n    pub restart: bool,\n    pub config_path: Option<std::path::PathBuf>,\n    pub action: Action,\n    pub no_daemonize: bool,\n}\n\n#[derive(Parser, Debug, Serialize, Deserialize, PartialEq)]\n#[clap(author = \"ElKowar\")]\n#[clap(version, about)]\npub(super) struct RawOpt {\n    /// Write out debug logs. (To read the logs, run `eww logs`).\n    #[arg(long = \"debug\", global = true)]\n    log_debug: bool,\n\n    /// Force eww to use wayland. This is a no-op if eww was compiled without wayland support.\n    #[arg(long = \"force-wayland\", global = true)]\n    force_wayland: bool,\n\n    /// override path to configuration directory (directory that contains eww.yuck and eww.(s)css)\n    #[arg(short, long, global = true)]\n    config: Option<std::path::PathBuf>,\n\n    /// Watch the log output after executing the command\n    #[arg(long = \"logs\", global = true)]\n    show_logs: bool,\n\n    /// Avoid daemonizing eww.\n    #[arg(long = \"no-daemonize\", global = true)]\n    no_daemonize: bool,\n\n    /// Restart the daemon completely before running the command\n    #[arg(long = \"restart\", global = true)]\n    restart: bool,\n\n    #[command(subcommand)]\n    action: Action,\n}\n\n#[derive(Subcommand, Debug, Serialize, Deserialize, PartialEq)]\npub enum Action {\n    /// Generate a shell completion script\n    ShellCompletions {\n        #[arg(short, long)]\n        #[serde(with = \"serde_shell\")]\n        shell: clap_complete::shells::Shell,\n    },\n\n    /// Start the Eww daemon.\n    #[command(name = \"daemon\", alias = \"d\")]\n    Daemon,\n\n    #[command(flatten)]\n    ClientOnly(ActionClientOnly),\n\n    #[command(flatten)]\n    WithServer(ActionWithServer),\n}\n\n#[derive(Subcommand, Debug, Serialize, Deserialize, PartialEq, Eq)]\npub enum ActionClientOnly {\n    /// Print and watch the eww logs\n    #[command(name = \"logs\")]\n    Logs,\n}\n\n#[derive(Subcommand, Debug, Serialize, Deserialize, PartialEq)]\npub enum ActionWithServer {\n    /// Ping the eww server, checking if it is reachable.\n    #[clap(name = \"ping\")]\n    Ping,\n\n    /// Update the value of a variable, in a running eww instance\n    #[clap(name = \"update\", alias = \"u\")]\n    Update {\n        /// variable_name=\"new_value\"-pairs that will be updated\n        #[arg(value_parser = parse_var_update_arg)]\n        mappings: Vec<(VarName, DynVal)>,\n    },\n\n    /// Update a polling variable using its script.\n    ///\n    /// This will force the variable to be updated even if its\n    /// automatic polling is disabled.\n    #[command(name = \"poll\")]\n    Poll {\n        /// Variables to be polled\n        names: Vec<VarName>,\n    },\n\n    /// Open the GTK debugger\n    #[command(name = \"inspector\", alias = \"debugger\")]\n    OpenInspector,\n\n    /// Open a window\n    #[clap(name = \"open\", alias = \"o\")]\n    OpenWindow {\n        /// Name of the window you want to open.\n        window_name: String,\n\n        // The id of the window instance\n        #[arg(long)]\n        id: Option<String>,\n\n        /// The identifier of the monitor the window should open on\n        #[arg(long)]\n        screen: Option<MonitorIdentifier>,\n\n        /// The position of the window, where it should open. (i.e.: 200x100)\n        #[arg(short, long)]\n        pos: Option<Coords>,\n\n        /// The size of the window to open (i.e.: 200x100)\n        #[arg(short, long)]\n        size: Option<Coords>,\n\n        /// Sidepoint of the window, formatted like \"top right\"\n        #[arg(short, long)]\n        anchor: Option<AnchorPoint>,\n\n        /// If the window is already open, close it instead\n        #[arg(long = \"toggle\")]\n        should_toggle: bool,\n\n        /// Automatically close the window after a specified amount of time, i.e.: 1s\n        #[arg(long, value_parser=parse_duration)]\n        duration: Option<std::time::Duration>,\n\n        /// Define a variable for the window, i.e.: `--arg \"var_name=value\"`\n        #[arg(long = \"arg\", value_parser = parse_var_update_arg)]\n        args: Option<Vec<(VarName, DynVal)>>,\n    },\n\n    /// Open multiple windows at once.\n    /// NOTE: This will in the future be part of eww open, and will then be removed.\n    #[command(name = \"open-many\")]\n    OpenMany {\n        /// List the windows to open, optionally including their id, i.e.: `--window \"window_name:window_id\"`\n        #[arg(value_parser = parse_window_config_and_id)]\n        windows: Vec<(String, String)>,\n\n        /// Define a variable for the window, i.e.: `--arg \"window_id:var_name=value\"`\n        #[arg(long = \"arg\", value_parser = parse_window_id_args)]\n        args: Vec<(String, VarName, DynVal)>,\n\n        /// If a window is already open, close it instead\n        #[arg(long = \"toggle\")]\n        should_toggle: bool,\n    },\n\n    /// Close the given windows\n    #[command(name = \"close\", alias = \"c\")]\n    CloseWindows { windows: Vec<String> },\n\n    /// Reload the configuration\n    #[command(name = \"reload\", alias = \"r\")]\n    Reload,\n\n    /// Kill the eww daemon\n    #[command(name = \"kill\", alias = \"k\")]\n    KillServer,\n\n    /// Close all windows, without killing the daemon\n    #[command(name = \"close-all\", alias = \"ca\")]\n    CloseAll,\n\n    /// Prints the variables used in all currently open window\n    #[command(name = \"state\")]\n    ShowState {\n        /// Shows all variables, including not currently used ones\n        #[arg(short, long)]\n        all: bool,\n    },\n\n    /// Get the value of a variable if defined\n    #[command(name = \"get\")]\n    GetVar { name: String },\n\n    /// List the names of active windows\n    #[command(name = \"list-windows\")]\n    ListWindows,\n\n    /// Show active window IDs, formatted linewise `<window_id>: <window_name>`\n    #[command(name = \"active-windows\")]\n    ListActiveWindows,\n\n    /// Print out the widget structure as seen by eww.\n    ///\n    /// This may be useful if you are facing issues with how eww is interpreting your configuration,\n    /// and to provide additional context to the eww developers if you are filing a bug.\n    #[command(name = \"debug\")]\n    ShowDebug,\n\n    /// Print out the scope graph structure in graphviz dot format.\n    #[command(name = \"graph\")]\n    ShowGraph,\n}\n\nimpl Opt {\n    pub fn from_env() -> Self {\n        let raw: RawOpt = RawOpt::parse();\n        raw.into()\n    }\n}\n\nimpl From<RawOpt> for Opt {\n    fn from(other: RawOpt) -> Self {\n        let RawOpt { log_debug, force_wayland, config, show_logs, no_daemonize, restart, action } = other;\n        Opt { log_debug, force_wayland, show_logs, restart, config_path: config, action, no_daemonize }\n    }\n}\n\n/// Parse a window-name:window-id pair of the form `name:id` or `name` into a tuple of `(name, id)`.\nfn parse_window_config_and_id(s: &str) -> Result<(String, String)> {\n    let (name, id) = s.split_once(':').unwrap_or((s, s));\n\n    Ok((name.to_string(), id.to_string()))\n}\n\n/// Parse a window-id specific variable value declaration with the syntax `window-id:variable_name=\"new_value\"`\n/// into a tuple of `(id, variable_name, new_value)`.\nfn parse_window_id_args(s: &str) -> Result<(String, VarName, DynVal)> {\n    // Parse the = first so we know if an id has not been given\n    let (name, value) = parse_var_update_arg(s)?;\n\n    let (id, var_name) = name.0.split_once(':').unwrap_or((\"\", &name.0));\n\n    Ok((id.to_string(), var_name.into(), value))\n}\n\n/// Split the input string at `=`, parsing the value into a [`DynVal`].\nfn parse_var_update_arg(s: &str) -> Result<(VarName, DynVal)> {\n    let (name, value) = s\n        .split_once('=')\n        .with_context(|| format!(\"arguments must be in the shape `variable_name=\\\"new_value\\\"`, but got: {}\", s))?;\n    Ok((name.into(), DynVal::from_string(value.to_owned())))\n}\n\nimpl ActionWithServer {\n    pub fn can_start_daemon(&self) -> bool {\n        matches!(self, ActionWithServer::OpenWindow { .. } | ActionWithServer::OpenMany { .. })\n    }\n\n    pub fn into_daemon_command(self) -> (app::DaemonCommand, Option<daemon_response::DaemonResponseReceiver>) {\n        let command = match self {\n            ActionWithServer::Update { mappings } => app::DaemonCommand::UpdateVars(mappings),\n            ActionWithServer::Poll { names } => app::DaemonCommand::PollVars(names),\n            ActionWithServer::OpenInspector => app::DaemonCommand::OpenInspector,\n\n            ActionWithServer::KillServer => app::DaemonCommand::KillServer,\n            ActionWithServer::CloseAll => app::DaemonCommand::CloseAll,\n            ActionWithServer::Ping => {\n                let (send, recv) = tokio::sync::mpsc::unbounded_channel();\n                let _ = send.send(DaemonResponse::Success(\"pong\".to_owned()));\n                return (app::DaemonCommand::NoOp, Some(recv));\n            }\n            ActionWithServer::OpenMany { windows, args, should_toggle } => {\n                return with_response_channel(|sender| app::DaemonCommand::OpenMany { windows, args, should_toggle, sender });\n            }\n            ActionWithServer::OpenWindow { window_name, id, pos, size, screen, anchor, should_toggle, duration, args } => {\n                return with_response_channel(|sender| app::DaemonCommand::OpenWindow {\n                    window_name,\n                    instance_id: id,\n                    pos,\n                    size,\n                    anchor,\n                    screen,\n                    should_toggle,\n                    duration,\n                    sender,\n                    args,\n                })\n            }\n            ActionWithServer::CloseWindows { windows } => {\n                return with_response_channel(|sender| app::DaemonCommand::CloseWindows { windows, auto_reopen: false, sender });\n            }\n            ActionWithServer::Reload => return with_response_channel(app::DaemonCommand::ReloadConfigAndCss),\n            ActionWithServer::ListWindows => return with_response_channel(app::DaemonCommand::ListWindows),\n            ActionWithServer::ListActiveWindows => return with_response_channel(app::DaemonCommand::ListActiveWindows),\n            ActionWithServer::ShowState { all } => {\n                return with_response_channel(|sender| app::DaemonCommand::PrintState { all, sender })\n            }\n            ActionWithServer::GetVar { name } => {\n                return with_response_channel(|sender| app::DaemonCommand::GetVar { name, sender })\n            }\n            ActionWithServer::ShowDebug => return with_response_channel(app::DaemonCommand::PrintDebug),\n            ActionWithServer::ShowGraph => return with_response_channel(app::DaemonCommand::PrintGraph),\n        };\n        (command, None)\n    }\n}\n\nfn with_response_channel<O, F>(f: F) -> (O, Option<tokio::sync::mpsc::UnboundedReceiver<DaemonResponse>>)\nwhere\n    F: FnOnce(DaemonResponseSender) -> O,\n{\n    let (sender, recv) = daemon_response::create_pair();\n    (f(sender), Some(recv))\n}\n\nfn parse_duration(s: &str) -> Result<std::time::Duration, simplexpr::dynval::ConversionError> {\n    DynVal::from_string(s.to_owned()).as_duration()\n}\n\nmod serde_shell {\n    use std::str::FromStr as _;\n\n    use clap_complete::Shell;\n    use serde::{Deserialize as _, Deserializer, Serialize as _, Serializer};\n\n    pub fn serialize<S: Serializer>(shell: &Shell, serializer: S) -> Result<S::Ok, S::Error> {\n        shell.to_string().serialize(serializer)\n    }\n\n    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Shell, D::Error> {\n        let s = String::deserialize(deserializer)?;\n        Shell::from_str(&s).map_err(serde::de::Error::custom)\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/paths.rs",
    "content": "use std::{\n    collections::hash_map::DefaultHasher,\n    hash::{Hash, Hasher},\n    path::{Path, PathBuf},\n};\n\nuse anyhow::{bail, Result};\n\n/// Stores references to all the paths relevant to eww, and abstracts access to these files and directories\n#[derive(Debug, Clone)]\npub struct EwwPaths {\n    pub log_file: PathBuf,\n    pub log_dir: PathBuf,\n    pub ipc_socket_file: PathBuf,\n    pub config_dir: PathBuf,\n}\n\nimpl EwwPaths {\n    pub fn from_config_dir<P: AsRef<Path>>(config_dir: P) -> Result<Self> {\n        let config_dir = config_dir.as_ref();\n        if config_dir.is_file() {\n            bail!(\"Please provide the path to the config directory, not a file within it\")\n        }\n\n        if !config_dir.exists() {\n            bail!(\"Configuration directory {} does not exist\", config_dir.display());\n        }\n\n        let config_dir = config_dir.canonicalize()?;\n\n        let mut hasher = DefaultHasher::new();\n        format!(\"{}\", config_dir.display()).hash(&mut hasher);\n        // daemon_id is a hash of the config dir path to ensure that, given a normal XDG_RUNTIME_DIR,\n        // the absolute path to the socket stays under the 108 bytes limit. (see #387, man 7 unix)\n        let daemon_id = format!(\"{:x}\", hasher.finish());\n\n        let ipc_socket_file = std::env::var(\"XDG_RUNTIME_DIR\")\n            .map(std::path::PathBuf::from)\n            .unwrap_or_else(|_| std::path::PathBuf::from(\"/tmp\"))\n            .join(format!(\"eww-server_{}\", daemon_id));\n\n        // 100 as the limit isn't quite 108 everywhere (i.e 104 on BSD or mac)\n        if format!(\"{}\", ipc_socket_file.display()).len() > 100 {\n            log::warn!(\"The IPC socket file's absolute path exceeds 100 bytes, the socket may fail to create.\");\n        }\n\n        let log_dir = std::env::var(\"XDG_CACHE_HOME\")\n            .map(PathBuf::from)\n            .unwrap_or_else(|_| PathBuf::from(std::env::var(\"HOME\").unwrap()).join(\".cache\"))\n            .join(\"eww\");\n\n        if !log_dir.exists() {\n            log::info!(\"Creating log dir\");\n            std::fs::create_dir_all(&log_dir)?;\n        }\n\n        Ok(EwwPaths { config_dir, log_file: log_dir.join(format!(\"eww_{}.log\", daemon_id)), log_dir, ipc_socket_file })\n    }\n\n    pub fn default() -> Result<Self> {\n        let config_dir = std::env::var(\"XDG_CONFIG_HOME\")\n            .map(PathBuf::from)\n            .unwrap_or_else(|_| PathBuf::from(std::env::var(\"HOME\").unwrap()).join(\".config\"))\n            .join(\"eww\");\n\n        Self::from_config_dir(config_dir)\n    }\n\n    pub fn get_log_file(&self) -> &Path {\n        self.log_file.as_path()\n    }\n\n    pub fn get_log_dir(&self) -> &Path {\n        self.log_dir.as_path()\n    }\n\n    pub fn get_ipc_socket_file(&self) -> &Path {\n        self.ipc_socket_file.as_path()\n    }\n\n    pub fn get_config_dir(&self) -> &Path {\n        self.config_dir.as_path()\n    }\n\n    pub fn get_yuck_path(&self) -> PathBuf {\n        self.config_dir.join(\"eww.yuck\")\n    }\n}\n\nimpl std::fmt::Display for EwwPaths {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"config-dir: {}, ipc-socket: {}, log-file: {}\",\n            self.config_dir.display(),\n            self.ipc_socket_file.display(),\n            self.log_file.display()\n        )\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/script_var_handler.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::{\n    app,\n    config::{create_script_var_failed_warn, script_var},\n};\nuse anyhow::{anyhow, Result};\nuse app::DaemonCommand;\n\nuse eww_shared_util::VarName;\nuse nix::{\n    sys::signal,\n    unistd::{setpgid, Pid},\n};\nuse simplexpr::dynval::DynVal;\nuse tokio::{\n    io::{AsyncBufReadExt, BufReader},\n    sync::mpsc::UnboundedSender,\n};\nuse tokio_util::sync::CancellationToken;\nuse yuck::config::script_var_definition::{ListenScriptVar, PollScriptVar, ScriptVarDefinition, VarSource};\n\n/// Initialize the script var handler, and return a handle to that handler, which can be used to control\n/// the script var execution.\npub fn init(evt_send: UnboundedSender<DaemonCommand>) -> ScriptVarHandlerHandle {\n    let (msg_send, mut msg_recv) = tokio::sync::mpsc::unbounded_channel();\n    let thread_handle = std::thread::Builder::new()\n        .name(\"outer-script-var-handler\".to_string())\n        .spawn(move || {\n            let rt = tokio::runtime::Builder::new_multi_thread()\n                .enable_all()\n                .thread_name(\"script-var-handler\")\n                .build()\n                .expect(\"Failed to initialize tokio runtime for script var handlers\");\n            rt.block_on(async {\n                let _: Result<_> = async {\n                    let mut handler = ScriptVarHandler {\n                        listen_handler: ListenVarHandler::new(evt_send.clone())?,\n                        poll_handler: PollVarHandler::new(evt_send)?,\n                    };\n                    crate::loop_select_exiting! {\n                        Some(msg) = msg_recv.recv() => match msg {\n                            ScriptVarHandlerMsg::AddVar(var) => {\n                                handler.add(var).await;\n                            }\n                            ScriptVarHandlerMsg::Stop(name) => {\n                                handler.stop_for_variable(&name).await?;\n                            }\n                            ScriptVarHandlerMsg::StopAll => {\n                                handler.stop_all().await;\n                                break;\n                            }\n                        },\n                        else => break,\n                    };\n                    Ok(())\n                }\n                .await;\n            })\n        })\n        .expect(\"Failed to start script-var-handler thread\");\n    ScriptVarHandlerHandle { msg_send, thread_handle }\n}\n\n/// Handle to the script-var handling system.\npub struct ScriptVarHandlerHandle {\n    msg_send: UnboundedSender<ScriptVarHandlerMsg>,\n    thread_handle: std::thread::JoinHandle<()>,\n}\n\nimpl ScriptVarHandlerHandle {\n    /// Add a new script-var that should be executed.\n    /// This is idempodent, meaning that running a definition that already has a script_var attached which is running\n    /// won't do anything.\n    pub fn add(&self, script_var: ScriptVarDefinition) {\n        crate::print_result_err!(\n            \"while forwarding instruction to script-var handler\",\n            self.msg_send.send(ScriptVarHandlerMsg::AddVar(script_var))\n        );\n    }\n\n    /// Stop the execution of a specific script-var.\n    pub fn stop_for_variable(&self, name: VarName) {\n        crate::print_result_err!(\n            \"while forwarding instruction to script-var handler\",\n            self.msg_send.send(ScriptVarHandlerMsg::Stop(name)),\n        );\n    }\n\n    /// Stop the execution of all script-vars.\n    pub fn stop_all(&self) {\n        crate::print_result_err!(\n            \"while forwarding instruction to script-var handler\",\n            self.msg_send.send(ScriptVarHandlerMsg::StopAll)\n        );\n    }\n\n    pub fn join_thread(self) {\n        let _ = self.thread_handle.join();\n    }\n}\n\n/// Message enum used by the ScriptVarHandlerHandle to communicate to the ScriptVarHandler\n#[derive(Debug, Eq, PartialEq)]\n#[allow(clippy::large_enum_variant)]\nenum ScriptVarHandlerMsg {\n    AddVar(ScriptVarDefinition),\n    Stop(VarName),\n    StopAll,\n}\n\n/// Handler that manages running and updating [ScriptVarDefinition]s\nstruct ScriptVarHandler {\n    listen_handler: ListenVarHandler,\n    poll_handler: PollVarHandler,\n}\n\nimpl ScriptVarHandler {\n    async fn add(&mut self, script_var: ScriptVarDefinition) {\n        match script_var {\n            ScriptVarDefinition::Poll(var) => self.poll_handler.start(var).await,\n            ScriptVarDefinition::Listen(var) => self.listen_handler.start(var).await,\n        };\n    }\n\n    /// Stop the handler that is responsible for a given variable.\n    async fn stop_for_variable(&mut self, name: &VarName) -> Result<()> {\n        log::debug!(\"Stopping script var process for variable {}\", name);\n        self.listen_handler.stop_for_variable(name).await;\n        self.poll_handler.stop_for_variable(name);\n        Ok(())\n    }\n\n    /// stop all running scripts and schedules\n    async fn stop_all(&mut self) {\n        log::debug!(\"Stopping script-var-handlers\");\n        self.listen_handler.stop_all().await;\n        self.poll_handler.stop_all();\n    }\n}\n\nstruct PollVarHandler {\n    evt_send: UnboundedSender<DaemonCommand>,\n    poll_handles: HashMap<VarName, CancellationToken>,\n}\n\nimpl PollVarHandler {\n    fn new(evt_send: UnboundedSender<DaemonCommand>) -> Result<Self> {\n        let handler = PollVarHandler { evt_send, poll_handles: HashMap::new() };\n        Ok(handler)\n    }\n\n    async fn start(&mut self, var: PollScriptVar) {\n        if self.poll_handles.contains_key(&var.name) {\n            return;\n        }\n\n        log::debug!(\"starting poll var {}\", &var.name);\n        let cancellation_token = CancellationToken::new();\n        self.poll_handles.insert(var.name.clone(), cancellation_token.clone());\n        let evt_send = self.evt_send.clone();\n        tokio::spawn(async move {\n            let result: Result<_> = (|| {\n                evt_send.send(app::DaemonCommand::UpdateVars(vec![(var.name.clone(), run_poll_once(&var)?)]))?;\n                Ok(())\n            })();\n            if let Err(err) = result {\n                crate::error_handling_ctx::print_error(err);\n            }\n\n            crate::loop_select_exiting! {\n                _ = cancellation_token.cancelled() => break,\n                _ = tokio::time::sleep(var.interval) => {\n                    let result: Result<_> = (|| {\n                        evt_send.send(app::DaemonCommand::UpdateVars(vec![(var.name.clone(), run_poll_once(&var)?)]))?;\n                        Ok(())\n                    })();\n\n                    if let Err(err) = result {\n                        crate::error_handling_ctx::print_error(err);\n                    }\n                }\n            }\n        });\n    }\n\n    fn stop_for_variable(&mut self, name: &VarName) {\n        if let Some(token) = self.poll_handles.remove(name) {\n            log::debug!(\"stopped poll var {}\", name);\n            token.cancel()\n        }\n    }\n\n    fn stop_all(&mut self) {\n        self.poll_handles.drain().for_each(|(_, token)| token.cancel());\n    }\n}\n\npub fn run_poll_once(var: &PollScriptVar) -> Result<DynVal> {\n    match &var.command {\n        VarSource::Shell(span, command) => {\n            script_var::run_command(command).map_err(|e| anyhow!(create_script_var_failed_warn(*span, &var.name, &e.to_string())))\n        }\n        VarSource::Function(x) => x().map_err(|e| anyhow!(e)),\n    }\n}\n\nimpl Drop for PollVarHandler {\n    fn drop(&mut self) {\n        self.stop_all();\n    }\n}\n\nstruct ListenVarHandler {\n    evt_send: UnboundedSender<DaemonCommand>,\n    listen_process_handles: HashMap<VarName, cancellation::AwaitableCancelationSender>,\n}\n\nimpl ListenVarHandler {\n    fn new(evt_send: UnboundedSender<DaemonCommand>) -> Result<Self> {\n        let handler = ListenVarHandler { evt_send, listen_process_handles: HashMap::new() };\n        Ok(handler)\n    }\n\n    /// Start a listen-var. Starting a variable that is already running will not do anything.\n    async fn start(&mut self, var: ListenScriptVar) {\n        log::debug!(\"starting listen-var {}\", &var.name);\n\n        // Make sure the same listenvar is never started twice,\n        // as that would cause eww to not clean up the older listenvar on window close.\n        if self.listen_process_handles.contains_key(&var.name) {\n            return;\n        }\n\n        let (cancel_send, mut cancel_recv) = cancellation::create();\n        self.listen_process_handles.insert(var.name.clone(), cancel_send);\n\n        let evt_send = self.evt_send.clone();\n        tokio::spawn(async move {\n            let result: Result<_> = async {\n                let mut handle = unsafe {\n                    tokio::process::Command::new(\"sh\")\n                        .args([\"-c\", &var.command])\n                        .stdout(std::process::Stdio::piped())\n                        .stderr(std::process::Stdio::piped())\n                        .stdin(std::process::Stdio::null())\n                        .pre_exec(|| {\n                            let _ = setpgid(Pid::from_raw(0), Pid::from_raw(0));\n                            Ok(())\n                        })\n                        .spawn()?\n                };\n                let mut stdout_lines = BufReader::new(handle.stdout.take().unwrap()).lines();\n                let mut stderr_lines = BufReader::new(handle.stderr.take().unwrap()).lines();\n                let mut completion_notify = None;\n                crate::loop_select_exiting! {\n                    _ = handle.wait() => break,\n                    notify = cancel_recv.wait_for_cancel() => {\n                        completion_notify = notify;\n                        break;\n                    }\n                    Ok(Some(line)) = stdout_lines.next_line() => {\n                        let new_value = DynVal::from_string(line.to_owned());\n                        evt_send.send(DaemonCommand::UpdateVars(vec![(var.name.to_owned(), new_value)]))?;\n                    }\n                    Ok(Some(line)) = stderr_lines.next_line() => {\n                        log::warn!(\"stderr of `{}`: {}\", var.name, line);\n                    }\n                    else => break,\n                };\n                terminate_handle(handle).await;\n\n                if let Some(completion_notify) = completion_notify {\n                    completion_notify.completed().await;\n                }\n                Ok(())\n            }\n            .await;\n\n            if let Err(err) = result {\n                log::error!(\n                    \"[{}:{}] Error while executing listen-var command {}: {:?}\",\n                    ::std::file!(),\n                    ::std::line!(),\n                    &var.command,\n                    err\n                );\n            }\n        });\n    }\n\n    async fn stop_for_variable(&mut self, name: &VarName) {\n        if let Some(token) = self.listen_process_handles.remove(name) {\n            log::debug!(\"stopped listen-var {}\", name);\n            token.cancel().await;\n        }\n    }\n\n    async fn stop_all(&mut self) {\n        for (_, token) in self.listen_process_handles.drain() {\n            token.cancel().await;\n        }\n    }\n}\n\nimpl Drop for ListenVarHandler {\n    fn drop(&mut self) {\n        if !self.listen_process_handles.is_empty() {\n            std::thread::scope(|s| {\n                s.spawn(|| {\n                    let rt = tokio::runtime::Builder::new_current_thread()\n                        .thread_name(\"listen-var-drop-stop-all\")\n                        .build()\n                        .expect(\"Failed to initialize tokio runtime for script var handlers\");\n                    rt.block_on(async {\n                        self.stop_all().await;\n                    });\n                });\n            })\n        }\n    }\n}\n\nasync fn terminate_handle(mut child: tokio::process::Child) {\n    if let Some(id) = child.id() {\n        log::debug!(\"Killing process with id {}\", id);\n        let _ = signal::killpg(Pid::from_raw(id as i32), signal::SIGTERM);\n        tokio::select! {\n            _ = child.wait() => { },\n            _ = tokio::time::sleep(std::time::Duration::from_secs(10)) => {\n                let _ = child.kill().await;\n            }\n        };\n    } else {\n        let _ = child.kill().await;\n    }\n}\n\n// Especially for listenvars, we want to make sure that the scripts are actually\n// cancelled before we kill the tokio task that they run in.\n// for that, we need to wait for the completion of the cancel itself\n/// Provides a CancellationToken-like object that allows to wait for completion of the cancellation.\nmod cancellation {\n    pub(super) struct CancelCompletionNotifier(tokio::sync::mpsc::Sender<()>);\n    impl CancelCompletionNotifier {\n        pub async fn completed(self) {\n            crate::print_result_err!(\"Sending cancellation completion\", self.0.send(()).await);\n        }\n    }\n\n    pub(super) struct AwaitableCancelationReceiver(tokio::sync::mpsc::Receiver<CancelCompletionNotifier>);\n\n    impl AwaitableCancelationReceiver {\n        pub(super) async fn wait_for_cancel(&mut self) -> Option<CancelCompletionNotifier> {\n            self.0.recv().await\n        }\n    }\n\n    #[derive(Clone)]\n    pub(super) struct AwaitableCancelationSender(tokio::sync::mpsc::Sender<CancelCompletionNotifier>);\n    impl AwaitableCancelationSender {\n        pub(super) async fn cancel(&self) {\n            let (send, mut recv) = tokio::sync::mpsc::channel(1);\n            if self.0.send(CancelCompletionNotifier(send)).await.is_ok() {\n                let _ = recv.recv().await;\n            }\n        }\n    }\n\n    pub(super) fn create() -> (AwaitableCancelationSender, AwaitableCancelationReceiver) {\n        let (send, recv) = tokio::sync::mpsc::channel(1);\n        (AwaitableCancelationSender(send), AwaitableCancelationReceiver(recv))\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/server.rs",
    "content": "use crate::{\n    app::{self, App, DaemonCommand},\n    config, daemon_response,\n    display_backend::DisplayBackend,\n    error_handling_ctx, ipc_server, script_var_handler,\n    state::scope_graph::ScopeGraph,\n    EwwPaths,\n};\nuse anyhow::{Context, Result};\n\nuse std::{\n    cell::RefCell,\n    collections::{HashMap, HashSet},\n    io::Write,\n    marker::PhantomData,\n    os::unix::io::AsRawFd,\n    path::Path,\n    rc::Rc,\n    sync::{atomic::Ordering, Arc},\n};\nuse tokio::sync::mpsc::*;\n\npub fn initialize_server<B: DisplayBackend>(\n    paths: EwwPaths,\n    action: Option<DaemonCommand>,\n    should_daemonize: bool,\n) -> Result<ForkResult> {\n    let (ui_send, mut ui_recv) = tokio::sync::mpsc::unbounded_channel();\n\n    std::env::set_current_dir(paths.get_config_dir())\n        .with_context(|| format!(\"Failed to change working directory to {}\", paths.get_config_dir().display()))?;\n\n    log::info!(\"Loading paths: {}\", &paths);\n\n    let read_config = config::read_from_eww_paths(&paths);\n\n    let eww_config = match read_config {\n        Ok(config) => config,\n        Err(err) => {\n            error_handling_ctx::print_error(err);\n            config::EwwConfig::default()\n        }\n    };\n\n    cleanup_log_dir(paths.get_log_dir())?;\n\n    if should_daemonize {\n        let fork_result = do_detach(paths.get_log_file())?;\n\n        if fork_result == ForkResult::Parent {\n            return Ok(ForkResult::Parent);\n        }\n    }\n\n    println!(\n        r#\"\n┏━━━━━━━━━━━━━━━━━━━━━━━┓\n┃Initializing eww daemon┃\n┗━━━━━━━━━━━━━━━━━━━━━━━┛\n\"#\n    );\n\n    simple_signal::set_handler(&[simple_signal::Signal::Int, simple_signal::Signal::Term], move |_| {\n        log::info!(\"Shutting down eww daemon...\");\n        if let Err(e) = crate::application_lifecycle::send_exit() {\n            log::error!(\"Failed to send application shutdown event to workers: {:?}\", e);\n            std::process::exit(1);\n        }\n    });\n\n    if B::IS_WAYLAND {\n        std::env::set_var(\"GDK_BACKEND\", \"wayland\")\n    }\n    gtk::init()?;\n\n    log::debug!(\"Initializing script var handler\");\n    let script_var_handler = script_var_handler::init(ui_send.clone());\n\n    let (scope_graph_evt_send, mut scope_graph_evt_recv) = tokio::sync::mpsc::unbounded_channel();\n\n    let mut app: App<B> = app::App {\n        scope_graph: Rc::new(RefCell::new(ScopeGraph::from_global_vars(\n            eww_config.generate_initial_state()?,\n            scope_graph_evt_send,\n        ))),\n        eww_config,\n        open_windows: HashMap::new(),\n        failed_windows: HashSet::new(),\n        instance_id_to_args: HashMap::new(),\n        css_provider: gtk::CssProvider::new(),\n        script_var_handler,\n        app_evt_send: ui_send.clone(),\n        window_close_timer_abort_senders: HashMap::new(),\n        paths,\n        phantom: PhantomData,\n    };\n\n    if let Some(screen) = gtk::gdk::Screen::default() {\n        gtk::StyleContext::add_provider_for_screen(&screen, &app.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);\n    }\n\n    if let Ok((file_id, css)) = config::scss::parse_scss_from_config(app.paths.get_config_dir()) {\n        if let Err(e) = app.load_css(file_id, &css) {\n            error_handling_ctx::print_error(e);\n        }\n    }\n\n    connect_monitor_added(ui_send.clone());\n\n    // initialize all the handlers and tasks running asyncronously\n    let tokio_handle = init_async_part(app.paths.clone(), ui_send);\n\n    gtk::glib::MainContext::default().spawn_local(async move {\n        // if an action was given to the daemon initially, execute it first.\n        if let Some(action) = action {\n            app.handle_command(action).await;\n        }\n\n        loop {\n            tokio::select! {\n                Some(scope_graph_evt) = scope_graph_evt_recv.recv() => {\n                    app.scope_graph.borrow_mut().handle_scope_graph_event(scope_graph_evt);\n                },\n                Some(ui_event) = ui_recv.recv() => {\n                    app.handle_command(ui_event).await;\n                }\n                else => break,\n            }\n        }\n    });\n\n    // allow the GTK main thread to do tokio things\n    let _g = tokio_handle.enter();\n\n    gtk::main();\n    log::info!(\"main application thread finished\");\n\n    Ok(ForkResult::Child)\n}\n\nfn connect_monitor_added(ui_send: UnboundedSender<DaemonCommand>) {\n    let display = gtk::gdk::Display::default().expect(\"could not get default display\");\n    display.connect_monitor_added({\n        move |_display: &gtk::gdk::Display, _monitor: &gtk::gdk::Monitor| {\n            log::info!(\"New monitor connected, reloading configuration\");\n            let _ = reload_config_and_css(&ui_send);\n        }\n    });\n}\n\nfn reload_config_and_css(ui_send: &UnboundedSender<DaemonCommand>) -> Result<()> {\n    let (daemon_resp_sender, mut daemon_resp_response) = daemon_response::create_pair();\n    ui_send.send(DaemonCommand::ReloadConfigAndCss(daemon_resp_sender))?;\n    tokio::spawn(async move {\n        match daemon_resp_response.recv().await {\n            Some(daemon_response::DaemonResponse::Success(_)) => log::info!(\"Reloaded config successfully\"),\n            Some(daemon_response::DaemonResponse::Failure(e)) => eprintln!(\"{}\", e),\n            None => log::error!(\"No response to reload configuration-reload request\"),\n        }\n    });\n    Ok(())\n}\n\nfn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>) -> tokio::runtime::Handle {\n    let rt = tokio::runtime::Builder::new_multi_thread()\n        .thread_name(\"main-async-runtime\")\n        .enable_all()\n        .build()\n        .expect(\"Failed to initialize tokio runtime\");\n    let handle = rt.handle().clone();\n\n    std::thread::Builder::new()\n        .name(\"outer-main-async-runtime\".to_string())\n        .spawn(move || {\n            rt.block_on(async {\n                let filewatch_join_handle = {\n                    let ui_send = ui_send.clone();\n                    let paths = paths.clone();\n                    tokio::spawn(async move { run_filewatch(paths.config_dir, ui_send).await })\n                };\n\n                let ipc_server_join_handle = {\n                    let ui_send = ui_send.clone();\n                    tokio::spawn(async move { ipc_server::run_server(ui_send, paths.get_ipc_socket_file()).await })\n                };\n\n                let forward_exit_to_app_handle = {\n                    let ui_send = ui_send.clone();\n                    tokio::spawn(async move {\n                        // Wait for application exit event\n                        let _ = crate::application_lifecycle::recv_exit().await;\n                        log::debug!(\"Forward task received exit event\");\n                        // Then forward that to the application\n                        let _ = ui_send.send(app::DaemonCommand::KillServer);\n                    })\n                };\n\n                let result = tokio::try_join!(filewatch_join_handle, ipc_server_join_handle, forward_exit_to_app_handle);\n\n                if let Err(e) = result {\n                    log::error!(\"Eww exiting with error: {:?}\", e);\n                }\n            })\n        })\n        .expect(\"Failed to start outer-main-async-runtime thread\");\n\n    handle\n}\n\n/// Watch configuration files for changes, sending reload events to the eww app when the files change.\nasync fn run_filewatch<P: AsRef<Path>>(config_dir: P, evt_send: UnboundedSender<app::DaemonCommand>) -> Result<()> {\n    use notify::{RecommendedWatcher, RecursiveMode, Watcher};\n\n    let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();\n    let mut watcher: RecommendedWatcher = notify::recommended_watcher(move |res: notify::Result<notify::Event>| match res {\n        Ok(notify::Event { kind: notify::EventKind::Modify(_), paths, .. }) => {\n            let relevant_files_changed = paths.iter().any(|path| {\n                let ext = path.extension().unwrap_or_default();\n                ext == \"yuck\" || ext == \"scss\" || ext == \"css\"\n            });\n            if relevant_files_changed {\n                if let Err(err) = tx.send(()) {\n                    log::warn!(\"Error forwarding file update event: {:?}\", err);\n                }\n            }\n        }\n        Ok(_) => {}\n        Err(e) => log::error!(\"Encountered Error While Watching Files: {}\", e),\n    })?;\n    watcher.watch(config_dir.as_ref(), RecursiveMode::Recursive)?;\n\n    // make sure to not trigger reloads too much by only accepting one reload every 500ms.\n    let debounce_done = Arc::new(std::sync::atomic::AtomicBool::new(true));\n\n    crate::loop_select_exiting! {\n        Some(()) = rx.recv() => {\n            let debounce_done = debounce_done.clone();\n            if debounce_done.swap(false, Ordering::SeqCst) {\n                tokio::spawn(async move {\n                    tokio::time::sleep(std::time::Duration::from_millis(500)).await;\n                    debounce_done.store(true, Ordering::SeqCst);\n                });\n\n                // without this sleep, reading the config file sometimes gives an empty file.\n                // This is probably a result of editors not locking the file correctly,\n                // and eww being too fast, thus reading the file while it's empty.\n                // There should be some cleaner solution for this, but this will do for now.\n                tokio::time::sleep(std::time::Duration::from_millis(50)).await;\n                reload_config_and_css(&evt_send)?;\n            }\n        },\n        else => break\n    };\n    Ok(())\n}\n\n#[derive(Clone, Debug, Eq, PartialEq)]\npub enum ForkResult {\n    Parent,\n    Child,\n}\n\n/// detach the process from the terminal, also redirecting stdout and stderr to LOG_FILE\nfn do_detach(log_file_path: impl AsRef<Path>) -> Result<ForkResult> {\n    // detach from terminal\n    match unsafe { nix::unistd::fork()? } {\n        nix::unistd::ForkResult::Child => {\n            nix::unistd::setsid()?;\n            match unsafe { nix::unistd::fork()? } {\n                nix::unistd::ForkResult::Parent { .. } => std::process::exit(0),\n                nix::unistd::ForkResult::Child => {}\n            }\n        }\n        nix::unistd::ForkResult::Parent { .. } => {\n            return Ok(ForkResult::Parent);\n        }\n    }\n\n    let file = std::fs::OpenOptions::new()\n        .create(true)\n        .append(true)\n        .open(&log_file_path)\n        .unwrap_or_else(|_| panic!(\"Error opening log file ({}), for writing\", log_file_path.as_ref().to_string_lossy()));\n    let fd = file.as_raw_fd();\n\n    if nix::unistd::isatty(1)? {\n        nix::unistd::dup2(fd, std::io::stdout().as_raw_fd())?;\n    }\n    if nix::unistd::isatty(2)? {\n        nix::unistd::dup2(fd, std::io::stderr().as_raw_fd())?;\n    }\n\n    Ok(ForkResult::Child)\n}\n\n/// Ensure the log directory never grows larger than 100MB by deleting files older than 7 days,\n/// and truncating all other logfiles to 100MB.\nfn cleanup_log_dir(log_dir: impl AsRef<Path>) -> Result<()> {\n    // Find all files named \"eww_*.log\" in the log directory\n    let log_files = std::fs::read_dir(&log_dir)?\n        .filter_map(|entry| {\n            let entry = entry.ok()?;\n            let path = entry.path();\n            if let Some(file_name) = path.file_name() {\n                if file_name.to_string_lossy().starts_with(\"eww_\") && file_name.to_string_lossy().ends_with(\".log\") {\n                    Some(path)\n                } else {\n                    None\n                }\n            } else {\n                None\n            }\n        })\n        .collect::<Vec<_>>();\n\n    for log_file in log_files {\n        // if the file is older than a week, delete it\n        if let Ok(metadata) = log_file.metadata() {\n            if metadata.modified()?.elapsed()?.as_secs() > 60 * 60 * 24 * 7 {\n                log::info!(\"Deleting old log file: {}\", log_file.display());\n                std::fs::remove_file(&log_file)?;\n            } else {\n                // If the file is larger than 200MB, delete the start of it until it's 100MB or less.\n                let mut file = std::fs::OpenOptions::new().append(true).open(&log_file)?;\n                let file_size = file.metadata()?.len();\n                if file_size > 200_000_000 {\n                    let mut file_content = std::fs::read(&log_file)?;\n                    let bytes_to_remove = file_content.len().saturating_sub(100_000_000);\n                    file_content.drain(0..bytes_to_remove);\n                    file.set_len(0)?;\n                    file.write_all(&file_content)?;\n                }\n            }\n        }\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "crates/eww/src/state/mod.rs",
    "content": "mod one_to_n_elements_map;\npub mod scope;\npub mod scope_graph;\n\n#[cfg(test)]\nmod test;\n"
  },
  {
    "path": "crates/eww/src/state/one_to_n_elements_map.rs",
    "content": "use anyhow::{bail, Result};\nuse std::collections::{HashMap, HashSet};\n\n/// A map that represents a structure of a 1-n relationship with edges that contain data.\n#[derive(Debug)]\npub struct OneToNElementsMap<I, T> {\n    pub(super) child_to_parent: HashMap<I, (I, T)>,\n    pub(super) parent_to_children: HashMap<I, HashSet<I>>,\n}\n\nimpl<I: Copy + std::hash::Hash + std::cmp::Eq + std::fmt::Debug, T> OneToNElementsMap<I, T> {\n    pub fn new() -> Self {\n        OneToNElementsMap { child_to_parent: HashMap::new(), parent_to_children: HashMap::new() }\n    }\n\n    pub fn clear(&mut self) {\n        self.child_to_parent.clear();\n        self.parent_to_children.clear()\n    }\n\n    pub fn insert(&mut self, child: I, parent: I, edge: T) -> Result<()> {\n        if self.child_to_parent.contains_key(&child) {\n            bail!(\"this child already has a parent\");\n        }\n        self.child_to_parent.insert(child, (parent, edge));\n        self.parent_to_children.entry(parent).or_default().insert(child);\n        Ok(())\n    }\n\n    pub fn remove(&mut self, scope: I) {\n        if let Some(children) = self.parent_to_children.remove(&scope) {\n            for child in &children {\n                self.child_to_parent.remove(child);\n            }\n        }\n        if let Some((parent, _)) = self.child_to_parent.remove(&scope) {\n            if let Some(children_of_parent) = self.parent_to_children.get_mut(&parent) {\n                children_of_parent.remove(&scope);\n            }\n        }\n    }\n\n    pub fn get_parent_of(&self, index: I) -> Option<I> {\n        self.child_to_parent.get(&index).map(|(parent, _)| *parent)\n    }\n\n    pub fn get_parent_edge_of(&self, index: I) -> Option<&(I, T)> {\n        self.child_to_parent.get(&index)\n    }\n\n    pub fn get_parent_edge_mut(&mut self, index: I) -> Option<&mut (I, T)> {\n        self.child_to_parent.get_mut(&index)\n    }\n\n    #[allow(unused)]\n    pub fn get_children_of(&self, index: I) -> HashSet<I> {\n        self.parent_to_children.get(&index).cloned().unwrap_or_default()\n    }\n\n    /// Return the children and edges to those children of a given scope\n    pub fn get_children_edges_of(&self, index: I) -> Vec<(I, &T)> {\n        let mut result = Vec::new();\n        if let Some(children) = self.parent_to_children.get(&index) {\n            for child_scope in children {\n                let (_, edge) = self.child_to_parent.get(child_scope).expect(\"OneToNElementsMap got into inconsistent state\");\n                result.push((*child_scope, edge));\n            }\n        }\n        result\n    }\n\n    #[cfg_attr(not(debug_assertions), allow(dead_code))]\n    pub fn validate(&self) -> Result<()> {\n        for (parent, children) in &self.parent_to_children {\n            for child in children {\n                if let Some((parent_2, _)) = self.child_to_parent.get(child) {\n                    if parent_2 != parent {\n                        bail!(\n                            \"parent_to_child stored mapping from {:?} to {:?}, but child_to_parent contained mapping to {:?} \\\n                             instead\",\n                            parent,\n                            child,\n                            parent_2\n                        );\n                    }\n                } else {\n                    bail!(\n                        \"parent_to_child stored mapping from {:?} to {:?}, which was not found in child_to_parent\",\n                        parent,\n                        child\n                    );\n                }\n            }\n        }\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    pub fn test_add_scope() {\n        let mut map = OneToNElementsMap::new();\n        map.insert(1, 2, \"a\".to_string()).unwrap();\n        map.insert(2, 3, \"b\".to_string()).unwrap();\n        map.insert(3, 4, \"c\".to_string()).unwrap();\n        map.insert(5, 4, \"d\".to_string()).unwrap();\n\n        assert_eq!(map.get_parent_of(1), Some(2));\n        assert_eq!(map.get_parent_of(2), Some(3));\n        assert_eq!(map.get_parent_of(3), Some(4));\n        assert_eq!(map.get_parent_of(4), None);\n        assert_eq!(map.get_parent_of(5), Some(4));\n\n        assert_eq!(map.get_children_of(4), HashSet::from_iter(vec![3, 5]));\n        assert_eq!(map.get_children_of(3), HashSet::from_iter(vec![2]));\n        assert_eq!(map.get_children_of(2), HashSet::from_iter(vec![1]));\n        assert_eq!(map.get_children_of(1), HashSet::new());\n    }\n\n    #[test]\n    pub fn test_remove_scope() {\n        let mut map = OneToNElementsMap::new();\n        map.insert(1, 2, \"a\".to_string()).unwrap();\n        map.insert(2, 3, \"b\".to_string()).unwrap();\n        map.insert(3, 4, \"c\".to_string()).unwrap();\n        map.insert(5, 4, \"d\".to_string()).unwrap();\n\n        map.remove(4);\n\n        assert_eq!(map.get_parent_of(1), Some(2));\n        assert_eq!(map.get_parent_of(2), Some(3));\n        assert_eq!(map.get_parent_of(3), None);\n        assert_eq!(map.get_parent_of(4), None);\n        assert_eq!(map.get_parent_of(5), None);\n\n        assert_eq!(map.get_children_of(3), HashSet::from_iter(vec![2]));\n        assert_eq!(map.get_children_of(2), HashSet::from_iter(vec![1]));\n        assert_eq!(map.get_children_of(1), HashSet::new());\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/state/scope.rs",
    "content": "use anyhow::Result;\nuse std::{collections::HashMap, rc::Rc};\n\nuse eww_shared_util::VarName;\nuse simplexpr::dynval::DynVal;\n\nuse super::scope_graph::{ScopeGraph, ScopeIndex};\n\n#[derive(Debug)]\npub struct Scope {\n    pub name: String,\n    pub ancestor: Option<ScopeIndex>,\n    pub data: HashMap<VarName, DynVal>,\n    /// The listeners that react to value changes in this scope.\n    /// **Note** that there might be VarNames referenced here that are not defined in this scope.\n    /// In those cases it is necessary to look into the scopes this scope is inheriting from.\n    pub listeners: HashMap<VarName, Vec<Rc<Listener>>>,\n    pub node_index: ScopeIndex,\n}\n\nimpl Scope {\n    /// Initializes a scope **incompletely**. The [`Self::node_index`] is not set correctly, and needs to be\n    /// set to the index of the node in the scope graph that connects to this scope.\n    pub(super) fn new(name: String, created_by: Option<ScopeIndex>, data: HashMap<VarName, DynVal>) -> Self {\n        Self { name, ancestor: created_by, data, listeners: HashMap::new(), node_index: ScopeIndex(0) }\n    }\n}\n\npub type ListenerFn = Box<dyn Fn(&mut ScopeGraph, HashMap<VarName, DynVal>) -> Result<()>>;\n\npub struct Listener {\n    pub needed_variables: Vec<VarName>,\n    pub f: ListenerFn,\n}\nimpl std::fmt::Debug for Listener {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Listener\").field(\"needed_variables\", &self.needed_variables).field(\"f\", &\"function\").finish()\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/state/scope_graph.rs",
    "content": "use std::{\n    collections::{HashMap, HashSet},\n    rc::Rc,\n};\n\nuse anyhow::{anyhow, bail, Context, Result};\nuse eww_shared_util::{AttrName, VarName};\nuse simplexpr::{dynval::DynVal, SimplExpr};\nuse tokio::sync::mpsc::UnboundedSender;\n\nuse crate::error_handling_ctx;\n\nuse super::scope::{Listener, Scope};\n\n#[derive(Hash, Eq, PartialEq, Copy, Clone)]\npub struct ScopeIndex(pub usize);\n\nimpl std::fmt::Debug for ScopeIndex {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"ScopeIndex({})\", self.0)\n    }\n}\nimpl ScopeIndex {\n    fn advance(&mut self) {\n        self.0 += 1;\n    }\n}\n\npub enum ScopeGraphEvent {\n    RemoveScope(ScopeIndex),\n}\n\n/// A graph structure of scopes where each scope may inherit from another scope,\n/// and can provide attributes to arbitrarily many descendant scopes.\n///\n/// ## Some terminology\n/// **Subscope / Superscope**: Subscopes are scopes that _inherit_ from their superscope.\n/// This means that they have access to all the variables defined in that scope as well.\n/// The variables a subscope references from it's superscope are listed in the [`internal::Inherits`].\n/// In most cases, scopes inherit from the global scope.\n///\n/// **Descendant / Ancestor**: Descendants of a scope are the scopes that are used\n/// _within_ that ancestor scope. This means that a descendant scope's widgets will aways be\n/// used as children of the ancestors widgets.\n/// Any scope can have 0 or 1 ancestor, and any arbitrary amount of descendants.\n/// An ancestor scope can provide attributes to it's descendants, which will be\n/// listed in the respective [`internal::ProvidedAttr`]s.\n///\n/// Invariants:\n/// - every scope inherits from exactly 0 or 1 scopes.\n/// - any scope may provide 0-n attributes to 0-n descendants.\n/// - There must not be inheritance loops\n/// - Inheritance is transitive - if a is subscope of b, and b is subscope of c, a has access to variables from c.\n/// - In case of transitive inheritance, all steps need to explicitly store the referenced variables. This means that\n///   if A is subscope of B, and B is subscope of C, and A references a variable \"foo\" from C, then this reference\n///   needs to be stored in both the inheritance connection A -> B and B -> C\n#[derive(Debug)]\npub struct ScopeGraph {\n    pub(self) graph: internal::ScopeGraphInternal,\n    pub root_index: ScopeIndex,\n    // TODO this should be factored out, it doesn't really belong into this module / struct.\n    pub event_sender: UnboundedSender<ScopeGraphEvent>,\n}\n\nimpl ScopeGraph {\n    pub fn from_global_vars(vars: HashMap<VarName, DynVal>, event_sender: UnboundedSender<ScopeGraphEvent>) -> Self {\n        let mut graph = internal::ScopeGraphInternal::new();\n        let root_index = graph.add_scope(Scope {\n            name: \"global\".to_string(),\n            ancestor: None,\n            data: vars,\n            listeners: HashMap::new(),\n            node_index: ScopeIndex(0),\n        });\n        if let Some(scope) = graph.scope_at_mut(root_index) {\n            scope.node_index = root_index;\n        }\n        Self { graph, root_index, event_sender }\n    }\n\n    pub fn update_global_value(&mut self, var_name: &VarName, value: DynVal) -> Result<()> {\n        self.update_value(self.root_index, var_name, value)\n    }\n\n    pub fn handle_scope_graph_event(&mut self, evt: ScopeGraphEvent) {\n        match evt {\n            ScopeGraphEvent::RemoveScope(scope_index) => {\n                self.remove_scope(scope_index);\n            }\n        }\n    }\n\n    /// Fully reinitialize the scope graph. Completely removes all state, and resets the ScopeIndex uniqueness.\n    pub fn clear(&mut self, vars: HashMap<VarName, DynVal>) {\n        self.graph.clear();\n        let root_index = self.graph.add_scope(Scope {\n            name: \"global\".to_string(),\n            ancestor: None,\n            data: vars,\n            listeners: HashMap::new(),\n            node_index: ScopeIndex(0),\n        });\n        if let Some(scope) = self.graph.scope_at_mut(root_index) {\n            scope.node_index = root_index;\n        }\n        self.root_index = root_index;\n    }\n\n    pub fn remove_scope(&mut self, scope_index: ScopeIndex) {\n        self.graph.remove_scope(scope_index);\n    }\n\n    #[cfg_attr(not(debug_assertions), allow(dead_code))]\n    pub fn validate(&self) -> Result<()> {\n        self.graph.validate()\n    }\n\n    pub fn visualize(&self) -> String {\n        self.graph.visualize()\n    }\n\n    pub fn currently_used_globals(&self) -> HashSet<VarName> {\n        self.variables_used_in_self_or_subscopes_of(self.root_index)\n    }\n\n    pub fn currently_unused_globals(&self) -> HashSet<VarName> {\n        let used_variables = self.currently_used_globals();\n        self.global_scope().data.keys().cloned().collect::<HashSet<_>>().difference(&used_variables).cloned().collect()\n    }\n\n    pub fn scope_at(&self, index: ScopeIndex) -> Option<&Scope> {\n        self.graph.scope_at(index)\n    }\n\n    pub fn global_scope(&self) -> &Scope {\n        self.graph.scope_at(self.root_index).expect(\"No root scope in graph\")\n    }\n\n    /// Evaluate a [SimplExpr] in a given scope. This will return `Err` if any referenced variables\n    /// are not available in the scope. If evaluation fails for other reasons (bad types, etc)\n    /// this will print a warning and return an empty string instead.\n    pub fn evaluate_simplexpr_in_scope(&self, index: ScopeIndex, expr: &SimplExpr) -> Result<DynVal> {\n        let needed_vars = self.lookup_variables_in_scope(index, &expr.collect_var_refs())?;\n        // TODORW\n        // TODO allowing it to fail here is painfully ugly\n        match expr.eval(&needed_vars) {\n            Ok(value) => Ok(value),\n            Err(err) => {\n                error_handling_ctx::print_error(anyhow!(err));\n                Ok(DynVal::from(\"\"))\n            }\n        }\n    }\n\n    /// Register a new scope in the graph.\n    /// This will look up and resolve variable references in attributes to set up the correct [`internal::ProvidedAttr`] relationships.\n    pub fn register_new_scope(\n        &mut self,\n        name: String,\n        superscope: Option<ScopeIndex>,\n        calling_scope: ScopeIndex,\n        attributes: HashMap<AttrName, SimplExpr>,\n    ) -> Result<ScopeIndex> {\n        let mut scope_variables = HashMap::new();\n\n        // First get the current values. If nothing here fails, we know that everything is in scope.\n        for (attr_name, attr_value) in &attributes {\n            let current_value = self.evaluate_simplexpr_in_scope(calling_scope, attr_value)?;\n            scope_variables.insert(attr_name.clone().into(), current_value);\n        }\n\n        // Now that we're sure that we have all of the values, we can make changes to the scopegraph  without\n        // risking getting it into an inconsistent state by adding a scope that can't get fully instantiated\n        // and aborting that operation prematurely.\n        let new_scope = Scope::new(name, Some(calling_scope), scope_variables);\n\n        let new_scope_index = self.graph.add_scope(new_scope);\n        if let Some(superscope) = superscope {\n            self.graph.add_inheritance_relation(new_scope_index, superscope);\n        }\n        if let Some(scope) = self.graph.scope_at_mut(new_scope_index) {\n            scope.node_index = new_scope_index;\n        }\n\n        for (attr_name, expression) in attributes {\n            let expression_var_refs = expression.collect_var_refs();\n            if !expression_var_refs.is_empty() {\n                self.graph.register_scope_provides_attr(\n                    calling_scope,\n                    new_scope_index,\n                    internal::ProvidedAttr { attr_name, expression },\n                );\n                for used_variable in expression_var_refs {\n                    self.register_scope_referencing_variable(calling_scope, used_variable)?;\n                }\n            }\n        }\n\n        #[cfg(debug_assertions)]\n        self.validate()?;\n\n        Ok(new_scope_index)\n    }\n\n    /// Register a listener. This listener will get called when any of the required variables change.\n    /// If there are no required_variables in the listener, nothing gets registered, but the listener\n    /// gets called once.\n    /// This should be used to update the gtk widgets that are in a scope.\n    /// This also calls the listener initially.\n    pub fn register_listener(&mut self, scope_index: ScopeIndex, listener: Listener) -> Result<()> {\n        if listener.needed_variables.is_empty() {\n            if let Err(err) = (*listener.f)(self, HashMap::new()).context(\"Error while updating UI after state change\") {\n                error_handling_ctx::print_error(err);\n            }\n        } else {\n            for required_var in &listener.needed_variables {\n                self.register_scope_referencing_variable(scope_index, required_var.clone())?;\n            }\n            let scope = self.graph.scope_at_mut(scope_index).context(\"Scope not in graph\")?;\n            let listener = Rc::new(listener);\n            for required_var in &listener.needed_variables {\n                scope.listeners.entry(required_var.clone()).or_default().push(listener.clone());\n            }\n\n            let required_variables = self.lookup_variables_in_scope(scope_index, &listener.needed_variables)?;\n            if let Err(err) = (*listener.f)(self, required_variables).context(\"Error while updating UI after state change\") {\n                error_handling_ctx::print_error(err);\n            }\n\n            #[cfg(debug_assertions)]\n            self.validate()?;\n        }\n\n        Ok(())\n    }\n\n    /// Register the fact that a scope is referencing a given variable.\n    /// If the scope contains the variable itself, this is a No-op. Otherwise, will add that reference to the inherited scope relation.\n    pub fn register_scope_referencing_variable(&mut self, scope_index: ScopeIndex, var_name: VarName) -> Result<()> {\n        if !self.graph.scope_at(scope_index).context(\"scope not in graph\")?.data.contains_key(&var_name) {\n            let superscope =\n                self.graph.superscope_of(scope_index).with_context(|| format!(\"Variable {} not in scope\", var_name))?;\n            self.graph.add_reference_to_inherits_edge(scope_index, var_name.clone())?;\n            self.register_scope_referencing_variable(superscope, var_name)?;\n        }\n        Ok(())\n    }\n\n    pub fn update_value(&mut self, original_scope_index: ScopeIndex, updated_var: &VarName, new_value: DynVal) -> Result<()> {\n        let scope_index = self\n            .find_scope_with_variable(original_scope_index, updated_var)\n            .with_context(|| format!(\"Variable {} not scope\", updated_var))?;\n\n        if let Some(entry) = self.graph.scope_at_mut(scope_index).and_then(|scope| scope.data.get_mut(updated_var)) {\n            *entry = new_value;\n        }\n\n        self.notify_value_changed(scope_index, updated_var)?;\n\n        #[cfg(debug_assertions)]\n        self.graph.validate()?;\n\n        Ok(())\n    }\n\n    /// Notify a scope that a value has been changed. This triggers the listeners and notifies further subscopes scopes recursively.\n    pub fn notify_value_changed(&mut self, scope_index: ScopeIndex, updated_var: &VarName) -> Result<()> {\n        // Update scopes that reference the changed variable in their attribute expressions.\n        let edges: Vec<(ScopeIndex, internal::ProvidedAttr)> =\n            self.graph.scopes_getting_attr_using(scope_index, updated_var).into_iter().map(|(a, b)| (a, b.clone())).collect();\n        for (referencing_scope, edge) in edges {\n            if let Err(err) = self.evaluate_simplexpr_in_scope(scope_index, &edge.expression).and_then(|updated_attr_value| {\n                self.update_value(referencing_scope, edge.attr_name.to_var_name_ref(), updated_attr_value)\n            }) {\n                error_handling_ctx::print_error(err);\n            }\n        }\n\n        // Trigger the listeners from this scope\n        self.call_listeners_in_scope(scope_index, updated_var)?;\n\n        // Now find subscopes that reference this variable\n        let affected_subscopes = self.graph.subscopes_referencing(scope_index, updated_var);\n        for affected_subscope in affected_subscopes {\n            self.notify_value_changed(affected_subscope, updated_var)?;\n        }\n        Ok(())\n    }\n\n    /// Call all of the listeners in a given `scope_index` that are affected by a change to the `updated_var`.\n    fn call_listeners_in_scope(&mut self, scope_index: ScopeIndex, updated_var: &VarName) -> Result<()> {\n        let scope = self.graph.scope_at(scope_index).context(\"Scope not in graph\")?;\n        if let Some(triggered_listeners) = scope.listeners.get(updated_var) {\n            for listener in triggered_listeners.clone() {\n                let required_variables = self.lookup_variables_in_scope(scope_index, &listener.needed_variables)?;\n                if let Err(err) = (*listener.f)(self, required_variables).context(\"Error while updating UI after state change\") {\n                    error_handling_ctx::print_error(err);\n                }\n            }\n        }\n        Ok(())\n    }\n\n    /// Find the closest available scope that contains variable with the given name.\n    pub fn find_scope_with_variable(&self, index: ScopeIndex, var_name: &VarName) -> Option<ScopeIndex> {\n        let scope = self.graph.scope_at(index)?;\n        if scope.data.contains_key(var_name) {\n            Some(index)\n        } else {\n            self.find_scope_with_variable(self.graph.superscope_of(index)?, var_name)\n        }\n    }\n\n    /// Find the value of a variable in the closest available scope that contains a variable with that name.\n    pub fn lookup_variable_in_scope(&self, index: ScopeIndex, var_name: &VarName) -> Option<&DynVal> {\n        self.find_scope_with_variable(index, var_name)\n            .and_then(|scope| self.graph.scope_at(scope))\n            .map(|x| x.data.get(var_name).unwrap())\n    }\n\n    /// Get all variables that are used in the given scope or in any descendants of that scope.\n    /// If called with an index not in the tree, will return an empty set of variables.\n    pub fn variables_used_in_self_or_subscopes_of(&self, index: ScopeIndex) -> HashSet<VarName> {\n        if let Some(scope) = self.scope_at(index) {\n            let mut variables: HashSet<VarName> = scope.listeners.keys().cloned().collect();\n            for (_, provided_attrs) in self.graph.descendant_edges_of(index) {\n                for attr in provided_attrs {\n                    variables.extend(attr.expression.collect_var_refs());\n                }\n            }\n            for (_, edge) in self.graph.subscope_edges_of(index) {\n                variables.extend(edge.references.clone());\n            }\n\n            // get all the variables that the current scope references from it's superscope\n            if let Some((_, edge)) = self.graph.superscope_edge_of(index) {\n                variables.extend(edge.references.clone())\n            }\n\n            // look through all descendants of this scope\n            for (descendant, _) in self.graph.descendant_edges_of(index) {\n                let used_in_descendant = self.variables_used_in_self_or_subscopes_of(descendant);\n\n                // only include those variables that are not shadowed by the descendant itself\n                let descendant_scope = self.scope_at(descendant).unwrap();\n                variables.extend(used_in_descendant.difference(&descendant_scope.data.keys().cloned().collect()).cloned());\n            }\n\n            variables\n        } else {\n            HashSet::new()\n        }\n    }\n\n    /// like [Self::lookup_variable_in_scope], but looks up a set of variables and stores them in a HashMap.\n    pub fn lookup_variables_in_scope(&self, scope_index: ScopeIndex, vars: &[VarName]) -> Result<HashMap<VarName, DynVal>> {\n        vars.iter()\n            .map(|required_var_name| {\n                let value = self\n                    .lookup_variable_in_scope(scope_index, required_var_name)\n                    .with_context(|| format!(\"Variable {} neither in scope nor any superscope\", required_var_name))?;\n\n                Ok((required_var_name.clone(), value.clone()))\n            })\n            .collect::<Result<_>>()\n    }\n}\n\nmod internal {\n    use super::{super::one_to_n_elements_map::OneToNElementsMap, *};\n\n    /// a --provides attribute [`Self::attr_name`] calculated via [`Self::expression`] to--> b\n    #[derive(Debug, Eq, PartialEq, Clone)]\n    pub struct ProvidedAttr {\n        pub attr_name: AttrName,\n        pub expression: SimplExpr,\n    }\n\n    /// a -- inherits scope of --> b\n    /// If a inherits from b, and references variable V, V may either be available in b or in scopes that b inherits from.\n    #[derive(Debug, Eq, PartialEq, Clone)]\n    pub struct Inherits {\n        /// The variable names the subscope references from the superscope\n        pub references: HashSet<VarName>,\n    }\n\n    /// The internal graph representation of the [`ScopeGraph`].\n    /// Unlike the public ScopeGraph, this may temporarily be in an inconsistent state while changes are being made.\n    #[derive(Debug)]\n    pub struct ScopeGraphInternal {\n        last_index: ScopeIndex,\n        scopes: HashMap<ScopeIndex, Scope>,\n\n        /// Edges from ancestors to descendants\n        pub(super) hierarchy_relations: OneToNElementsMap<ScopeIndex, Vec<ProvidedAttr>>,\n\n        /// Edges from superscopes to subscopes.\n        pub(super) inheritance_relations: OneToNElementsMap<ScopeIndex, Inherits>,\n    }\n\n    impl ScopeGraphInternal {\n        pub fn new() -> Self {\n            Self {\n                last_index: ScopeIndex(0),\n                scopes: HashMap::new(),\n                inheritance_relations: OneToNElementsMap::new(),\n                hierarchy_relations: OneToNElementsMap::new(),\n            }\n        }\n\n        pub fn clear(&mut self) {\n            self.scopes.clear();\n            self.inheritance_relations.clear();\n            self.hierarchy_relations.clear();\n        }\n\n        pub fn add_scope(&mut self, scope: Scope) -> ScopeIndex {\n            let idx = self.last_index;\n            if let Some(ancestor) = scope.ancestor {\n                let _ = self.hierarchy_relations.insert(idx, ancestor, Vec::new());\n            }\n            self.scopes.insert(idx, scope);\n            self.last_index.advance();\n            idx\n        }\n\n        pub fn descendant_edges_of(&self, index: ScopeIndex) -> Vec<(ScopeIndex, &Vec<ProvidedAttr>)> {\n            self.hierarchy_relations.get_children_edges_of(index)\n        }\n\n        pub fn subscope_edges_of(&self, index: ScopeIndex) -> Vec<(ScopeIndex, &Inherits)> {\n            self.inheritance_relations.get_children_edges_of(index)\n        }\n\n        pub fn superscope_edge_of(&self, index: ScopeIndex) -> Option<&(ScopeIndex, Inherits)> {\n            self.inheritance_relations.get_parent_edge_of(index)\n        }\n\n        pub fn remove_scope(&mut self, index: ScopeIndex) {\n            self.scopes.remove(&index);\n            if let Some(descendants) = self.hierarchy_relations.parent_to_children.get(&index).cloned() {\n                for descendant in descendants {\n                    self.remove_scope(descendant);\n                }\n            }\n            self.hierarchy_relations.remove(index);\n            self.inheritance_relations.remove(index);\n        }\n\n        pub fn add_inheritance_relation(&mut self, a: ScopeIndex, b: ScopeIndex) {\n            self.inheritance_relations.insert(a, b, Inherits { references: HashSet::new() }).unwrap();\n        }\n\n        /// Register that a given scope `a` provides an attribute to it's descendant `b`.\n        pub fn register_scope_provides_attr(&mut self, a: ScopeIndex, b: ScopeIndex, edge: ProvidedAttr) {\n            if let Some((superscope, edges)) = self.hierarchy_relations.get_parent_edge_mut(b) {\n                assert_eq!(*superscope, a, \"Hierarchy map had a different superscope for a given scope than what was given here\");\n                edges.push(edge);\n            } else {\n                log::error!(\n                    \"Tried to register a provided attribute edge between two scopes that are not connected in the hierarchy map\"\n                );\n            }\n        }\n\n        pub fn scope_at(&self, index: ScopeIndex) -> Option<&Scope> {\n            self.scopes.get(&index)\n        }\n\n        pub fn scope_at_mut(&mut self, index: ScopeIndex) -> Option<&mut Scope> {\n            self.scopes.get_mut(&index)\n        }\n\n        /// List all subscopes that reference a given variable directly (-> the variable is in the [Inherits::references])\n        pub fn subscopes_referencing(&self, index: ScopeIndex, var_name: &VarName) -> Vec<ScopeIndex> {\n            self.inheritance_relations\n                .get_children_edges_of(index)\n                .iter()\n                .filter(|(_, edge)| edge.references.contains(var_name))\n                .map(|(scope, _)| *scope)\n                .collect()\n        }\n\n        pub fn superscope_of(&self, index: ScopeIndex) -> Option<ScopeIndex> {\n            self.inheritance_relations.get_parent_of(index)\n        }\n\n        /// List the scopes that are provided some attribute referencing `var_name` by the given scope `index`.\n        pub fn scopes_getting_attr_using(&self, index: ScopeIndex, var_name: &VarName) -> Vec<(ScopeIndex, &ProvidedAttr)> {\n            let edge_mappings = self.hierarchy_relations.get_children_edges_of(index);\n            edge_mappings\n                .iter()\n                .flat_map(|(k, v)| v.iter().map(move |edge| (*k, edge)))\n                .filter(|(_, edge)| edge.expression.references_var(var_name))\n                .collect()\n        }\n\n        /// Register that a given scope references a variable from it's direct superscope.\n        /// If the given scope does not have a superscope, this will return an `Err`.\n        pub fn add_reference_to_inherits_edge(&mut self, subscope: ScopeIndex, var_name: VarName) -> Result<()> {\n            let (_, edge) = self\n                .inheritance_relations\n                .get_parent_edge_mut(subscope)\n                .with_context(|| format!(\"Given scope {:?} does not have any superscope\", subscope))?;\n            edge.references.insert(var_name);\n            Ok(())\n        }\n\n        #[cfg_attr(not(debug_assertions), allow(dead_code))]\n        pub fn validate(&self) -> Result<()> {\n            for (child_scope, (parent_scope, _edge)) in &self.hierarchy_relations.child_to_parent {\n                if !self.scopes.contains_key(child_scope) {\n                    bail!(\"hierarchy_relations lists key that is not in graph\");\n                }\n                if !self.scopes.contains_key(parent_scope) {\n                    bail!(\"hierarchy_relations values lists scope that is not in graph\");\n                }\n            }\n            for (child_scope, (parent_scope_idx, edge)) in &self.inheritance_relations.child_to_parent {\n                if !self.scopes.contains_key(child_scope) {\n                    bail!(\"inheritance_relations lists key that is not in graph\");\n                }\n                if let Some(parent_scope) = self.scopes.get(parent_scope_idx) {\n                    // check that everything the scope references from it's parent is actually\n                    // accessible by the parent, meaning it either stores it directly or\n                    // inherits it itself\n                    for var in &edge.references {\n                        let parent_has_access_to_var = parent_scope.data.contains_key(var)\n                            || self\n                                .inheritance_relations\n                                .child_to_parent\n                                .get(parent_scope_idx)\n                                .map_or(false, |(_, e)| e.references.contains(var));\n                        if !parent_has_access_to_var {\n                            bail!(\"scope inherited variable that parent scope doesn't have access to\");\n                        }\n                    }\n                } else {\n                    bail!(\"inheritance_relations values lists scope that is not in graph\");\n                }\n            }\n\n            self.hierarchy_relations.validate()?;\n            self.inheritance_relations.validate()?;\n\n            Ok(())\n        }\n\n        pub fn visualize(&self) -> String {\n            let mut output = String::new();\n            output.push_str(\"digraph {\\n\");\n\n            for (scope_index, scope) in &self.scopes {\n                output.push_str(&format!(\n                    \"  \\\"{:?}\\\"[label=\\\"{}\\\\n{}\\\"]\\n\",\n                    scope_index,\n                    scope.name,\n                    format!(\n                        \"data: {:?}, listeners: {:?}\",\n                        scope.data.iter().filter(|(k, _v)| !k.0.starts_with(\"EWW\")).collect::<Vec<_>>(),\n                        scope\n                            .listeners\n                            .iter()\n                            .map(|(k, v)| format!(\n                                \"on {}: {:?}\",\n                                k.0,\n                                v.iter()\n                                    .map(|l| format!(\"{:?}\", l.needed_variables.iter().map(|x| x.0.clone()).collect::<Vec<_>>()))\n                                    .collect::<Vec<_>>()\n                            ))\n                            .collect::<Vec<_>>()\n                    )\n                    .replace('\\\"', \"'\")\n                ));\n                if let Some(created_by) = scope.ancestor {\n                    output.push_str(&format!(\"  \\\"{:?}\\\" -> \\\"{:?}\\\"[label=\\\"ancestor\\\"]\\n\", created_by, scope_index));\n                }\n            }\n\n            for (child, (parent, edges)) in &self.hierarchy_relations.child_to_parent {\n                for edge in edges {\n                    output.push_str(&format!(\n                        \"  \\\"{:?}\\\" -> \\\"{:?}\\\" [color = \\\"red\\\", label = \\\"{}\\\"]\\n\",\n                        parent,\n                        child,\n                        format!(\":{} `{:?}`\", edge.attr_name, edge.expression).replace('\\\"', \"'\")\n                    ));\n                }\n            }\n            for (child, (parent, edge)) in &self.inheritance_relations.child_to_parent {\n                output.push_str(&format!(\n                    \"  \\\"{:?}\\\" -> \\\"{:?}\\\" [color = \\\"blue\\\", label = \\\"{}\\\"]\\n\",\n                    child,\n                    parent,\n                    format!(\"inherits({:?})\", edge.references).replace('\\\"', \"'\")\n                ));\n            }\n\n            output.push('}');\n            output\n        }\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use maplit::{hashmap, hashset};\n\n    use super::*;\n\n    #[test]\n    fn test_nested_inheritance() {\n        let globals = hashmap! {\n            \"global\".into() => \"hi\".into(),\n        };\n\n        let (send, _recv) = tokio::sync::mpsc::unbounded_channel();\n\n        let mut scope_graph = ScopeGraph::from_global_vars(globals, send);\n        let root_scope = scope_graph.root_index;\n\n        let widget1_scope = scope_graph.register_new_scope(\"1\".into(), Some(root_scope), root_scope, hashmap! {}).unwrap();\n        let widget2_scope = scope_graph.register_new_scope(\"2\".into(), Some(widget1_scope), widget1_scope, hashmap! {}).unwrap();\n        scope_graph.register_scope_referencing_variable(widget2_scope, \"global\".into()).unwrap();\n\n        let inheritance_child_to_parent = scope_graph.graph.inheritance_relations.child_to_parent;\n        assert!(inheritance_child_to_parent.get(&widget2_scope).unwrap().1.references.contains(\"global\"));\n        assert!(inheritance_child_to_parent.get(&widget1_scope).unwrap().1.references.contains(\"global\"));\n    }\n\n    #[test]\n    fn test_lookup_variable_in_scope() {\n        let globals = hashmap! {\n            \"global\".into() => \"hi\".into(),\n        };\n\n        let (send, _recv) = tokio::sync::mpsc::unbounded_channel();\n\n        let mut scope_graph = ScopeGraph::from_global_vars(globals, send);\n\n        let widget_1_scope = scope_graph\n            .register_new_scope(\"1\".to_string(), Some(scope_graph.root_index), scope_graph.root_index, hashmap! {})\n            .unwrap();\n        let widget_2_scope =\n            scope_graph.register_new_scope(\"2\".to_string(), Some(widget_1_scope), widget_1_scope, hashmap! {}).unwrap();\n        let widget_no_parent_scope = scope_graph.register_new_scope(\"2\".to_string(), None, widget_1_scope, hashmap! {}).unwrap();\n\n        scope_graph.register_scope_referencing_variable(widget_2_scope, \"global\".into()).unwrap();\n\n        assert_eq!(scope_graph.lookup_variable_in_scope(widget_2_scope, &\"global\".into()).unwrap(), &\"hi\".into());\n        assert_eq!(scope_graph.lookup_variable_in_scope(widget_1_scope, &\"global\".into()).unwrap(), &\"hi\".into());\n        assert_eq!(scope_graph.lookup_variable_in_scope(widget_no_parent_scope, &\"global\".into()), None);\n    }\n\n    /// tests the following graph structure:\n    /// ```\n    ///              ┌───────────────────────────────────────────────────┐\n    ///              │                      widget2                      │\n    ///              │          data: [('shadowed_var', 'hi')]           │ ──────────────────┐\n    ///              └───────────────────────────────────────────────────┘                   │\n    ///                ▲                                                                     │\n    ///                │ ancestor                                                            │\n    ///                │                                                                     │\n    ///              ┌───────────────────────────────────────────────────┐                   │\n    ///              │                      window                       │                   │\n    ///   ┌────────▶ │                     data: []                      │ ─┐                │\n    ///   │          └───────────────────────────────────────────────────┘  │                │\n    ///   │            │                                                    │                │\n    ///   │            │ ancestor                                           │                │\n    ///   │            ▼                                                    │                │\n    ///   │          ┌───────────────────────────────────────────────────┐  │                │\n    ///   │          │                      widget                       │  │                │\n    ///   │ ancestor │                     data: []                      │  │ inherits({})   │\n    ///   │          └───────────────────────────────────────────────────┘  │                │\n    ///   │            │                                                    │                │\n    ///   │            │ inherits({'the_var'})                              │                │\n    ///   │            ▼                                                    │                │\n    ///   │          ┌───────────────────────────────────────────────────┐  │                │\n    ///   │          │                      global                       │  │                │\n    ///   └───────── │ data: [('shadowed_var', 'hi'), ('the_var', 'hi')] │ ◀┘                │\n    ///              └───────────────────────────────────────────────────┘                   │\n    ///                ▲                                                   inherits({})      │\n    ///                └─────────────────────────────────────────────────────────────────────┘\n    /// ```\n    #[test]\n    fn test_variables_used_in_self_or_subscopes_of() {\n        let globals = hashmap! {\n            \"the_var\".into() => \"hi\".into(),\n            \"shadowed_var\".into() => \"hi\".into(),\n        };\n\n        let (send, _recv) = tokio::sync::mpsc::unbounded_channel();\n\n        let mut scope_graph = ScopeGraph::from_global_vars(globals, send);\n\n        let window_scope = scope_graph\n            .register_new_scope(\"window\".to_string(), Some(scope_graph.root_index), scope_graph.root_index, hashmap! {})\n            .unwrap();\n        let widget_scope = scope_graph\n            .register_new_scope(\"widget\".to_string(), Some(scope_graph.root_index), window_scope, hashmap! {})\n            .unwrap();\n        let _widget_with_local_var_scope = scope_graph\n            .register_new_scope(\n                \"widget2\".to_string(),\n                Some(scope_graph.root_index),\n                window_scope,\n                hashmap! { \"shadowed_var\".into() => SimplExpr::synth_literal(\"hi\") },\n            )\n            .unwrap();\n\n        scope_graph.register_scope_referencing_variable(widget_scope, \"the_var\".into()).unwrap();\n\n        assert_eq!(\n            scope_graph.variables_used_in_self_or_subscopes_of(scope_graph.root_index),\n            hashset![\"the_var\".into()],\n            \"Wrong variables assumed to be used by global\"\n        );\n        assert_eq!(\n            scope_graph.variables_used_in_self_or_subscopes_of(window_scope),\n            hashset![\"the_var\".into()],\n            \"Wrong variables assumed to be used by window\"\n        );\n        assert_eq!(\n            scope_graph.variables_used_in_self_or_subscopes_of(widget_scope),\n            hashset![\"the_var\".into()],\n            \"Wrong variables assumed to be used by widget\"\n        );\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/state/test.rs",
    "content": "use super::scope::Listener;\nuse std::sync::{\n    atomic::{AtomicBool, Ordering},\n    Arc,\n};\n\nuse eww_shared_util::{Span, VarName};\nuse maplit::hashmap;\nuse simplexpr::{dynval::DynVal, SimplExpr};\n\nuse crate::state::scope_graph::ScopeGraph;\n\npub fn create_fn_verificator() -> (Arc<AtomicBool>, Box<dyn Fn()>) {\n    let check = Arc::new(AtomicBool::new(false));\n    let check_moved = check.clone();\n    let f = Box::new(move || check_moved.store(true, Ordering::Relaxed));\n    (check, f)\n}\n\n#[allow(unused)]\nmacro_rules! make_listener {\n    (|$($varname:expr => $name:ident),*| $body:block) => {\n        Listener {\n            needed_variables: vec![$($varname),*],\n            f: Box::new(move |_, values| {\n                $(\n                    let $name = values.get(&$varname).unwrap();\n                )*\n                $body\n                Ok(())\n            })\n        }\n    };\n    (@short |$($varname:ident),*| $body:block) => {\n        make_listener!(|$(VarName(stringify!($varname).to_string()) => $varname),*| $body)\n    }\n}\n\n#[test]\npub fn test_delete_scope() {\n    let globals = hashmap! {\n        VarName(\"global_1\".to_string()) => DynVal::from(\"hi\"),\n    };\n\n    let (send, _recv) = tokio::sync::mpsc::unbounded_channel();\n\n    let mut scope_graph = ScopeGraph::from_global_vars(globals, send);\n\n    let widget_foo_scope = scope_graph\n        .register_new_scope(\n            \"foo\".to_string(),\n            Some(scope_graph.root_index),\n            scope_graph.root_index,\n            hashmap! {\n                \"arg_1\".into() => SimplExpr::var_ref(Span::DUMMY, \"global_1\"),\n            },\n        )\n        .unwrap();\n    let widget_bar_scope = scope_graph\n        .register_new_scope(\n            \"bar\".to_string(),\n            Some(scope_graph.root_index),\n            widget_foo_scope,\n            hashmap! {\n                \"arg_3\".into() => SimplExpr::var_ref(Span::DUMMY, \"arg_1\"),\n            },\n        )\n        .unwrap();\n\n    scope_graph.validate().unwrap();\n\n    scope_graph.remove_scope(widget_bar_scope);\n    scope_graph.validate().unwrap();\n    assert!(scope_graph.scope_at(widget_bar_scope).is_none());\n}\n\n#[test]\nfn test_state_updates() {\n    let globals = hashmap! {\n     \"global_1\".into() => DynVal::from(\"hi\"),\n     \"global_2\".into() => DynVal::from(\"hey\"),\n    };\n\n    let (send, _recv) = tokio::sync::mpsc::unbounded_channel();\n\n    let mut scope_graph = ScopeGraph::from_global_vars(globals, send);\n\n    let widget_foo_scope = scope_graph\n        .register_new_scope(\n            \"foo\".to_string(),\n            Some(scope_graph.root_index),\n            scope_graph.root_index,\n            hashmap! {\n                \"arg_1\".into() => SimplExpr::var_ref(Span::DUMMY, \"global_1\"),\n                \"arg_2\".into() => SimplExpr::synth_string(\"static value\"),\n            },\n        )\n        .unwrap();\n    let widget_bar_scope = scope_graph\n        .register_new_scope(\n            \"bar\".to_string(),\n            Some(scope_graph.root_index),\n            widget_foo_scope,\n            hashmap! {\n                \"arg_3\".into() => SimplExpr::Concat(Span::DUMMY, vec![\n                    SimplExpr::var_ref(Span::DUMMY, \"arg_1\"),\n                    SimplExpr::synth_literal(\"static_value\"),\n                ])\n            },\n        )\n        .unwrap();\n\n    let (foo_verify, foo_f) = create_fn_verificator();\n\n    scope_graph\n        .register_listener(\n            widget_foo_scope,\n            make_listener!(@short |arg_1| {\n                println!(\"foo: arg_1 changed to {}\", arg_1);\n                if arg_1 == &\"pog\".into() {\n                    foo_f()\n                }\n            }),\n        )\n        .unwrap();\n    let (bar_verify, bar_f) = create_fn_verificator();\n    scope_graph\n        .register_listener(\n            widget_bar_scope,\n            make_listener!(@short |arg_3| {\n                println!(\"bar: arg_3 changed to {}\", arg_3);\n                if arg_3 == &\"pogstatic_value\".into() {\n                    bar_f()\n                }\n            }),\n        )\n        .unwrap();\n\n    let (bar_2_verify, bar_2_f) = create_fn_verificator();\n    scope_graph\n        .register_listener(\n            widget_bar_scope,\n            make_listener!(@short |global_2| {\n                println!(\"bar: global_2 changed to {}\", global_2);\n                if global_2 == &\"new global 2\".into() {\n                    bar_2_f()\n                }\n            }),\n        )\n        .unwrap();\n\n    scope_graph.update_value(scope_graph.root_index, &\"global_1\".into(), \"pog\".into()).unwrap();\n    assert!(foo_verify.load(Ordering::Relaxed), \"update in foo did not trigger properly\");\n    assert!(bar_verify.load(Ordering::Relaxed), \"update in bar did not trigger properly\");\n\n    scope_graph.update_value(scope_graph.root_index, &\"global_2\".into(), \"new global 2\".into()).unwrap();\n    assert!(bar_2_verify.load(Ordering::Relaxed), \"inherited global update did not trigger properly\");\n}\n"
  },
  {
    "path": "crates/eww/src/util.rs",
    "content": "use extend::ext;\nuse itertools::Itertools;\nuse std::fmt::Write;\n\n#[macro_export]\nmacro_rules! print_result_err {\n    ($context:expr, $result:expr $(,)?) => {{\n        if let Err(err) = $result {\n            log::error!(\"[{}:{}] Error {}: {:?}\", ::std::file!(), ::std::line!(), $context, err);\n        }\n    }};\n}\n\n#[macro_export]\nmacro_rules! loop_select {\n    ($($body:tt)*) => {\n        loop {\n            ::tokio::select! {\n                $($body)*\n            };\n        }\n    }\n}\n\n#[macro_export]\nmacro_rules! regex {\n    ($re:literal $(,)?) => {{\n        static RE: once_cell::sync::OnceCell<regex::Regex> = once_cell::sync::OnceCell::new();\n        RE.get_or_init(|| regex::Regex::new($re).unwrap())\n    }};\n}\n\n/// Parse a string with a concrete set of options into some data-structure,\n/// and return a nicely formatted error message on invalid values. I.e.:\n/// ```rs\n/// let input = \"up\";\n/// enum_parse { \"direction\", input,\n///   \"up\" => Direction::Up,\n///   \"down\" => Direction::Down,\n/// }\n/// ```\n#[macro_export]\nmacro_rules! enum_parse {\n    ($name:literal, $input:expr, $($($s:literal)|* => $val:expr),* $(,)?) => {\n        let input = $input.to_lowercase();\n        match input.as_str() {\n            $( $( $s )|* => Ok($val) ),*,\n            _ => Err(anyhow!(concat!(\"Couldn't parse \", $name, \": '{}'. Possible values are \", $($($s, \" \"),*),*), input))\n        }\n    };\n}\n\n/// Compute the difference of two lists, returning a tuple of\n/// (\n///   elements that where in a but not in b,\n///   elements that where in b but not in a\n/// ).\npub fn list_difference<'a, 'b, T: PartialEq>(a: &'a [T], b: &'b [T]) -> (Vec<&'a T>, Vec<&'b T>) {\n    let mut missing = Vec::new();\n    for elem in a {\n        if !b.contains(elem) {\n            missing.push(elem);\n        }\n    }\n\n    let mut new = Vec::new();\n    for elem in b {\n        if !a.contains(elem) {\n            new.push(elem);\n        }\n    }\n    (missing, new)\n}\n\n#[ext(pub, name = StringExt)]\nimpl<T: AsRef<str>> T {\n    /// check if the string is empty after removing all linebreaks and trimming\n    /// whitespace\n    fn is_blank(self) -> bool {\n        self.as_ref().replace('\\n', \"\").trim().is_empty()\n    }\n\n    /// trim all lines in a string\n    fn trim_lines(self) -> String {\n        self.as_ref().lines().map(|line| line.trim()).join(\"\\n\")\n    }\n}\n\npub trait IterAverage {\n    fn avg(self) -> f32;\n}\n\nimpl<I: Iterator<Item = f32>> IterAverage for I {\n    fn avg(self) -> f32 {\n        let mut total = 0f32;\n        let mut cnt = 0f32;\n        for value in self {\n            total += value;\n            cnt += 1f32;\n        }\n        total / cnt\n    }\n}\n\n/// Replace all env-var references of the format `\"something ${foo}\"` in a string\n/// by the actual env-variables. If the env-var isn't found, will replace the\n/// reference with an empty string.\npub fn replace_env_var_references(input: String) -> String {\n    regex!(r\"\\$\\{([^\\s]*)\\}\")\n        .replace_all(&input, |var_name: &regex::Captures| std::env::var(var_name.get(1).unwrap().as_str()).unwrap_or_default())\n        .into_owned()\n}\n\npub fn unindent(text: &str) -> String {\n    // take all the lines of our text and skip over the first empty ones\n    let lines = text.lines().skip_while(|x| x.is_empty());\n    // find the smallest indentation\n    let min = lines\n        .clone()\n        .fold(None, |min, line| {\n            let min = min.unwrap_or(usize::MAX);\n            Some(min.min(line.chars().take(min).take_while(|&c| c == ' ').count()))\n        })\n        .unwrap_or(0);\n\n    let mut result = String::new();\n    for i in lines {\n        writeln!(result, \"{}\", &i[min..]).expect(\"Something went wrong unindenting the string\");\n    }\n    result.pop();\n    result\n}\n\n#[cfg(test)]\nmod test {\n    use super::{replace_env_var_references, unindent};\n\n    #[test]\n    fn test_replace_env_var_references() {\n        let scss = \"$test: ${USER};\";\n\n        assert_eq!(\n            replace_env_var_references(String::from(scss)),\n            format!(\"$test: {};\", std::env::var(\"USER\").unwrap_or_default())\n        )\n    }\n\n    #[test]\n    fn test_unindent() {\n        let indented = \"\n            line one\n            line two\";\n        assert_eq!(\"line one\\nline two\", unindent(indented));\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/widgets/build_widget.rs",
    "content": "use anyhow::{Context, Result};\nuse codespan_reporting::diagnostic::Severity;\nuse eww_shared_util::{AttrName, Spanned};\nuse gtk::{\n    gdk::prelude::Cast,\n    prelude::{BoxExt, ContainerExt, WidgetExt},\n    Orientation,\n};\nuse itertools::Itertools;\nuse maplit::hashmap;\nuse simplexpr::{dynval::DynVal, SimplExpr};\nuse std::{cell::RefCell, collections::HashMap, rc::Rc};\nuse yuck::{\n    config::{\n        attributes::AttrEntry,\n        widget_definition::WidgetDefinition,\n        widget_use::{BasicWidgetUse, ChildrenWidgetUse, LoopWidgetUse, WidgetUse},\n    },\n    error::DiagError,\n    gen_diagnostic,\n};\n\nuse crate::{\n    error_handling_ctx,\n    state::{\n        scope::Listener,\n        scope_graph::{ScopeGraph, ScopeGraphEvent, ScopeIndex},\n    },\n    widgets::widget_definitions,\n};\n\nuse super::widget_definitions::{resolve_orientable_attrs, resolve_range_attrs, resolve_widget_attrs};\n\npub struct BuilderArgs<'a> {\n    pub calling_scope: ScopeIndex,\n    pub widget_use: BasicWidgetUse,\n    pub scope_graph: &'a mut ScopeGraph,\n    pub unhandled_attrs: HashMap<AttrName, AttrEntry>,\n    pub widget_defs: Rc<HashMap<String, WidgetDefinition>>,\n    pub custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,\n}\n\n// TODO in case of custom widgets, we should add a validation step where\n// warnings for unknown attributes (attributes not expected by the widget) are emitted.\n\n/// Build a [`gtk::Widget`] out of a [`WidgetUse`].\n/// This will set up scopes in the [`ScopeGraph`], register all the listeners there,\n/// and recursively generate all the widgets and child widgets.\npub fn build_gtk_widget(\n    graph: &mut ScopeGraph,\n    widget_defs: Rc<HashMap<String, WidgetDefinition>>,\n    calling_scope: ScopeIndex,\n    widget_use: WidgetUse,\n    custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,\n) -> Result<gtk::Widget> {\n    match widget_use {\n        WidgetUse::Basic(widget_use) => {\n            build_basic_gtk_widget(graph, widget_defs, calling_scope, widget_use, custom_widget_invocation)\n        }\n        WidgetUse::Loop(_) | WidgetUse::Children(_) => Err(anyhow::anyhow!(DiagError(gen_diagnostic! {\n            msg = \"This widget can only be used as a child of some container widget such as box\",\n            label = widget_use.span(),\n            note = \"Hint: try wrapping this in a `box`\"\n        }))),\n    }\n}\n\nfn build_basic_gtk_widget(\n    graph: &mut ScopeGraph,\n    widget_defs: Rc<HashMap<String, WidgetDefinition>>,\n    calling_scope: ScopeIndex,\n    mut widget_use: BasicWidgetUse,\n    custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,\n) -> Result<gtk::Widget> {\n    if let Some(custom_widget) = widget_defs.clone().get(&widget_use.name) {\n        let widget_use_attributes = custom_widget\n            .expected_args\n            .iter()\n            .map(|spec| {\n                let expr = if spec.optional {\n                    widget_use\n                        .attrs\n                        .ast_optional::<SimplExpr>(&spec.name.0)?\n                        .unwrap_or_else(|| SimplExpr::literal(spec.span, \"\".to_string()))\n                } else {\n                    widget_use.attrs.ast_required::<SimplExpr>(&spec.name.0)?\n                };\n                Ok((spec.name.clone(), expr))\n            })\n            .collect::<Result<HashMap<_, _>>>()?;\n\n        let root_index = graph.root_index;\n        let new_scope_index =\n            graph.register_new_scope(widget_use.name, Some(root_index), calling_scope, widget_use_attributes)?;\n\n        let gtk_widget = build_gtk_widget(\n            graph,\n            widget_defs,\n            new_scope_index,\n            custom_widget.widget.clone(),\n            Some(Rc::new(CustomWidgetInvocation { scope: calling_scope, children: widget_use.children })),\n        )?;\n\n        let scope_graph_sender = graph.event_sender.clone();\n\n        gtk_widget.connect_destroy(move |_| {\n            let _ = scope_graph_sender.send(ScopeGraphEvent::RemoveScope(new_scope_index));\n        });\n        Ok(gtk_widget)\n    } else {\n        build_builtin_gtk_widget(graph, widget_defs, calling_scope, widget_use, custom_widget_invocation)\n    }\n}\n\n/// build a [`gtk::Widget`] out of a [`WidgetUse`] that uses a\n/// **builtin widget**. User defined widgets are handled by [`widget_definitions::widget_use_to_gtk_widget`].\n///\n/// Also registers all the necessary scopes in the scope graph\n///\n/// This may return `Err` in case there was an actual error while parsing or\n/// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any\n/// widget name.\nfn build_builtin_gtk_widget(\n    graph: &mut ScopeGraph,\n    widget_defs: Rc<HashMap<String, WidgetDefinition>>,\n    calling_scope: ScopeIndex,\n    widget_use: BasicWidgetUse,\n    custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,\n) -> Result<gtk::Widget> {\n    let mut bargs = BuilderArgs {\n        unhandled_attrs: widget_use.attrs.attrs.clone(),\n        scope_graph: graph,\n        calling_scope,\n        widget_use,\n        widget_defs,\n        custom_widget_invocation,\n    };\n    let gtk_widget = widget_definitions::widget_use_to_gtk_widget(&mut bargs)?;\n\n    if let Some(gtk_container) = gtk_widget.dynamic_cast_ref::<gtk::Container>() {\n        validate_container_children_count(gtk_container, &bargs.widget_use)?;\n\n        // Only populate children if there haven't been any children added anywhere else\n        // TODO this is somewhat hacky\n        if gtk_container.children().is_empty() {\n            populate_widget_children(\n                bargs.scope_graph,\n                bargs.widget_defs.clone(),\n                calling_scope,\n                gtk_container,\n                bargs.widget_use.children.clone(),\n                bargs.custom_widget_invocation.clone(),\n            )?;\n        }\n    }\n\n    if let Some(w) = gtk_widget.dynamic_cast_ref() {\n        resolve_range_attrs(&mut bargs, w)?;\n    }\n    if let Some(w) = gtk_widget.dynamic_cast_ref() {\n        resolve_orientable_attrs(&mut bargs, w)?;\n    };\n    resolve_widget_attrs(&mut bargs, &gtk_widget)?;\n\n    for (attr_name, attr_entry) in bargs.unhandled_attrs {\n        let diag = error_handling_ctx::stringify_diagnostic(gen_diagnostic! {\n            kind =  Severity::Warning,\n            msg = format!(\"Unknown attribute {attr_name}\"),\n            label = attr_entry.key_span => \"given here\"\n        })?;\n        eprintln!(\"{}\", diag);\n    }\n    Ok(gtk_widget)\n}\n\n/// If a gtk widget can take children (→ it is a [`gtk::Container`]) we need to add the provided `widget_use_children`\n/// into that container. Those children might be uses of the special `children`-[`WidgetUse`], which will get expanded here, too.\nfn populate_widget_children(\n    tree: &mut ScopeGraph,\n    widget_defs: Rc<HashMap<String, WidgetDefinition>>,\n    calling_scope: ScopeIndex,\n    gtk_container: &gtk::Container,\n    widget_use_children: Vec<WidgetUse>,\n    custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,\n) -> Result<()> {\n    for child in widget_use_children {\n        match child {\n            WidgetUse::Children(child) => {\n                build_children_special_widget(\n                    tree,\n                    widget_defs.clone(),\n                    calling_scope,\n                    child,\n                    gtk_container,\n                    custom_widget_invocation.clone().context(\"Not in a custom widget invocation\")?,\n                )?;\n            }\n            WidgetUse::Loop(child) => {\n                build_loop_special_widget(\n                    tree,\n                    widget_defs.clone(),\n                    calling_scope,\n                    child,\n                    gtk_container,\n                    custom_widget_invocation.clone(),\n                )?;\n            }\n            _ => {\n                let child_widget =\n                    build_gtk_widget(tree, widget_defs.clone(), calling_scope, child, custom_widget_invocation.clone())?;\n                gtk_container.add(&child_widget);\n            }\n        }\n    }\n    Ok(())\n}\n\nfn build_loop_special_widget(\n    tree: &mut ScopeGraph,\n    widget_defs: Rc<HashMap<String, WidgetDefinition>>,\n    calling_scope: ScopeIndex,\n    widget_use: LoopWidgetUse,\n    gtk_container: &gtk::Container,\n    custom_widget_invocation: Option<Rc<CustomWidgetInvocation>>,\n) -> Result<()> {\n    tree.register_listener(\n        calling_scope,\n        Listener {\n            needed_variables: widget_use.elements_expr.collect_var_refs(),\n            f: Box::new({\n                let elements_expr = widget_use.elements_expr.clone();\n                let elements_expr_span = widget_use.elements_expr_span;\n                let element_name = widget_use.element_name.clone();\n                let body: WidgetUse = widget_use.body.as_ref().clone();\n                let created_children = Rc::new(RefCell::new(Vec::<gtk::Widget>::new()));\n                let created_child_scopes = Rc::new(RefCell::new(Vec::<ScopeIndex>::new()));\n                let gtk_container = gtk_container.clone();\n                move |tree, values| {\n                    let elements_value = elements_expr\n                        .eval(&values)?\n                        .as_json_value()?\n                        .as_array()\n                        .context(\"Not an array value\")?\n                        .iter()\n                        .map(DynVal::from)\n                        .collect_vec();\n                    let mut created_children = created_children.borrow_mut();\n                    for old_child in created_children.drain(..) {\n                        gtk_container.remove(&old_child);\n                    }\n                    let mut created_child_scopes = created_child_scopes.borrow_mut();\n                    for child_scope in created_child_scopes.drain(..) {\n                        tree.remove_scope(child_scope);\n                    }\n\n                    for element in elements_value {\n                        let scope = tree.register_new_scope(\n                            format!(\"for {} = {}\", element_name.0, element),\n                            Some(calling_scope),\n                            calling_scope,\n                            hashmap! {\n                                element_name.clone().into() => SimplExpr::Literal(DynVal(element.0, elements_expr_span))\n                            },\n                        )?;\n                        created_child_scopes.push(scope);\n                        let new_child_widget =\n                            build_gtk_widget(tree, widget_defs.clone(), scope, body.clone(), custom_widget_invocation.clone())?;\n                        gtk_container.add(&new_child_widget);\n                        created_children.push(new_child_widget);\n                    }\n\n                    Ok(())\n                }\n            }),\n        },\n    )\n}\n\n/// Handle an invocation of the special `children` [`WidgetUse`].\n/// This widget expands to multiple other widgets, thus we require the `gtk_container` we should expand the widgets into.\n/// The `custom_widget_invocation` will be used here to evaluate the provided children in their\n/// original scope and expand them into the given container.\nfn build_children_special_widget(\n    tree: &mut ScopeGraph,\n    widget_defs: Rc<HashMap<String, WidgetDefinition>>,\n    calling_scope: ScopeIndex,\n    widget_use: ChildrenWidgetUse,\n    gtk_container: &gtk::Container,\n    custom_widget_invocation: Rc<CustomWidgetInvocation>,\n) -> Result<()> {\n    if let Some(nth) = widget_use.nth_expr {\n        // TODORW this might not be necessary, if I can keep a copy of the widget I can destroy it directly, no need to go through the container.\n        // This should be a custom gtk::Bin subclass,..\n        let child_container = gtk::Box::new(Orientation::Horizontal, 0);\n        child_container.set_homogeneous(true);\n        gtk_container.add(&child_container);\n\n        tree.register_listener(\n            calling_scope,\n            Listener {\n                needed_variables: nth.collect_var_refs(),\n                f: Box::new({\n                    move |tree, values| {\n                        let nth_value = nth.eval(&values)?.as_i32()?;\n                        let nth_child_widget_use = custom_widget_invocation\n                            .children\n                            .get(nth_value as usize)\n                            .with_context(|| format!(\"No child at index {}\", nth_value))?;\n                        let scope = tree.register_new_scope(\n                            format!(\"child {nth_value}\"),\n                            Some(custom_widget_invocation.scope),\n                            calling_scope,\n                            HashMap::new(),\n                        )?;\n                        let new_child_widget =\n                            build_gtk_widget(tree, widget_defs.clone(), scope, nth_child_widget_use.clone(), None)?;\n                        child_container.children().iter().for_each(|f| child_container.remove(f));\n                        child_container.set_child(Some(&new_child_widget));\n                        new_child_widget.show();\n                        Ok(())\n                    }\n                }),\n            },\n        )?;\n    } else {\n        for child in &custom_widget_invocation.children {\n            let scope = tree.register_new_scope(\n                String::from(\"child\"),\n                Some(custom_widget_invocation.scope),\n                calling_scope,\n                HashMap::new(),\n            )?;\n            let child_widget = build_gtk_widget(tree, widget_defs.clone(), scope, child.clone(), None)?;\n            gtk_container.add(&child_widget);\n        }\n    }\n    Ok(())\n}\n\n/// When a custom widget gets used, some context about that invocation needs to be\n/// remembered whilst building it's content. If the body of the custom widget uses a `children`\n/// widget, the children originally passed to the widget need to be set.\n/// This struct represents that context\npub struct CustomWidgetInvocation {\n    /// The scope the custom widget was invoked in\n    scope: ScopeIndex,\n    /// The children the custom widget was given. These should be evaluated in [`Self::scope`]\n    children: Vec<WidgetUse>,\n}\n\n/// Make sure that [`gtk::Bin`] widgets only get a single child.\nfn validate_container_children_count(container: &gtk::Container, widget_use: &BasicWidgetUse) -> Result<(), DiagError> {\n    // ignore for overlay as it can take more than one.\n    if container.dynamic_cast_ref::<gtk::Overlay>().is_some() {\n        return Ok(());\n    }\n\n    if container.dynamic_cast_ref::<gtk::Bin>().is_some() && widget_use.children.len() > 1 {\n        Err(DiagError(gen_diagnostic! {\n            kind =  Severity::Error,\n            msg = format!(\"{} can only have one child\", widget_use.name),\n            label = widget_use.children_span() => format!(\"Was given {} children here\", widget_use.children.len())\n        }))\n    } else {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/widgets/circular_progressbar.rs",
    "content": "use anyhow::{anyhow, Result};\nuse gtk::glib::{self, object_subclass, prelude::*, wrapper, Properties};\nuse gtk::{cairo, gdk, prelude::*, subclass::prelude::*};\nuse std::cell::RefCell;\n\nuse crate::error_handling_ctx;\n\nwrapper! {\n    pub struct CircProg(ObjectSubclass<CircProgPriv>)\n    @extends gtk::Bin, gtk::Container, gtk::Widget;\n}\n\n#[derive(Properties)]\n#[properties(wrapper_type = CircProg)]\npub struct CircProgPriv {\n    #[property(get, set, nick = \"Starting at\", blurb = \"Starting at\", minimum = 0f64, maximum = 100f64, default = 0f64)]\n    start_at: RefCell<f64>,\n\n    #[property(get, set, nick = \"Value\", blurb = \"The value\", minimum = 0f64, maximum = 100f64, default = 0f64)]\n    value: RefCell<f64>,\n\n    #[property(get, set, nick = \"Thickness\", blurb = \"Thickness\", minimum = 0f64, maximum = 100f64, default = 1f64)]\n    thickness: RefCell<f64>,\n\n    #[property(get, set, nick = \"Clockwise\", blurb = \"Clockwise\", default = true)]\n    clockwise: RefCell<bool>,\n\n    content: RefCell<Option<gtk::Widget>>,\n}\n\n// This should match the default values from the ParamSpecs\nimpl Default for CircProgPriv {\n    fn default() -> Self {\n        CircProgPriv {\n            start_at: RefCell::new(0.0),\n            value: RefCell::new(0.0),\n            thickness: RefCell::new(1.0),\n            clockwise: RefCell::new(true),\n            content: RefCell::new(None),\n        }\n    }\n}\n\nimpl ObjectImpl for CircProgPriv {\n    fn properties() -> &'static [glib::ParamSpec] {\n        Self::derived_properties()\n    }\n\n    fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {\n        match pspec.name() {\n            \"value\" => {\n                self.value.replace(value.get().unwrap());\n                self.obj().queue_draw(); // Queue a draw call with the updated value\n            }\n            \"thickness\" => {\n                self.thickness.replace(value.get().unwrap());\n            }\n            \"start-at\" => {\n                self.start_at.replace(value.get().unwrap());\n            }\n            \"clockwise\" => {\n                self.clockwise.replace(value.get().unwrap());\n            }\n            x => panic!(\"Tried to set inexistant property of CircProg: {}\", x,),\n        }\n    }\n\n    fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {\n        self.derived_property(id, pspec)\n    }\n}\n\n#[object_subclass]\nimpl ObjectSubclass for CircProgPriv {\n    type ParentType = gtk::Bin;\n    type Type = CircProg;\n\n    const NAME: &'static str = \"CircProg\";\n\n    fn class_init(klass: &mut Self::Class) {\n        klass.set_css_name(\"circular-progress\");\n    }\n}\n\nimpl Default for CircProg {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl CircProg {\n    pub fn new() -> Self {\n        glib::Object::new::<Self>()\n    }\n}\n\nimpl ContainerImpl for CircProgPriv {\n    fn add(&self, widget: &gtk::Widget) {\n        if let Some(content) = &*self.content.borrow() {\n            // TODO: Handle this error when populating children widgets instead\n            error_handling_ctx::print_error(anyhow!(\"Error, trying to add multiple children to a circular-progress widget\"));\n            self.parent_remove(content);\n        }\n        self.parent_add(widget);\n        self.content.replace(Some(widget.clone()));\n    }\n}\n\nfn calc_widget_lowest_preferred_dimension(widget: &gtk::Widget) -> (i32, i32) {\n    let preferred_width = widget.preferred_width();\n    let preferred_height = widget.preferred_height();\n    let min_lowest = i32::min(preferred_width.0, preferred_height.0);\n    let natural_lowest = i32::min(preferred_width.1, preferred_height.1);\n    (min_lowest, natural_lowest)\n}\n\nimpl BinImpl for CircProgPriv {}\n\nimpl WidgetImpl for CircProgPriv {\n    // We overwrite preferred_* so that overflowing content from the children gets cropped\n    //  We return min(child_width, child_height)\n    fn preferred_width(&self) -> (i32, i32) {\n        let styles = self.obj().style_context();\n        let margin = styles.margin(gtk::StateFlags::NORMAL);\n\n        if let Some(child) = &*self.content.borrow() {\n            let (min_child, natural_child) = calc_widget_lowest_preferred_dimension(child);\n            (min_child + margin.right as i32 + margin.left as i32, natural_child + margin.right as i32 + margin.left as i32)\n        } else {\n            let empty_width = (2 * *self.thickness.borrow() as i32) + margin.right as i32 + margin.left as i32;\n            (empty_width, empty_width)\n        }\n    }\n\n    fn preferred_width_for_height(&self, _height: i32) -> (i32, i32) {\n        self.preferred_width()\n    }\n\n    fn preferred_height(&self) -> (i32, i32) {\n        let styles = self.obj().style_context();\n        let margin = styles.margin(gtk::StateFlags::NORMAL);\n\n        if let Some(child) = &*self.content.borrow() {\n            let (min_child, natural_child) = calc_widget_lowest_preferred_dimension(child);\n            (min_child + margin.bottom as i32 + margin.top as i32, natural_child + margin.bottom as i32 + margin.top as i32)\n        } else {\n            let empty_height = (2 * *self.thickness.borrow() as i32) + margin.right as i32 + margin.left as i32;\n            (empty_height, empty_height)\n        }\n    }\n\n    fn preferred_height_for_width(&self, _width: i32) -> (i32, i32) {\n        self.preferred_height()\n    }\n\n    fn draw(&self, cr: &cairo::Context) -> glib::Propagation {\n        let res: Result<()> = (|| {\n            let value = *self.value.borrow();\n            let start_at = *self.start_at.borrow();\n            let thickness = *self.thickness.borrow();\n            let clockwise = *self.clockwise.borrow();\n\n            let styles = self.obj().style_context();\n            let margin = styles.margin(gtk::StateFlags::NORMAL);\n            // Padding is not supported yet\n            let fg_color: gdk::RGBA = styles.color(gtk::StateFlags::NORMAL);\n            let bg_color: gdk::RGBA = styles.style_property_for_state(\"background-color\", gtk::StateFlags::NORMAL).get()?;\n            let (start_angle, end_angle) =\n                if clockwise { (0.0, perc_to_rad(value)) } else { (perc_to_rad(100.0 - value), 2f64 * std::f64::consts::PI) };\n\n            let total_width = self.obj().allocated_width() as f64;\n            let total_height = self.obj().allocated_height() as f64;\n            let center = (total_width / 2.0, total_height / 2.0);\n\n            let circle_width = total_width - margin.left as f64 - margin.right as f64;\n            let circle_height = total_height - margin.top as f64 - margin.bottom as f64;\n            let outer_ring = f64::min(circle_width, circle_height) / 2.0;\n            let inner_ring = (f64::min(circle_width, circle_height) / 2.0) - thickness;\n\n            cr.save()?;\n\n            // Centering\n            cr.translate(center.0, center.1);\n            cr.rotate(perc_to_rad(start_at));\n            cr.translate(-center.0, -center.1);\n\n            // Background Ring\n            cr.move_to(center.0, center.1);\n            cr.arc(center.0, center.1, outer_ring, 0.0, perc_to_rad(100.0));\n            cr.set_source_rgba(bg_color.red(), bg_color.green(), bg_color.blue(), bg_color.alpha());\n            cr.move_to(center.0, center.1);\n            cr.arc(center.0, center.1, inner_ring, 0.0, perc_to_rad(100.0));\n            cr.set_fill_rule(cairo::FillRule::EvenOdd); // Substract one circle from the other\n            cr.fill()?;\n\n            // Foreground Ring\n            cr.move_to(center.0, center.1);\n            cr.arc(center.0, center.1, outer_ring, start_angle, end_angle);\n            cr.set_source_rgba(fg_color.red(), fg_color.green(), fg_color.blue(), fg_color.alpha());\n            cr.move_to(center.0, center.1);\n            cr.arc(center.0, center.1, inner_ring, start_angle, end_angle);\n            cr.set_fill_rule(cairo::FillRule::EvenOdd); // Substract one circle from the other\n            cr.fill()?;\n            cr.restore()?;\n\n            // Draw the children widget, clipping it to the inside\n            if let Some(child) = &*self.content.borrow() {\n                cr.save()?;\n\n                // Center circular clip\n                cr.arc(center.0, center.1, inner_ring + 1.0, 0.0, perc_to_rad(100.0));\n                cr.set_source_rgba(bg_color.red(), 0.0, 0.0, bg_color.alpha());\n                cr.clip();\n\n                // Children widget\n                self.obj().propagate_draw(child, cr);\n\n                cr.reset_clip();\n                cr.restore()?;\n            }\n            Ok(())\n        })();\n\n        if let Err(error) = res {\n            error_handling_ctx::print_error(error)\n        };\n\n        glib::Propagation::Proceed\n    }\n}\n\nfn perc_to_rad(n: f64) -> f64 {\n    (n / 100f64) * 2f64 * std::f64::consts::PI\n}\n"
  },
  {
    "path": "crates/eww/src/widgets/def_widget_macro.rs",
    "content": "#[macro_export]\nmacro_rules! def_widget {\n    ($args:ident, $scope_graph:ident, $gtk_widget:ident, {\n        $(\n            prop($(\n                $attr_name:ident : $typecast_func:ident $(? $(@ $optional:tt @)?)? $(= $default:expr)?\n            ),*) $code:block\n        ),+ $(,)?\n    }) => {\n        $({\n            $(\n                // explicitly box the function to not cause tons of monomorphization related duplications of Vec::retain\n                let retain_fn: Box<dyn Fn(&eww_shared_util::wrappers::AttrName, &mut yuck::config::attributes::AttrEntry) -> bool> =\n                    Box::new(|a, _| &a.0 != &::std::stringify!($attr_name).replace('_', \"-\"));\n                $args.unhandled_attrs.retain(retain_fn);\n            )*\n\n            // Map of all attributes to their provided expressions.\n            // If an attribute is explicitly marked as optional (? appended to type)\n            // the attribute will still show up here, as a `None` value. Otherwise, all values in this map\n            // will be `Some`.\n            let attr_map: Result<HashMap<eww_shared_util::AttrName, Option<simplexpr::SimplExpr>>> = (|| {\n                Ok(::maplit::hashmap! {\n                    $(\n                        eww_shared_util::AttrName(::std::stringify!($attr_name).to_owned()) =>\n                            def_widget!(@get_value $args, &::std::stringify!($attr_name).replace('_', \"-\"), $(? $($optional)?)? $(= $default)?)\n                    ),*\n                })\n            })();\n\n            // Only proceed if any attributes from this `prop` where actually provided\n            if let Ok(attr_map) = attr_map {\n                if attr_map.values().any(|x| x.is_some()) {\n\n                    // Get all the variables that are referred to in any of the attributes expressions\n                    let required_vars: Vec<eww_shared_util::VarName> = attr_map\n                        .values()\n                        .flat_map(|expr| expr.as_ref().map(|x| x.collect_var_refs()).unwrap_or_default())\n                        .collect();\n\n                    $args.scope_graph.register_listener(\n                        $args.calling_scope,\n                            $crate::state::scope::Listener {\n                            needed_variables: required_vars,\n                            f: Box::new({\n                                // create a weak reference to the widget, such that this listener doesn't prevent the actual widget from\n                                // getting deallocated (garbage collected by the gtk runtime)\n                                let $gtk_widget = gdk::glib::clone::Downgrade::downgrade(&$gtk_widget);\n                                move |$scope_graph, values| {\n                                    // TODO when this fails, shouldn't we technically remove the listener somehow? Need to analyze when exactly this happens.\n                                    let $gtk_widget = gdk::glib::clone::Upgrade::upgrade(&$gtk_widget)\n                                        .context(\"Couldn't upgrade reference, widget got deallocated\")?;\n                                    // values is a map of all the variables that are required to evaluate the\n                                    // attributes expression.\n\n                                    // allow $gtk_widget to never be used, by creating a reference that gets immediately discarded\n                                    {let _ = &$gtk_widget;};\n\n                                    // We first initialize all the local variables for all the expected attributes in scope\n                                    $(\n                                        // get the simplexprs from the attr_map\n                                        let $attr_name = attr_map.get(::std::stringify!($attr_name))\n                                            .context(\"Missing attribute, this should never happen\")?;\n\n                                        // if the value is Some, evaluate and typecast it as expected\n                                        let $attr_name = if let Some(x) = $attr_name {\n                                            Some(x.eval(&values)?.$typecast_func()?)\n                                        } else {\n                                            None\n                                        };\n                                        // If the attribute is optional, keep it as Option<T>, otherwise unwrap\n                                        // because we _know_ the value in the attr_map is Some if the attribute is not optional.\n                                        def_widget!(@unwrap_if_required $attr_name $(? $($optional)?)?);\n                                    )*\n\n                                    // And then run the provided code with those attributes in scope.\n                                    $code\n                                    Ok(())\n                                }\n                            }),\n                        },\n                    )?;\n                }\n            }\n        })+\n    };\n\n    (@unwrap_if_required $value:ident ?) => { };\n    (@unwrap_if_required $value:ident) => {\n        let $value = $value.unwrap();\n    };\n\n    // The attribute is explicitly marked as optional - the value should be provided to the prop function body as Option<T>\n    (@get_value $args:ident, $name:expr, ?) => {\n        $args.widget_use.attrs.ast_optional::<simplexpr::SimplExpr>($name)?.clone()\n    };\n\n    // The attribute has a default value\n    (@get_value $args:ident, $name:expr, = $default:expr) => {\n        Some($args.widget_use.attrs.ast_optional::<simplexpr::SimplExpr>($name)?.clone().unwrap_or_else(|| simplexpr::SimplExpr::synth_literal($default)))\n    };\n\n    // The attribute is required - the prop will only be ran if this attribute is actually provided.\n    (@get_value $args:ident, $name:expr,) => {\n        Some($args.widget_use.attrs.ast_required::<simplexpr::SimplExpr>($name)?.clone())\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/widgets/graph.rs",
    "content": "use std::{cell::RefCell, collections::VecDeque};\n// https://www.figuiere.net/technotes/notes/tn002/\n// https://github.com/gtk-rs/examples/blob/master/src/bin/listbox_model.rs\nuse anyhow::{anyhow, Result};\nuse gtk::glib::{self, object_subclass, wrapper, Properties};\nuse gtk::{cairo, gdk, prelude::*, subclass::prelude::*};\n\nuse crate::error_handling_ctx;\n\n// This widget shouldn't be a Bin/Container but I've not been\n//  able to subclass just a gtk::Widget\nwrapper! {\n    pub struct Graph(ObjectSubclass<GraphPriv>)\n    @extends gtk::Bin, gtk::Container, gtk::Widget;\n}\n\n#[derive(Properties)]\n#[properties(wrapper_type = Graph)]\npub struct GraphPriv {\n    #[property(get, set, nick = \"Value\", blurb = \"The value\", minimum = 0f64, maximum = f64::MAX, default = 0f64)]\n    value: RefCell<f64>,\n\n    #[property(get, set, nick = \"Thickness\", blurb = \"The Thickness\", minimum = 0f64, maximum = f64::MAX, default = 1f64)]\n    thickness: RefCell<f64>,\n\n    #[property(get, set, nick = \"Line Style\", blurb = \"The Line Style\", default = \"miter\")]\n    line_style: RefCell<String>,\n\n    #[property(get, set, nick = \"Maximum Value\", blurb = \"The Maximum Value\", minimum = 0f64, maximum = f64::MAX, default = 100f64)]\n    min: RefCell<f64>,\n\n    #[property(get, set, nick = \"Minumum Value\", blurb = \"The Minimum Value\", minimum = 0f64, maximum = f64::MAX, default = 0f64)]\n    max: RefCell<f64>,\n\n    #[property(get, set, nick = \"Dynamic\", blurb = \"If it is dynamic\", default = true)]\n    dynamic: RefCell<bool>,\n\n    #[property(get, set, nick = \"Time Range\", blurb = \"The Time Range\", minimum = 0u64, maximum = u64::MAX, default = 10u64)]\n    time_range: RefCell<u64>,\n\n    #[property(get, set, nick = \"Flip X\", blurb = \"Flip the x axis\", default = true)]\n    flip_x: RefCell<bool>,\n    #[property(get, set, nick = \"Flip Y\", blurb = \"Flip the y axis\", default = true)]\n    flip_y: RefCell<bool>,\n    #[property(get, set, nick = \"Vertical\", blurb = \"Exchange the x and y axes\", default = false)]\n    vertical: RefCell<bool>,\n\n    history: RefCell<VecDeque<(std::time::Instant, f64)>>,\n    extra_point: RefCell<Option<(std::time::Instant, f64)>>,\n    last_updated_at: RefCell<std::time::Instant>,\n}\n\nimpl Default for GraphPriv {\n    fn default() -> Self {\n        Self {\n            value: RefCell::new(0.0),\n            thickness: RefCell::new(1.0),\n            line_style: RefCell::new(\"miter\".to_string()),\n            min: RefCell::new(0.0),\n            max: RefCell::new(100.0),\n            dynamic: RefCell::new(true),\n            time_range: RefCell::new(10),\n            flip_x: RefCell::new(true),\n            flip_y: RefCell::new(true),\n            vertical: RefCell::new(false),\n            history: RefCell::new(VecDeque::new()),\n            extra_point: RefCell::new(None),\n            last_updated_at: RefCell::new(std::time::Instant::now()),\n        }\n    }\n}\n\nimpl GraphPriv {\n    // Updates the history, removing points ouside the range\n    fn update_history(&self, v: (std::time::Instant, f64)) {\n        let mut history = self.history.borrow_mut();\n        let mut last_value = self.extra_point.borrow_mut();\n        let mut last_updated_at = self.last_updated_at.borrow_mut();\n        *last_updated_at = std::time::Instant::now();\n\n        while let Some(entry) = history.front() {\n            if last_updated_at.duration_since(entry.0).as_millis() as u64 > *self.time_range.borrow() {\n                *last_value = history.pop_front();\n            } else {\n                break;\n            }\n        }\n        history.push_back(v);\n    }\n    /**\n     * Receives normalized (0-1) coordinates `x` and `y` and convert them to the\n     * point on the widget.\n     */\n    fn value_to_point(&self, width: f64, height: f64, x: f64, y: f64) -> (f64, f64) {\n        let x = if *self.flip_x.borrow() { 1.0 - x } else { x };\n        let y = if *self.flip_y.borrow() { 1.0 - y } else { y };\n        let (x, y) = if *self.vertical.borrow() { (y, x) } else { (x, y) };\n        (width * x, height * y)\n    }\n}\n\nimpl ObjectImpl for GraphPriv {\n    fn properties() -> &'static [glib::ParamSpec] {\n        Self::derived_properties()\n    }\n\n    fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {\n        match pspec.name() {\n            \"value\" => {\n                let value = value.get().unwrap();\n                self.value.replace(value);\n                self.update_history((std::time::Instant::now(), value));\n                self.obj().queue_draw();\n            }\n            \"thickness\" => {\n                self.thickness.replace(value.get().unwrap());\n            }\n            \"max\" => {\n                self.max.replace(value.get().unwrap());\n            }\n            \"min\" => {\n                self.min.replace(value.get().unwrap());\n            }\n            \"dynamic\" => {\n                self.dynamic.replace(value.get().unwrap());\n            }\n            \"time-range\" => {\n                self.time_range.replace(value.get().unwrap());\n            }\n            \"line-style\" => {\n                self.line_style.replace(value.get().unwrap());\n            }\n            \"flip-x\" => {\n                self.flip_x.replace(value.get().unwrap());\n            }\n            \"flip-y\" => {\n                self.flip_y.replace(value.get().unwrap());\n            }\n            \"vertical\" => {\n                self.vertical.replace(value.get().unwrap());\n            }\n            x => panic!(\"Tried to set inexistant property of Graph: {}\", x,),\n        }\n    }\n\n    fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {\n        self.derived_property(id, pspec)\n    }\n}\n\n#[object_subclass]\nimpl ObjectSubclass for GraphPriv {\n    type ParentType = gtk::Bin;\n    type Type = Graph;\n\n    const NAME: &'static str = \"Graph\";\n\n    fn class_init(klass: &mut Self::Class) {\n        klass.set_css_name(\"graph\");\n    }\n}\n\nimpl Default for Graph {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Graph {\n    pub fn new() -> Self {\n        glib::Object::new::<Self>()\n    }\n}\n\nimpl ContainerImpl for GraphPriv {\n    fn add(&self, _widget: &gtk::Widget) {\n        error_handling_ctx::print_error(anyhow!(\"Error, Graph widget shoudln't have any children\"));\n    }\n}\n\nimpl BinImpl for GraphPriv {}\nimpl WidgetImpl for GraphPriv {\n    fn preferred_width(&self) -> (i32, i32) {\n        let thickness = *self.thickness.borrow() as i32;\n        (thickness, thickness)\n    }\n\n    fn preferred_width_for_height(&self, height: i32) -> (i32, i32) {\n        (height, height)\n    }\n\n    fn preferred_height(&self) -> (i32, i32) {\n        let thickness = *self.thickness.borrow() as i32;\n        (thickness, thickness)\n    }\n\n    fn preferred_height_for_width(&self, width: i32) -> (i32, i32) {\n        (width, width)\n    }\n\n    fn draw(&self, cr: &cairo::Context) -> glib::Propagation {\n        let res: Result<()> = (|| {\n            let history = &*self.history.borrow();\n            let extra_point = *self.extra_point.borrow();\n\n            // Calculate the max value\n            let (min, max) = {\n                let mut max = *self.max.borrow();\n                let min = *self.min.borrow();\n                let dynamic = *self.dynamic.borrow();\n                if dynamic {\n                    // Check for points higher than max\n                    for (_, value) in history {\n                        if *value > max {\n                            max = *value;\n                        }\n                    }\n                    if let Some((_, value)) = extra_point {\n                        if value > max {\n                            max = value;\n                        }\n                    }\n                }\n                (min, max)\n            };\n\n            let styles = self.obj().style_context();\n            let (margin_top, margin_right, margin_bottom, margin_left) = {\n                let margin = styles.margin(gtk::StateFlags::NORMAL);\n                (margin.top as f64, margin.right as f64, margin.bottom as f64, margin.left as f64)\n            };\n            let width = self.obj().allocated_width() as f64 - margin_left - margin_right;\n            let height = self.obj().allocated_height() as f64 - margin_top - margin_bottom;\n\n            // Calculate graph points once\n            //  Separating this into another function would require pasing a\n            //  GraphPriv that would hide interior mutability\n            let points = {\n                let value_range = max - min;\n                let time_range = *self.time_range.borrow() as f64;\n                let last_updated_at = self.last_updated_at.borrow();\n                let mut points = history\n                    .iter()\n                    .map(|(instant, value)| {\n                        let t = last_updated_at.duration_since(*instant).as_millis() as f64;\n                        self.value_to_point(width, height, t / time_range, (value - min) / value_range)\n                    })\n                    .collect::<VecDeque<(f64, f64)>>();\n\n                // Aad an extra point outside of the graph to extend the line to the left\n                if let Some((instant, value)) = extra_point {\n                    let t = last_updated_at.duration_since(instant).as_millis() as f64;\n                    let (x, y) = self.value_to_point(width, height, (t - time_range) / time_range, (value - min) / value_range);\n                    points.push_front(if *self.vertical.borrow() { (x, -y) } else { (-x, y) });\n                }\n                points\n            };\n\n            // Actually draw the graph\n            cr.save()?;\n            cr.translate(margin_left, margin_top);\n            cr.rectangle(0.0, 0.0, width, height);\n            cr.clip();\n\n            // Draw Background\n            let bg_color: gdk::RGBA = styles.style_property_for_state(\"background-color\", gtk::StateFlags::NORMAL).get()?;\n            if bg_color.alpha() > 0.0 {\n                if let Some(first_point) = points.front() {\n                    cr.line_to(first_point.0, height + margin_bottom);\n                }\n                for (x, y) in points.iter() {\n                    cr.line_to(*x, *y);\n                }\n                cr.line_to(width, height);\n\n                cr.set_source_rgba(bg_color.red(), bg_color.green(), bg_color.blue(), bg_color.alpha());\n                cr.fill()?;\n            }\n\n            // Draw Line\n            let line_color: gdk::RGBA = styles.color(gtk::StateFlags::NORMAL);\n            let thickness = *self.thickness.borrow();\n            if line_color.alpha() > 0.0 && thickness > 0.0 {\n                for (x, y) in points.iter() {\n                    cr.line_to(*x, *y);\n                }\n\n                let line_style = &*self.line_style.borrow();\n                apply_line_style(line_style.as_str(), cr)?;\n                cr.set_line_width(thickness);\n                cr.set_source_rgba(line_color.red(), line_color.green(), line_color.blue(), line_color.alpha());\n                cr.stroke()?;\n            }\n\n            cr.reset_clip();\n            cr.restore()?;\n            Ok(())\n        })();\n\n        if let Err(error) = res {\n            error_handling_ctx::print_error(error)\n        };\n\n        glib::Propagation::Proceed\n    }\n}\n\nfn apply_line_style(style: &str, cr: &cairo::Context) -> Result<()> {\n    match style {\n        \"miter\" => {\n            cr.set_line_cap(cairo::LineCap::Butt);\n            cr.set_line_join(cairo::LineJoin::Miter);\n        }\n        \"bevel\" => {\n            cr.set_line_cap(cairo::LineCap::Square);\n            cr.set_line_join(cairo::LineJoin::Bevel);\n        }\n        \"round\" => {\n            cr.set_line_cap(cairo::LineCap::Round);\n            cr.set_line_join(cairo::LineJoin::Round);\n        }\n        _ => Err(anyhow!(\"Error, the value: {} for atribute join is not valid\", style))?,\n    };\n    Ok(())\n}\n"
  },
  {
    "path": "crates/eww/src/widgets/mod.rs",
    "content": "use std::process::Command;\n\npub mod build_widget;\npub mod circular_progressbar;\npub mod def_widget_macro;\npub mod graph;\nmod systray;\npub mod transform;\npub mod widget_definitions;\npub mod window;\n\n/// Run a command that was provided as an attribute.\n/// This command may use placeholders which will be replaced by the values of the arguments given.\n/// This can either be the placeholder `{}`, which will be replaced by the first argument,\n/// Or a placeholder like `{0}`, `{1}`, etc, which will refer to the respective argument.\nfn run_command<T>(timeout: std::time::Duration, cmd: &str, args: &[T])\nwhere\n    T: 'static + std::fmt::Display + Send + Sync + Clone,\n{\n    use wait_timeout::ChildExt;\n    let cmd = replace_placeholders(cmd, args);\n    std::thread::Builder::new()\n        .name(\"command-execution-thread\".to_string())\n        .spawn(move || {\n            log::debug!(\"Running command from widget [timeout: {}ms]: {}\", timeout.as_millis(), cmd);\n            let child = Command::new(\"/bin/sh\").arg(\"-c\").arg(&cmd).spawn();\n            match child {\n                Ok(mut child) => match child.wait_timeout(timeout) {\n                    // child timed out\n                    Ok(None) => {\n                        log::error!(\"WARNING: command {} timed out\", &cmd);\n                        let _ = child.kill();\n                        let _ = child.wait();\n                    }\n                    Err(err) => log::error!(\"Failed to execute command {}: {}\", cmd, err),\n                    Ok(Some(_)) => {}\n                },\n                Err(err) => log::error!(\"Failed to launch child process: {}\", err),\n            }\n        })\n        .expect(\"Failed to start command-execution-thread\");\n}\n\nfn replace_placeholders<T>(cmd: &str, args: &[T]) -> String\nwhere\n    T: 'static + std::fmt::Display + Send + Sync + Clone,\n{\n    if !args.is_empty() {\n        let cmd = cmd.replace(\"{}\", &format!(\"{}\", args[0]));\n        args.iter().enumerate().fold(cmd, |acc, (i, arg)| acc.replace(&format!(\"{{{}}}\", i), &format!(\"{}\", arg)))\n    } else {\n        cmd.to_string()\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    #[test]\n    fn test_replace_placeholders() {\n        assert_eq!(\"foo\", replace_placeholders(\"foo\", &[\"\"]),);\n        assert_eq!(\"foo hi\", replace_placeholders(\"foo {}\", &[\"hi\"]),);\n        assert_eq!(\"foo hi\", replace_placeholders(\"foo {}\", &[\"hi\", \"ho\"]),);\n        assert_eq!(\"bar foo baz\", replace_placeholders(\"{0} foo {1}\", &[\"bar\", \"baz\"]),);\n        assert_eq!(\"baz foo bar\", replace_placeholders(\"{1} foo {0}\", &[\"bar\", \"baz\"]),);\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/widgets/systray.rs",
    "content": "use crate::widgets::window::Window;\nuse futures::StreamExt;\nuse gtk::{\n    cairo::Surface,\n    gdk::{self, ffi::gdk_cairo_surface_create_from_pixbuf, NotifyType},\n    glib,\n    prelude::*,\n};\nuse std::{cell::RefCell, future::Future, rc::Rc};\n\n// DBus state shared between systray instances, to avoid creating too many connections etc.\nstruct DBusSession {\n    snw: notifier_host::proxy::StatusNotifierWatcherProxy<'static>,\n}\n\nasync fn dbus_session() -> zbus::Result<&'static DBusSession> {\n    // TODO make DBusSession reference counted so it's dropped when not in use?\n\n    static DBUS_STATE: tokio::sync::OnceCell<DBusSession> = tokio::sync::OnceCell::const_new();\n    DBUS_STATE\n        .get_or_try_init(|| async {\n            let con = zbus::Connection::session().await?;\n            notifier_host::Watcher::new().attach_to(&con).await?;\n\n            let (_, snw) = notifier_host::register_as_host(&con).await?;\n\n            Ok(DBusSession { snw })\n        })\n        .await\n}\n\nfn run_async_task<F: Future>(f: F) -> F::Output {\n    let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().expect(\"Failed to initialize tokio runtime\");\n    rt.block_on(f)\n}\n\npub struct Props {\n    icon_size_tx: tokio::sync::watch::Sender<i32>,\n    pub prepend_new: Rc<RefCell<bool>>,\n}\n\nimpl Props {\n    pub fn new() -> Self {\n        let (icon_size_tx, _) = tokio::sync::watch::channel(24);\n        Self { icon_size_tx, prepend_new: Rc::new(RefCell::new(false)) }\n    }\n\n    pub fn icon_size(&self, value: i32) {\n        let _ = self.icon_size_tx.send_if_modified(|x| {\n            if *x == value {\n                false\n            } else {\n                *x = value;\n                true\n            }\n        });\n    }\n}\n\nstruct Tray {\n    container: gtk::Box,\n    items: std::collections::HashMap<String, Item>,\n\n    icon_size: tokio::sync::watch::Receiver<i32>,\n    prepend_new: Rc<RefCell<bool>>,\n}\n\npub fn spawn_systray(container: &gtk::Box, props: &Props) {\n    let mut systray = Tray {\n        container: container.clone(),\n        items: Default::default(),\n        icon_size: props.icon_size_tx.subscribe(),\n        prepend_new: props.prepend_new.clone(),\n    };\n\n    let task = glib::MainContext::default().spawn_local(async move {\n        let s = match dbus_session().await {\n            Ok(x) => x,\n            Err(e) => {\n                log::error!(\"could not initialise dbus connection for tray: {}\", e);\n                return;\n            }\n        };\n\n        systray.container.show();\n        let e = notifier_host::run_host(&mut systray, &s.snw).await;\n        log::error!(\"notifier host error: {}\", e);\n    });\n\n    // stop the task when the widget is dropped\n    container.connect_destroy(move |_| {\n        task.abort();\n    });\n}\n\nimpl notifier_host::Host for Tray {\n    fn add_item(&mut self, id: &str, item: notifier_host::Item) {\n        let item = Item::new(id.to_owned(), item, self.icon_size.clone());\n        if *self.prepend_new.borrow() {\n            self.container.pack_end(&item.widget, true, true, 0);\n        } else {\n            self.container.pack_start(&item.widget, true, true, 0);\n        }\n        if let Some(old_item) = self.items.insert(id.to_string(), item) {\n            self.container.remove(&old_item.widget);\n        }\n    }\n\n    fn remove_item(&mut self, id: &str) {\n        if let Some(item) = self.items.get(id) {\n            self.container.remove(&item.widget);\n            self.items.remove(id);\n        } else {\n            log::warn!(\"Tried to remove nonexistent item {:?} from systray\", id);\n        }\n    }\n}\n\n/// Item represents a single icon being shown in the system tray.\nstruct Item {\n    /// Main widget representing this tray item.\n    widget: gtk::EventBox,\n\n    /// Async task to stop when this item gets removed.\n    task: Option<glib::JoinHandle<()>>,\n}\n\nimpl Drop for Item {\n    fn drop(&mut self) {\n        if let Some(task) = &self.task {\n            task.abort();\n        }\n    }\n}\n\nimpl Item {\n    fn new(id: String, item: notifier_host::Item, icon_size: tokio::sync::watch::Receiver<i32>) -> Self {\n        let gtk_widget = gtk::EventBox::new();\n\n        // Support :hover selector\n        gtk_widget.connect_enter_notify_event(|gtk_widget, evt| {\n            if evt.detail() != NotifyType::Inferior {\n                gtk_widget.clone().set_state_flags(gtk::StateFlags::PRELIGHT, false);\n            }\n            glib::Propagation::Proceed\n        });\n\n        gtk_widget.connect_leave_notify_event(|gtk_widget, evt| {\n            if evt.detail() != NotifyType::Inferior {\n                gtk_widget.clone().unset_state_flags(gtk::StateFlags::PRELIGHT);\n            }\n            glib::Propagation::Proceed\n        });\n\n        let out_widget = gtk_widget.clone(); // copy so we can return it\n\n        let task = glib::MainContext::default().spawn_local(async move {\n            if let Err(e) = Item::maintain(gtk_widget.clone(), item, icon_size).await {\n                log::error!(\"error for systray item {}: {}\", id, e);\n            }\n        });\n\n        Self { widget: out_widget, task: Some(task) }\n    }\n\n    async fn maintain(\n        widget: gtk::EventBox,\n        mut item: notifier_host::Item,\n        mut icon_size: tokio::sync::watch::Receiver<i32>,\n    ) -> zbus::Result<()> {\n        // init icon\n        let icon = gtk::Image::new();\n        widget.add(&icon);\n        icon.show();\n\n        // init menu\n        if let Err(e) = item.set_menu(&widget).await {\n            log::warn!(\"failed to set menu: {}\", e);\n        }\n\n        // TODO this is a lot of code duplication unfortunately, i'm not really sure how to\n        // refactor without making the borrow checker angry\n\n        // set status\n        match item.status().await? {\n            notifier_host::Status::Passive => widget.hide(),\n            notifier_host::Status::Active | notifier_host::Status::NeedsAttention => widget.show(),\n        }\n\n        // set title\n        widget.set_tooltip_text(Some(&item.sni.title().await?));\n\n        // set icon\n        let scale = icon.scale_factor();\n        load_icon_for_item(&icon, &item, *icon_size.borrow_and_update(), scale).await;\n\n        let item = Rc::new(item);\n        let window =\n            widget.toplevel().expect(\"Failed to obtain toplevel window\").downcast::<Window>().expect(\"Failed to downcast window\");\n        widget.add_events(gdk::EventMask::BUTTON_PRESS_MASK);\n        widget.connect_button_press_event(glib::clone!(@strong item => move |_, evt| {\n            let (x, y) = (evt.root().0 as i32 + window.x(), evt.root().1 as i32 + window.y());\n            let item_is_menu = run_async_task(async { item.sni.item_is_menu().await });\n            let have_item_is_menu = item_is_menu.is_ok();\n            let item_is_menu = item_is_menu.unwrap_or(false);\n            log::debug!(\n                \"mouse click button={}, x={}, y={}, have_item_is_menu={}, item_is_menu={}\",\n                evt.button(),\n                x,\n                y,\n                have_item_is_menu,\n                item_is_menu\n            );\n\n            let result = match (evt.button(), item_is_menu) {\n                (gdk::BUTTON_PRIMARY, false) => {\n                    let result = run_async_task(async { item.sni.activate(x, y).await });\n                    if result.is_err() && !have_item_is_menu {\n                        log::debug!(\"fallback to context menu due to: {}\", result.unwrap_err());\n                        // Some applications are in fact menu-only (don't have Activate method)\n                        // but don't report so through ItemIsMenu property. Fallback to menu if\n                        // activate failed in this case.\n                        run_async_task(async { item.popup_menu( evt, x, y).await })\n                    } else {\n                        result\n                    }\n                }\n                (gdk::BUTTON_MIDDLE, _) => run_async_task(async { item.sni.secondary_activate(x, y).await }),\n                (gdk::BUTTON_SECONDARY, _) | (gdk::BUTTON_PRIMARY, true) => {\n                    run_async_task(async { item.popup_menu( evt, x, y).await })\n                }\n                _ => Err(zbus::Error::Failure(format!(\"unknown button {}\", evt.button()))),\n            };\n            if let Err(result) = result {\n                log::error!(\"failed to handle mouse click {}: {}\", evt.button(), result);\n            }\n            glib::Propagation::Stop\n        }));\n\n        // updates\n        let mut status_updates = item.sni.receive_new_status().await?;\n        let mut title_updates = item.sni.receive_new_title().await?;\n        let mut icon_updates = item.sni.receive_new_icon().await?;\n\n        loop {\n            tokio::select! {\n                Some(_) = status_updates.next() => {\n                    // set status\n                    match item.status().await? {\n                        notifier_host::Status::Passive => widget.hide(),\n                        notifier_host::Status::Active | notifier_host::Status::NeedsAttention => widget.show(),\n                    }\n                }\n                Ok(_) = icon_size.changed() => {\n                    // set icon\n                    load_icon_for_item(&icon, &item, *icon_size.borrow_and_update(), scale).await;\n                }\n                Some(_) = title_updates.next() => {\n                    // set title\n                    widget.set_tooltip_text(Some(&item.sni.title().await?));\n                }\n                Some(_) = icon_updates.next() => {\n                    // set icon\n                    load_icon_for_item(&icon, &item, *icon_size.borrow_and_update(), scale).await;\n                }\n            }\n        }\n    }\n}\n\nasync fn load_icon_for_item(icon: &gtk::Image, item: &notifier_host::Item, size: i32, scale: i32) {\n    if let Some(pixbuf) = item.icon(size, scale).await {\n        let surface = unsafe {\n            // gtk::cairo::Surface will destroy the underlying surface on drop\n            let ptr = gdk_cairo_surface_create_from_pixbuf(\n                pixbuf.as_ptr(),\n                scale,\n                icon.window().map_or(std::ptr::null_mut(), |v| v.as_ptr()),\n            );\n            Surface::from_raw_full(ptr)\n        };\n        icon.set_from_surface(surface.ok().as_ref());\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/widgets/transform.rs",
    "content": "use anyhow::{anyhow, Result};\nuse gtk::glib::{self, object_subclass, wrapper, Properties};\nuse gtk::{prelude::*, subclass::prelude::*};\nuse std::{cell::RefCell, str::FromStr};\nuse yuck::value::NumWithUnit;\n\nuse crate::error_handling_ctx;\n\nwrapper! {\n    pub struct Transform(ObjectSubclass<TransformPriv>)\n    @extends gtk::Bin, gtk::Container, gtk::Widget;\n}\n\n#[derive(Properties)]\n#[properties(wrapper_type = Transform)]\npub struct TransformPriv {\n    #[property(get, set, nick = \"Rotate\", blurb = \"The Rotation\", minimum = f64::MIN, maximum = f64::MAX, default = 0f64)]\n    rotate: RefCell<f64>,\n\n    #[property(get, set, nick = \"Transform-Origin X\", blurb = \"X coordinate (%/px) for the Transform-Origin\", default = None)]\n    transform_origin_x: RefCell<Option<String>>,\n\n    #[property(get, set, nick = \"Transform-Origin Y\", blurb = \"Y coordinate (%/px) for the Transform-Origin\", default = None)]\n    transform_origin_y: RefCell<Option<String>>,\n\n    #[property(get, set, nick = \"Translate x\", blurb = \"The X Translation\", default = None)]\n    translate_x: RefCell<Option<String>>,\n\n    #[property(get, set, nick = \"Translate y\", blurb = \"The Y Translation\", default = None)]\n    translate_y: RefCell<Option<String>>,\n\n    #[property(get, set, nick = \"Scale x\", blurb = \"The amount to scale in x\", default = None)]\n    scale_x: RefCell<Option<String>>,\n\n    #[property(get, set, nick = \"Scale y\", blurb = \"The amount to scale in y\", default = None)]\n    scale_y: RefCell<Option<String>>,\n\n    content: RefCell<Option<gtk::Widget>>,\n}\n\n// This should match the default values from the ParamSpecs\nimpl Default for TransformPriv {\n    fn default() -> Self {\n        TransformPriv {\n            rotate: RefCell::new(0.0),\n            transform_origin_x: RefCell::new(None),\n            transform_origin_y: RefCell::new(None),\n            translate_x: RefCell::new(None),\n            translate_y: RefCell::new(None),\n            scale_x: RefCell::new(None),\n            scale_y: RefCell::new(None),\n            content: RefCell::new(None),\n        }\n    }\n}\n\nimpl ObjectImpl for TransformPriv {\n    fn properties() -> &'static [glib::ParamSpec] {\n        Self::derived_properties()\n    }\n\n    fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {\n        match pspec.name() {\n            \"rotate\" => {\n                self.rotate.replace(value.get().unwrap());\n                self.obj().queue_draw(); // Queue a draw call with the updated value\n            }\n            \"transform-origin-x\" => {\n                self.transform_origin_x.replace(value.get().unwrap());\n                self.obj().queue_draw(); // Queue a draw call with the updated value\n            }\n            \"transform-origin-y\" => {\n                self.transform_origin_y.replace(value.get().unwrap());\n                self.obj().queue_draw(); // Queue a draw call with the updated value\n            }\n            \"translate-x\" => {\n                self.translate_x.replace(value.get().unwrap());\n                self.obj().queue_draw(); // Queue a draw call with the updated value\n            }\n            \"translate-y\" => {\n                self.translate_y.replace(value.get().unwrap());\n                self.obj().queue_draw(); // Queue a draw call with the updated value\n            }\n            \"scale-x\" => {\n                self.scale_x.replace(value.get().unwrap());\n                self.obj().queue_draw(); // Queue a draw call with the updated value\n            }\n            \"scale-y\" => {\n                self.scale_y.replace(value.get().unwrap());\n                self.obj().queue_draw(); // Queue a draw call with the updated value\n            }\n            x => panic!(\"Tried to set inexistant property of Transform: {}\", x,),\n        }\n    }\n\n    fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {\n        self.derived_property(id, pspec)\n    }\n}\n\n#[object_subclass]\nimpl ObjectSubclass for TransformPriv {\n    type ParentType = gtk::Bin;\n    type Type = Transform;\n\n    const NAME: &'static str = \"Transform\";\n\n    fn class_init(klass: &mut Self::Class) {\n        klass.set_css_name(\"transform\");\n    }\n}\n\nimpl Default for Transform {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Transform {\n    pub fn new() -> Self {\n        glib::Object::new::<Self>()\n    }\n}\n\nimpl ContainerImpl for TransformPriv {\n    fn add(&self, widget: &gtk::Widget) {\n        if let Some(content) = &*self.content.borrow() {\n            // TODO: Handle this error when populating children widgets instead\n            error_handling_ctx::print_error(anyhow!(\"Error, trying to add multiple children to a circular-progress widget\"));\n            self.parent_remove(content);\n        }\n        self.parent_add(widget);\n        self.content.replace(Some(widget.clone()));\n    }\n}\n\nimpl BinImpl for TransformPriv {}\nimpl WidgetImpl for TransformPriv {\n    fn draw(&self, cr: &gtk::cairo::Context) -> glib::Propagation {\n        let res: Result<()> = (|| {\n            let rotate = *self.rotate.borrow();\n            let total_width = self.obj().allocated_width() as f64;\n            let total_height = self.obj().allocated_height() as f64;\n\n            cr.save()?;\n\n            let transform_origin_x = match &*self.transform_origin_x.borrow() {\n                Some(rcx) => NumWithUnit::from_str(rcx)?.pixels_relative_to(total_width as i32) as f64,\n                None => 0.0,\n            };\n            let transform_origin_y = match &*self.transform_origin_y.borrow() {\n                Some(rcy) => NumWithUnit::from_str(rcy)?.pixels_relative_to(total_height as i32) as f64,\n                None => 0.0,\n            };\n\n            let translate_x = match &*self.translate_x.borrow() {\n                Some(tx) => NumWithUnit::from_str(tx)?.pixels_relative_to(total_width as i32) as f64,\n                None => 0.0,\n            };\n\n            let translate_y = match &*self.translate_y.borrow() {\n                Some(ty) => NumWithUnit::from_str(ty)?.pixels_relative_to(total_height as i32) as f64,\n                None => 0.0,\n            };\n\n            let scale_x = match &*self.scale_x.borrow() {\n                Some(sx) => NumWithUnit::from_str(sx)?.perc_relative_to(total_width as i32) as f64 / 100.0,\n                None => 1.0,\n            };\n\n            let scale_y = match &*self.scale_y.borrow() {\n                Some(sy) => NumWithUnit::from_str(sy)?.perc_relative_to(total_height as i32) as f64 / 100.0,\n                None => 1.0,\n            };\n\n            cr.translate(transform_origin_x, transform_origin_y);\n            cr.rotate(perc_to_rad(rotate));\n            cr.translate(translate_x - transform_origin_x, translate_y - transform_origin_y);\n            cr.scale(scale_x, scale_y);\n\n            // Children widget\n            if let Some(child) = &*self.content.borrow() {\n                self.obj().propagate_draw(child, cr);\n            }\n\n            cr.restore()?;\n            Ok(())\n        })();\n\n        if let Err(error) = res {\n            error_handling_ctx::print_error(error)\n        };\n\n        glib::Propagation::Proceed\n    }\n}\n\nfn perc_to_rad(n: f64) -> f64 {\n    (n / 100f64) * 2f64 * std::f64::consts::PI\n}\n"
  },
  {
    "path": "crates/eww/src/widgets/widget_definitions.rs",
    "content": "#![allow(clippy::option_map_unit_fn)]\nuse super::{build_widget::BuilderArgs, circular_progressbar::*, run_command, transform::*};\nuse crate::{\n    def_widget, enum_parse, error_handling_ctx,\n    util::{self, list_difference},\n    widgets::{build_widget::build_gtk_widget, systray},\n};\nuse anyhow::{anyhow, Context, Result};\nuse codespan_reporting::diagnostic::Severity;\nuse eww_shared_util::Spanned;\n\nuse gdk::{ModifierType, NotifyType};\nuse glib::translate::FromGlib;\nuse gtk::{self, glib, prelude::*, DestDefaults, TargetEntry, TargetList};\nuse gtk::{gdk, pango};\nuse itertools::Itertools;\nuse once_cell::sync::Lazy;\n\nuse std::{\n    cell::RefCell,\n    cmp::Ordering,\n    collections::{HashMap, HashSet},\n    rc::Rc,\n    time::Duration,\n};\nuse yuck::{\n    config::file_provider::YuckFileProvider,\n    error::{DiagError, DiagResult},\n    format_diagnostic::{span_to_secondary_label, DiagnosticExt},\n    gen_diagnostic,\n    parser::from_ast::FromAst,\n};\n\n/// Connect a gtk signal handler inside of this macro to ensure that when the same code gets run multiple times,\n/// the previously connected singal handler first gets disconnected.\n/// Can take an optional condition.\n/// If the condition is false, we disconnect the handler without running the connect_expr,\n/// thus not connecting a new handler unless the condition is met.\nmacro_rules! connect_signal_handler {\n    ($widget:ident, if $cond:expr, $connect_expr:expr) => {{\n        const KEY:&str = std::concat!(\"signal-handler:\", std::line!());\n        unsafe {\n            let old = $widget.data::<gtk::glib::SignalHandlerId>(KEY);\n\n            if let Some(old) = old {\n                 let a = old.as_ref().as_raw();\n                 $widget.disconnect(gtk::glib::SignalHandlerId::from_glib(a));\n            }\n\n            $widget.set_data::<gtk::glib::SignalHandlerId>(KEY, $connect_expr);\n        }\n    }};\n    ($widget:ident, $connect_expr:expr) => {{\n        connect_signal_handler!($widget, if true, $connect_expr)\n    }};\n}\n\n// TODO figure out how to\n// TODO https://developer.gnome.org/gtk3/stable/GtkFixed.html\n\npub const BUILTIN_WIDGET_NAMES: &[&str] = &[\n    WIDGET_NAME_BOX,\n    WIDGET_NAME_CENTERBOX,\n    WIDGET_NAME_EVENTBOX,\n    WIDGET_NAME_TOOLTIP,\n    WIDGET_NAME_CIRCULAR_PROGRESS,\n    WIDGET_NAME_GRAPH,\n    WIDGET_NAME_TRANSFORM,\n    WIDGET_NAME_SCALE,\n    WIDGET_NAME_PROGRESS,\n    WIDGET_NAME_IMAGE,\n    WIDGET_NAME_BUTTON,\n    WIDGET_NAME_LABEL,\n    WIDGET_NAME_LITERAL,\n    WIDGET_NAME_INPUT,\n    WIDGET_NAME_CALENDAR,\n    WIDGET_NAME_COLOR_BUTTON,\n    WIDGET_NAME_EXPANDER,\n    WIDGET_NAME_COLOR_CHOOSER,\n    WIDGET_NAME_COMBO_BOX_TEXT,\n    WIDGET_NAME_CHECKBOX,\n    WIDGET_NAME_REVEALER,\n    WIDGET_NAME_SCROLL,\n    WIDGET_NAME_OVERLAY,\n    WIDGET_NAME_STACK,\n    WIDGET_NAME_SYSTRAY,\n];\n\n/// widget definitions\npub(super) fn widget_use_to_gtk_widget(bargs: &mut BuilderArgs) -> Result<gtk::Widget> {\n    let gtk_widget = match bargs.widget_use.name.as_str() {\n        WIDGET_NAME_BOX => build_gtk_box(bargs)?.upcast(),\n        WIDGET_NAME_CENTERBOX => build_center_box(bargs)?.upcast(),\n        WIDGET_NAME_EVENTBOX => build_gtk_event_box(bargs)?.upcast(),\n        WIDGET_NAME_TOOLTIP => build_tooltip(bargs)?.upcast(),\n        WIDGET_NAME_CIRCULAR_PROGRESS => build_circular_progress_bar(bargs)?.upcast(),\n        WIDGET_NAME_GRAPH => build_graph(bargs)?.upcast(),\n        WIDGET_NAME_TRANSFORM => build_transform(bargs)?.upcast(),\n        WIDGET_NAME_SCALE => build_gtk_scale(bargs)?.upcast(),\n        WIDGET_NAME_PROGRESS => build_gtk_progress(bargs)?.upcast(),\n        WIDGET_NAME_IMAGE => build_gtk_image(bargs)?.upcast(),\n        WIDGET_NAME_BUTTON => build_gtk_button(bargs)?.upcast(),\n        WIDGET_NAME_LABEL => build_gtk_label(bargs)?.upcast(),\n        WIDGET_NAME_LITERAL => build_gtk_literal(bargs)?.upcast(),\n        WIDGET_NAME_INPUT => build_gtk_input(bargs)?.upcast(),\n        WIDGET_NAME_CALENDAR => build_gtk_calendar(bargs)?.upcast(),\n        WIDGET_NAME_COLOR_BUTTON => build_gtk_color_button(bargs)?.upcast(),\n        WIDGET_NAME_EXPANDER => build_gtk_expander(bargs)?.upcast(),\n        WIDGET_NAME_COLOR_CHOOSER => build_gtk_color_chooser(bargs)?.upcast(),\n        WIDGET_NAME_COMBO_BOX_TEXT => build_gtk_combo_box_text(bargs)?.upcast(),\n        WIDGET_NAME_CHECKBOX => build_gtk_checkbox(bargs)?.upcast(),\n        WIDGET_NAME_REVEALER => build_gtk_revealer(bargs)?.upcast(),\n        WIDGET_NAME_SCROLL => build_gtk_scrolledwindow(bargs)?.upcast(),\n        WIDGET_NAME_OVERLAY => build_gtk_overlay(bargs)?.upcast(),\n        WIDGET_NAME_STACK => build_gtk_stack(bargs)?.upcast(),\n        WIDGET_NAME_SYSTRAY => build_systray(bargs)?.upcast(),\n        _ => {\n            return Err(DiagError(gen_diagnostic! {\n                msg = format!(\"referenced unknown widget `{}`\", bargs.widget_use.name),\n                label = bargs.widget_use.name_span => \"Used here\",\n            })\n            .into())\n        }\n    };\n    Ok(gtk_widget)\n}\n\n/// Deprecated attributes from top of widget hierarchy\nstatic DEPRECATED_ATTRS: Lazy<HashSet<&str>> =\n    Lazy::new(|| [\"timeout\", \"onscroll\", \"onhover\", \"cursor\"].iter().cloned().collect());\n\n/// attributes that apply to all widgets\n/// @widget widget\n/// @desc these properties apply to _all_ widgets, and can be used anywhere!\npub(super) fn resolve_widget_attrs(bargs: &mut BuilderArgs, gtk_widget: &gtk::Widget) -> Result<()> {\n    let contained_deprecated: Vec<_> = DEPRECATED_ATTRS.iter().filter_map(|x| bargs.unhandled_attrs.remove_entry(*x)).collect();\n    if !contained_deprecated.is_empty() {\n        let diag = error_handling_ctx::stringify_diagnostic(gen_diagnostic! {\n            kind =  Severity::Error,\n            msg = \"Unsupported attributes provided\",\n            label = bargs.widget_use.span => \"Found in here\",\n            note = format!(\n                \"The attribute(s) ({}) has/have been removed, as GTK does not support it consistently. Instead, use eventbox to wrap this widget and set the attribute there. See #251 (https://github.com/elkowar/eww/issues/251) for more details.\",\n                contained_deprecated.iter().map(|(x, _)| x).join(\", \")\n            ),\n        }).unwrap();\n        eprintln!(\"{}\", diag);\n    }\n\n    let css_provider = gtk::CssProvider::new();\n    let css_provider2 = css_provider.clone();\n\n    let visible_result: Result<_> = (|| {\n        let visible_expr = bargs.widget_use.attrs.attrs.get(\"visible\").map(|x| x.value.as_simplexpr()).transpose()?;\n        if let Some(visible_expr) = visible_expr {\n            let visible = bargs.scope_graph.evaluate_simplexpr_in_scope(bargs.calling_scope, &visible_expr)?.as_bool()?;\n            connect_first_map(gtk_widget, move |w| {\n                if visible {\n                    w.show();\n                } else {\n                    w.hide();\n                }\n            });\n        }\n        Ok(())\n    })();\n    if let Err(err) = visible_result {\n        error_handling_ctx::print_error(err);\n    }\n\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop class - css class name\n        prop(class: as_string) {\n            // TODO currently this overrides classes that gtk adds automatically, which is kinda stupid...\n            let old_classes = gtk_widget.style_context().list_classes();\n            let old_classes = old_classes.iter().map(|x| x.as_str()).collect::<Vec<&str>>();\n            let new_classes = class.split(' ').collect::<Vec<_>>();\n            let (missing, new) = list_difference(&old_classes, &new_classes);\n            for class in missing {\n                gtk_widget.style_context().remove_class(class);\n            }\n            for class in new {\n                gtk_widget.style_context().add_class(class);\n            }\n        },\n        // @prop valign - how to align this vertically. possible values: $alignment\n        prop(valign: as_string) { gtk_widget.set_valign(parse_align(&valign)?) },\n        // @prop halign - how to align this horizontally. possible values: $alignment\n        prop(halign: as_string) { gtk_widget.set_halign(parse_align(&halign)?) },\n        // @prop vexpand - should this container expand vertically. Default: false.\n        prop(vexpand: as_bool = false) { gtk_widget.set_vexpand(vexpand) },\n        // @prop hexpand - should this widget expand horizontally. Default: false.\n        prop(hexpand: as_bool = false) { gtk_widget.set_hexpand(hexpand) },\n        // @prop width - width of this element. note that this can not restrict the size if the contents stretch it\n        // @prop height - height of this element. note that this can not restrict the size if the contents stretch it\n        prop(width: as_i32?, height: as_i32?) {\n            gtk_widget.set_size_request(\n                width.unwrap_or_else(|| gtk_widget.allocated_width()),\n                height.unwrap_or_else(|| gtk_widget.allocated_height())\n            );\n        },\n        // @prop active - If this widget can be interacted with\n        prop(active: as_bool = true) { gtk_widget.set_sensitive(active) },\n        // @prop tooltip - tooltip text (on hover)\n        prop(tooltip: as_string) {\n            gtk_widget.set_tooltip_text(Some(&tooltip));\n        },\n        // @prop visible - visibility of the widget\n        prop(visible: as_bool = true) {\n            if visible { gtk_widget.show(); } else { gtk_widget.hide(); }\n        },\n        // @prop style - inline scss style applied to the widget\n        prop(style: as_string) {\n            gtk_widget.reset_style();\n            css_provider.load_from_data(grass::from_string(format!(\"* {{ {} }}\", style), &grass::Options::default())?.as_bytes())?;\n            gtk_widget.style_context().add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION)\n        },\n        // @prop css - scss code applied to the widget, i.e.: `button {color: red;}`\n        prop(css: as_string) {\n            gtk_widget.reset_style();\n            css_provider2.load_from_data(grass::from_string(css, &grass::Options::default())?.as_bytes())?;\n            gtk_widget.style_context().add_provider(&css_provider2, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION)\n        },\n    });\n    Ok(())\n}\n\n/// @widget !range\npub(super) fn resolve_range_attrs(bargs: &mut BuilderArgs, gtk_widget: &gtk::Range) -> Result<()> {\n    gtk_widget.set_sensitive(false);\n\n    // only allow changing the value via the value property if the user isn't currently dragging\n    let is_being_dragged = Rc::new(RefCell::new(false));\n    gtk_widget.connect_button_press_event(glib::clone!(@strong is_being_dragged => move |_, _| {\n        *is_being_dragged.borrow_mut() = true;\n        glib::Propagation::Proceed\n    }));\n    gtk_widget.connect_button_release_event(glib::clone!(@strong is_being_dragged => move |_, _| {\n        *is_being_dragged.borrow_mut() = false;\n        glib::Propagation::Proceed\n    }));\n\n    // We keep track of the last value that has been set via gtk_widget.set_value (by a change in the value property).\n    // We do this so we can detect if the new value came from a scripted change or from a user input from within the value_changed handler\n    // and only run on_change when it's caused by manual user input\n    let last_set_value = Rc::new(RefCell::new(None));\n    let last_set_value_clone = last_set_value.clone();\n\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop value - the value\n        prop(value: as_f64) {\n            if !*is_being_dragged.borrow() {\n                *last_set_value.borrow_mut() = Some(value);\n                gtk_widget.set_value(value);\n            }\n        },\n        // @prop min - the minimum value\n        prop(min: as_f64) { gtk_widget.adjustment().set_lower(min)},\n        // @prop max - the maximum value\n        prop(max: as_f64) { gtk_widget.adjustment().set_upper(max)},\n        // @prop timeout - timeout of the command. Default: \"200ms\"\n        // @prop onchange - command executed once the value is changes. The placeholder `{}`, used in the command will be replaced by the new value.\n        prop(timeout: as_duration = Duration::from_millis(200), onchange: as_string) {\n            gtk_widget.set_sensitive(true);\n            gtk_widget.add_events(gdk::EventMask::PROPERTY_CHANGE_MASK);\n            let last_set_value = last_set_value_clone.clone();\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_value_changed(move |gtk_widget| {\n                let value = gtk_widget.value();\n                if last_set_value.borrow_mut().take() != Some(value) {\n                    run_command(timeout, &onchange, &[value]);\n                }\n            }));\n        }\n    });\n    Ok(())\n}\n\n/// @widget !orientable\npub(super) fn resolve_orientable_attrs(bargs: &mut BuilderArgs, gtk_widget: &gtk::Range) -> Result<()> {\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop orientation - orientation of the widget. Possible values: $orientation\n        prop(orientation: as_string) { gtk_widget.set_orientation(parse_orientation(&orientation)?) },\n    });\n    Ok(())\n}\n\n// concrete widgets\n\nconst WIDGET_NAME_COMBO_BOX_TEXT: &str = \"combo-box-text\";\n/// @widget combo-box-text\n/// @desc A combo box allowing the user to choose between several items.\nfn build_gtk_combo_box_text(bargs: &mut BuilderArgs) -> Result<gtk::ComboBoxText> {\n    let gtk_widget = gtk::ComboBoxText::new();\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop items - Items that should be displayed in the combo box\n        prop(items: as_vec) {\n            gtk_widget.remove_all();\n            for i in items {\n                gtk_widget.append_text(&i);\n            }\n        },\n        // @prop timeout - timeout of the command: Default: \"200ms\"\n        // @prop onchange - runs the code when a item was selected, replacing {} with the item as a string\n        prop(timeout: as_duration = Duration::from_millis(200), onchange: as_string) {\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_changed(move |gtk_widget| {\n                run_command(timeout, &onchange, &[gtk_widget.active_text().unwrap_or_else(|| \"\".into())]);\n            }));\n        },\n    });\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_EXPANDER: &str = \"expander\";\n/// @widget expander\n/// @desc A widget that can expand and collapse, showing/hiding it's children. Should contain\n/// exactly one child.\nfn build_gtk_expander(bargs: &mut BuilderArgs) -> Result<gtk::Expander> {\n    let gtk_widget = gtk::Expander::new(None);\n\n    match bargs.widget_use.children.len().cmp(&1) {\n        Ordering::Less => {\n            return Err(DiagError(gen_diagnostic!(\"expander must contain exactly one element\", bargs.widget_use.span)).into());\n        }\n        Ordering::Greater => {\n            let (_, additional_children) = bargs.widget_use.children.split_at(1);\n            // we know that there is more than one child, so unwrapping on first and last here is fine.\n            let first_span = additional_children.first().unwrap().span();\n            let last_span = additional_children.last().unwrap().span();\n            return Err(DiagError(gen_diagnostic!(\n                \"expander must contain exactly one element, but got more\",\n                first_span.to(last_span)\n            ))\n            .into());\n        }\n        Ordering::Equal => {\n            let mut children = bargs.widget_use.children.iter().map(|child| {\n                build_gtk_widget(\n                    bargs.scope_graph,\n                    bargs.widget_defs.clone(),\n                    bargs.calling_scope,\n                    child.clone(),\n                    bargs.custom_widget_invocation.clone(),\n                )\n            });\n            // we have exactly one child, we can unwrap\n            let child = children.next().unwrap()?;\n            gtk_widget.add(&child);\n            child.show();\n        }\n    }\n\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop name - name of the expander\n        prop(name: as_string) { gtk_widget.set_label(Some(&name)); },\n        // @prop expanded - sets if the tree is expanded\n        prop(expanded: as_bool) { gtk_widget.set_expanded(expanded); }\n    });\n\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_REVEALER: &str = \"revealer\";\n/// @widget revealer\n/// @desc A widget that can reveal a child with an animation.\nfn build_gtk_revealer(bargs: &mut BuilderArgs) -> Result<gtk::Revealer> {\n    let gtk_widget = gtk::Revealer::new();\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop transition - the name of the transition. Possible values: $transition\n        prop(transition: as_string = \"crossfade\") { gtk_widget.set_transition_type(parse_revealer_transition(&transition)?); },\n        // @prop reveal - sets if the child is revealed or not\n        prop(reveal: as_bool) { gtk_widget.set_reveal_child(reveal); },\n        // @prop duration - the duration of the reveal transition. Default: \"500ms\"\n        prop(duration: as_duration = Duration::from_millis(500)) { gtk_widget.set_transition_duration(duration.as_millis() as u32); },\n    });\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_CHECKBOX: &str = \"checkbox\";\n/// @widget a checkbox\n/// @desc A checkbox that can trigger events on checked / unchecked.\nfn build_gtk_checkbox(bargs: &mut BuilderArgs) -> Result<gtk::CheckButton> {\n    let gtk_widget = gtk::CheckButton::new();\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop checked - whether the checkbox is toggled or not when created\n        // @prop timeout - timeout of the command. Default: \"200ms\"\n        // @prop onchecked - action (command) to be executed when checked by the user\n        // @prop onunchecked - similar to onchecked but when the widget is unchecked\n        prop(checked: as_bool = false, timeout: as_duration = Duration::from_millis(200), onchecked: as_string = \"\", onunchecked: as_string = \"\") {\n            gtk_widget.set_active(checked);\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_toggled(move |gtk_widget| {\n                run_command(timeout, if gtk_widget.is_active() { &onchecked } else { &onunchecked }, &[] as &[&str]);\n            }));\n       }\n    });\n\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_COLOR_BUTTON: &str = \"color-button\";\n/// @widget color-button\n/// @desc A button opening a color chooser window\nfn build_gtk_color_button(bargs: &mut BuilderArgs) -> Result<gtk::ColorButton> {\n    let gtk_widget = gtk::ColorButton::builder().build();\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop use-alpha - bool to whether or not use alpha\n        prop(use_alpha: as_bool) {gtk_widget.set_use_alpha(use_alpha);},\n\n        // @prop onchange - runs the code when the color was selected\n        // @prop timeout - timeout of the command. Default: \"200ms\"\n        prop(timeout: as_duration = Duration::from_millis(200), onchange: as_string) {\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_color_set(move |gtk_widget| {\n                run_command(timeout, &onchange, &[gtk_widget.rgba()]);\n            }));\n        }\n    });\n\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_COLOR_CHOOSER: &str = \"color-chooser\";\n/// @widget color-chooser\n/// @desc A color chooser widget\nfn build_gtk_color_chooser(bargs: &mut BuilderArgs) -> Result<gtk::ColorChooserWidget> {\n    let gtk_widget = gtk::ColorChooserWidget::new();\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop use-alpha - bool to wether or not use alpha\n        prop(use_alpha: as_bool) {gtk_widget.set_use_alpha(use_alpha);},\n\n        // @prop onchange - runs the code when the color was selected\n        // @prop timeout - timeout of the command. Default: \"200ms\"\n        prop(timeout: as_duration = Duration::from_millis(200), onchange: as_string) {\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_color_activated(move |_a, color| {\n                run_command(timeout, &onchange, &[*color]);\n            }));\n        }\n    });\n\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_SCALE: &str = \"scale\";\n/// @widget scale extends range, orientable\n/// @desc A slider.\nfn build_gtk_scale(bargs: &mut BuilderArgs) -> Result<gtk::Scale> {\n    let gtk_widget = gtk::Scale::new(gtk::Orientation::Horizontal, Some(&gtk::Adjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0)));\n\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop flipped - flip the direction\n        prop(flipped: as_bool) { gtk_widget.set_inverted(flipped) },\n\n        // @prop marks - draw marks\n        prop(marks: as_string) {\n            gtk_widget.clear_marks();\n            for mark in marks.split(',') {\n                gtk_widget.add_mark(mark.trim().parse()?, gtk::PositionType::Bottom, None)\n            }\n        },\n\n        // @prop draw-value - draw the value of the property\n        prop(draw_value: as_bool = false) { gtk_widget.set_draw_value(draw_value) },\n\n        // @prop value-pos - position of the drawn value. possible values: $position\n        prop(value_pos: as_string) { gtk_widget.set_value_pos(parse_position_type(&value_pos)?) },\n\n        // @prop round-digits - Sets the number of decimals to round the value to when it changes\n        prop(round_digits: as_i32 = 0) { gtk_widget.set_round_digits(round_digits) }\n\n    });\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_PROGRESS: &str = \"progress\";\n/// @widget progress\n/// @desc A progress bar. HINT: for the `width` property to work, you may need to set the `min-width` of `progressbar > trough` in your css.\nfn build_gtk_progress(bargs: &mut BuilderArgs) -> Result<gtk::ProgressBar> {\n    let gtk_widget = gtk::ProgressBar::new();\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop flipped - flip the direction\n        prop(flipped: as_bool) { gtk_widget.set_inverted(flipped) },\n\n        // @prop value - value of the progress bar (between 0-100)\n        prop(value: as_f64) { gtk_widget.set_fraction(value / 100f64) },\n\n        // @prop orientation - orientation of the progress bar. possible values: $orientation\n        prop(orientation: as_string) { gtk_widget.set_orientation(parse_orientation(&orientation)?) },\n    });\n\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_INPUT: &str = \"input\";\n/// @widget input\n/// @desc An input field. For this to be useful, set `focusable=\"true\"` on the window.\nfn build_gtk_input(bargs: &mut BuilderArgs) -> Result<gtk::Entry> {\n    let gtk_widget = gtk::Entry::new();\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop value - the content of the text field\n        prop(value: as_string) {\n            gtk_widget.set_text(&value);\n        },\n        // @prop onchange - Command to run when the text changes. The placeholder `{}` will be replaced by the value\n        // @prop timeout - timeout of the command. Default: \"200ms\"\n        prop(timeout: as_duration = Duration::from_millis(200), onchange: as_string) {\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_changed(move |gtk_widget| {\n                run_command(timeout, &onchange, &[gtk_widget.text().to_string()]);\n            }));\n        },\n        // @prop onaccept - Command to run when the user hits return in the input field. The placeholder `{}` will be replaced by the value\n        // @prop timeout - timeout of the command. Default: \"200ms\"\n        prop(timeout: as_duration = Duration::from_millis(200), onaccept: as_string) {\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_activate(move |gtk_widget| {\n                run_command(timeout, &onaccept, &[gtk_widget.text().to_string()]);\n            }));\n        },\n        // @prop password - if the input is obscured\n        prop(password: as_bool = false) {\n            gtk_widget.set_visibility(!password);\n        }\n    });\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_BUTTON: &str = \"button\";\n/// @widget button\n/// @desc A button containing any widget as it's child. Events are triggered on release.\nfn build_gtk_button(bargs: &mut BuilderArgs) -> Result<gtk::Button> {\n    let gtk_widget = gtk::Button::new();\n\n    def_widget!(bargs, _g, gtk_widget, {\n        prop(\n            // @prop timeout - timeout of the command. Default: \"200ms\"\n            timeout: as_duration = Duration::from_millis(200),\n            // @prop onclick - command to run when the button is activated either by leftclicking or keyboard\n            onclick: as_string = \"\",\n            // @prop onmiddleclick - command to run when the button is middleclicked\n            onmiddleclick: as_string = \"\",\n            // @prop onrightclick - command to run when the button is rightclicked\n            onrightclick: as_string = \"\"\n        ) {\n            // animate button upon right-/middleclick (if gtk theme supports it)\n            // since we do this, we can't use `connect_clicked` as that would always run `onclick` as well\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_button_press_event(move |button, _| {\n                button.emit_activate();\n                glib::Propagation::Proceed\n            }));\n            let onclick_ = onclick.clone();\n            // mouse click events\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_button_release_event(move |_, evt| {\n                match evt.button() {\n                    1 => run_command(timeout, &onclick, &[] as &[&str]),\n                    2 => run_command(timeout, &onmiddleclick, &[] as &[&str]),\n                    3 => run_command(timeout, &onrightclick, &[] as &[&str]),\n                    _ => {},\n                }\n                glib::Propagation::Proceed\n            }));\n            // keyboard events\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_key_release_event(move |_, evt| {\n                match evt.scancode() {\n                    // return\n                    36 => run_command(timeout, &onclick_, &[] as &[&str]),\n                    // space\n                    65 => run_command(timeout, &onclick_, &[] as &[&str]),\n                    _ => {},\n                }\n                glib::Propagation::Proceed\n            }));\n        }\n    });\n    Ok(gtk_widget)\n}\n\n/// @var icon-size - \"menu\", \"small-toolbar\", \"toolbar\", \"large-toolbar\", \"button\", \"dnd\", \"dialog\"\nfn parse_icon_size(o: &str) -> Result<gtk::IconSize> {\n    enum_parse! { \"icon-size\", o,\n        \"menu\" => gtk::IconSize::Menu,\n        \"small-toolbar\" | \"toolbar\" => gtk::IconSize::SmallToolbar,\n        \"large-toolbar\" => gtk::IconSize::LargeToolbar,\n        \"button\" => gtk::IconSize::Button,\n        \"dnd\" => gtk::IconSize::Dnd,\n        \"dialog\" => gtk::IconSize::Dialog,\n    }\n}\n\nconst WIDGET_NAME_IMAGE: &str = \"image\";\n/// @widget image\n/// @desc A widget displaying an image\nfn build_gtk_image(bargs: &mut BuilderArgs) -> Result<gtk::Image> {\n    let gtk_widget = gtk::Image::new();\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop path - path to the image file\n        // @prop image-width - width of the image\n        // @prop image-height - height of the image\n        // @prop preserve-aspect-ratio - whether to keep the aspect ratio when resizing an image. Default: true, false doesn't work for all image types\n        // @prop fill-svg - sets the color of svg images\n        prop(path: as_string, image_width: as_i32 = -1, image_height: as_i32 = -1, preserve_aspect_ratio: as_bool = true, fill_svg: as_string = \"\") {\n            if !path.ends_with(\".svg\") && !fill_svg.is_empty() {\n                log::warn!(\"Fill attribute ignored, file is not an svg image\");\n            }\n\n            if path.ends_with(\".gif\") {\n                let pixbuf_animation = gtk::gdk_pixbuf::PixbufAnimation::from_file(std::path::PathBuf::from(path))?;\n                gtk_widget.set_from_animation(&pixbuf_animation);\n            } else {\n                let pixbuf;\n                // populate the pixel buffer\n                if path.ends_with(\".svg\") && !fill_svg.is_empty() {\n                    let svg_data = std::fs::read_to_string(std::path::PathBuf::from(path.clone()))?;\n                    // The fastest way to add/change fill color\n                    let svg_data = if svg_data.contains(\"fill=\") {\n                        let reg = regex::Regex::new(r#\"fill=\"[^\"]*\"\"#)?;\n                        reg.replace(&svg_data, &format!(\"fill=\\\"{}\\\"\", fill_svg))\n                    } else {\n                        let reg = regex::Regex::new(r\"<svg\")?;\n                        reg.replace(&svg_data, &format!(\"<svg fill=\\\"{}\\\"\", fill_svg))\n                    };\n                    let stream = gtk::gio::MemoryInputStream::from_bytes(&gtk::glib::Bytes::from(svg_data.as_bytes()));\n                    pixbuf = gtk::gdk_pixbuf::Pixbuf::from_stream_at_scale(&stream, image_width, image_height, preserve_aspect_ratio, None::<&gtk::gio::Cancellable>)?;\n                    stream.close(None::<&gtk::gio::Cancellable>)?;\n                } else {\n                    pixbuf = gtk::gdk_pixbuf::Pixbuf::from_file_at_scale(std::path::PathBuf::from(path), image_width, image_height, preserve_aspect_ratio)?;\n                }\n                gtk_widget.set_from_pixbuf(Some(&pixbuf));\n            }\n        },\n        // @prop icon - name of a theme icon\n        // @prop icon-size - size of the theme icon\n        prop(icon: as_string, icon_size: as_string = \"button\") {\n            gtk_widget.set_from_icon_name(Some(&icon), parse_icon_size(&icon_size)?);\n        },\n    });\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_BOX: &str = \"box\";\n/// @widget box\n/// @desc the main layout container\nfn build_gtk_box(bargs: &mut BuilderArgs) -> Result<gtk::Box> {\n    let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0);\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop spacing - spacing between elements\n        prop(spacing: as_i32 = 0) { gtk_widget.set_spacing(spacing) },\n        // @prop orientation - orientation of the box. possible values: $orientation\n        prop(orientation: as_string) { gtk_widget.set_orientation(parse_orientation(&orientation)?) },\n        // @prop space-evenly - space the widgets evenly.\n        prop(space_evenly: as_bool = true) { gtk_widget.set_homogeneous(space_evenly) },\n    });\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_OVERLAY: &str = \"overlay\";\n/// @widget overlay\n/// @desc a widget that places its children on top of each other. The overlay widget takes the size of its first child.\nfn build_gtk_overlay(bargs: &mut BuilderArgs) -> Result<gtk::Overlay> {\n    let gtk_widget = gtk::Overlay::new();\n\n    // no def_widget because this widget has no props.\n\n    match bargs.widget_use.children.len().cmp(&1) {\n        Ordering::Less => {\n            Err(DiagError(gen_diagnostic!(\"overlay must contain at least one element\", bargs.widget_use.span)).into())\n        }\n        Ordering::Greater | Ordering::Equal => {\n            let mut children = bargs.widget_use.children.iter().map(|child| {\n                build_gtk_widget(\n                    bargs.scope_graph,\n                    bargs.widget_defs.clone(),\n                    bargs.calling_scope,\n                    child.clone(),\n                    bargs.custom_widget_invocation.clone(),\n                )\n            });\n            // we have more than one child, we can unwrap\n            let first = children.next().unwrap()?;\n            gtk_widget.add(&first);\n            first.show();\n            for child in children {\n                let child = child?;\n                gtk_widget.add_overlay(&child);\n                gtk_widget.set_overlay_pass_through(&child, true);\n                child.show();\n            }\n            Ok(gtk_widget)\n        }\n    }\n}\n\nconst WIDGET_NAME_TOOLTIP: &str = \"tooltip\";\n/// @widget tooltip\n/// @desc A widget that have a custom tooltip. The first child is the content of the tooltip, the second one is the content of the widget.\nfn build_tooltip(bargs: &mut BuilderArgs) -> Result<gtk::Box> {\n    let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0);\n    gtk_widget.set_has_tooltip(true);\n\n    match bargs.widget_use.children.len().cmp(&2) {\n        Ordering::Less => {\n            Err(DiagError(gen_diagnostic!(\"tooltip must contain exactly 2 elements\", bargs.widget_use.span)).into())\n        }\n        Ordering::Greater => {\n            let (_, additional_children) = bargs.widget_use.children.split_at(2);\n            // we know that there is more than two children, so unwrapping on first and last here is fine.\n            let first_span = additional_children.first().unwrap().span();\n            let last_span = additional_children.last().unwrap().span();\n            Err(DiagError(gen_diagnostic!(\"tooltip must contain exactly 2 elements, but got more\", first_span.to(last_span)))\n                .into())\n        }\n        Ordering::Equal => {\n            let mut children = bargs.widget_use.children.iter().map(|child| {\n                build_gtk_widget(\n                    bargs.scope_graph,\n                    bargs.widget_defs.clone(),\n                    bargs.calling_scope,\n                    child.clone(),\n                    bargs.custom_widget_invocation.clone(),\n                )\n            });\n            // we know that we have exactly two children here, so we can unwrap here.\n            let (tooltip, content) = children.next_tuple().unwrap();\n            let (tooltip_content, content) = (tooltip?, content?);\n\n            gtk_widget.add(&content);\n            gtk_widget.connect_query_tooltip(move |_this, _x, _y, _keyboard_mode, tooltip| {\n                tooltip.set_custom(Some(&tooltip_content));\n                true\n            });\n\n            Ok(gtk_widget)\n        }\n    }\n}\n\nconst WIDGET_NAME_CENTERBOX: &str = \"centerbox\";\n/// @widget centerbox\n/// @desc a box that must contain exactly three children, which will be layed out at the start, center and end of the container.\nfn build_center_box(bargs: &mut BuilderArgs) -> Result<gtk::Box> {\n    let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0);\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop orientation - orientation of the centerbox. possible values: $orientation\n        prop(orientation: as_string) { gtk_widget.set_orientation(parse_orientation(&orientation)?) },\n    });\n\n    match bargs.widget_use.children.len().cmp(&3) {\n        Ordering::Less => {\n            Err(DiagError(gen_diagnostic!(\"centerbox must contain exactly 3 elements\", bargs.widget_use.span)).into())\n        }\n        Ordering::Greater => {\n            let (_, additional_children) = bargs.widget_use.children.split_at(3);\n            // we know that there is more than three children, so unwrapping on first and left here is fine.\n            let first_span = additional_children.first().unwrap().span();\n            let last_span = additional_children.last().unwrap().span();\n            Err(DiagError(gen_diagnostic!(\"centerbox must contain exactly 3 elements, but got more\", first_span.to(last_span)))\n                .into())\n        }\n        Ordering::Equal => {\n            let mut children = bargs.widget_use.children.iter().map(|child| {\n                build_gtk_widget(\n                    bargs.scope_graph,\n                    bargs.widget_defs.clone(),\n                    bargs.calling_scope,\n                    child.clone(),\n                    bargs.custom_widget_invocation.clone(),\n                )\n            });\n            // we know that we have exactly three children here, so we can unwrap here.\n            let (first, center, end) = children.next_tuple().unwrap();\n            let (first, center, end) = (first?, center?, end?);\n            gtk_widget.pack_start(&first, true, true, 0);\n            gtk_widget.set_center_widget(Some(&center));\n            gtk_widget.pack_end(&end, true, true, 0);\n            first.show();\n            center.show();\n            end.show();\n            Ok(gtk_widget)\n        }\n    }\n}\n\nconst WIDGET_NAME_SCROLL: &str = \"scroll\";\n/// @widget scroll\n/// @desc a container with a single child that can scroll.\nfn build_gtk_scrolledwindow(bargs: &mut BuilderArgs) -> Result<gtk::ScrolledWindow> {\n    // I don't have single idea of what those two generics are supposed to be, but this works.\n    let gtk_widget = gtk::ScrolledWindow::new(None::<&gtk::Adjustment>, None::<&gtk::Adjustment>);\n\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop hscroll - scroll horizontally\n        // @prop vscroll - scroll vertically\n        prop(hscroll: as_bool = true, vscroll: as_bool = true) {\n            gtk_widget.set_policy(\n                if hscroll { gtk::PolicyType::Automatic } else { gtk::PolicyType::Never },\n                if vscroll { gtk::PolicyType::Automatic } else { gtk::PolicyType::Never },\n            )\n        },\n    });\n\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_EVENTBOX: &str = \"eventbox\";\n/// @widget eventbox\n/// @desc a container which can receive events and must contain exactly one child. Supports `:hover` and `:active` css selectors.\nfn build_gtk_event_box(bargs: &mut BuilderArgs) -> Result<gtk::EventBox> {\n    let gtk_widget = gtk::EventBox::new();\n\n    // Support :hover selector\n    gtk_widget.connect_enter_notify_event(|gtk_widget, evt| {\n        if evt.detail() != NotifyType::Inferior {\n            gtk_widget.set_state_flags(gtk::StateFlags::PRELIGHT, false);\n        }\n        glib::Propagation::Proceed\n    });\n\n    gtk_widget.connect_leave_notify_event(|gtk_widget, evt| {\n        if evt.detail() != NotifyType::Inferior {\n            gtk_widget.unset_state_flags(gtk::StateFlags::PRELIGHT);\n        }\n        glib::Propagation::Proceed\n    });\n\n    // Support :active selector\n    gtk_widget.connect_button_press_event(|gtk_widget, _| {\n        gtk_widget.set_state_flags(gtk::StateFlags::ACTIVE, false);\n        glib::Propagation::Proceed\n    });\n\n    gtk_widget.connect_button_release_event(|gtk_widget, _| {\n        gtk_widget.unset_state_flags(gtk::StateFlags::ACTIVE);\n        glib::Propagation::Proceed\n    });\n\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop timeout - timeout of the command. Default: \"200ms\"\n        // @prop onscroll - event to execute when the user scrolls with the mouse over the widget. The placeholder `{}` used in the command will be replaced with either `up` or `down`.\n        prop(timeout: as_duration = Duration::from_millis(200), onscroll: as_string) {\n            gtk_widget.add_events(gdk::EventMask::SCROLL_MASK);\n            gtk_widget.add_events(gdk::EventMask::SMOOTH_SCROLL_MASK);\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_scroll_event(move |_, evt| {\n                let delta = evt.delta().1;\n                if delta != 0f64 { // Ignore the first event https://bugzilla.gnome.org/show_bug.cgi?id=675959\n                    run_command(timeout, &onscroll, &[if delta < 0f64 { \"up\" } else { \"down\" }]);\n                }\n                glib::Propagation::Proceed\n            }));\n        },\n        // @prop timeout - timeout of the command. Default: \"200ms\"\n        // @prop onhover - event to execute when the user hovers over the widget\n        prop(timeout: as_duration = Duration::from_millis(200), onhover: as_string) {\n            gtk_widget.add_events(gdk::EventMask::ENTER_NOTIFY_MASK);\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_enter_notify_event(move |_, evt| {\n                if evt.detail() != NotifyType::Inferior {\n                    run_command(timeout, &onhover, &[evt.position().0, evt.position().1]);\n                }\n                glib::Propagation::Proceed\n            }));\n        },\n        // @prop timeout - timeout of the command. Default: \"200ms\"\n        // @prop onhoverlost - event to execute when the user losts hovers over the widget\n        prop(timeout: as_duration = Duration::from_millis(200), onhoverlost: as_string) {\n            gtk_widget.add_events(gdk::EventMask::LEAVE_NOTIFY_MASK);\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_leave_notify_event(move |_, evt| {\n                if evt.detail() != NotifyType::Inferior {\n                    run_command(timeout, &onhoverlost, &[evt.position().0, evt.position().1]);\n                }\n                glib::Propagation::Proceed\n            }));\n        },\n        // @prop cursor - Cursor to show while hovering (see [gtk3-cursors](https://docs.gtk.org/gdk3/ctor.Cursor.new_from_name.html) for possible names)\n        prop(cursor: as_string) {\n            gtk_widget.add_events(gdk::EventMask::ENTER_NOTIFY_MASK);\n            gtk_widget.add_events(gdk::EventMask::LEAVE_NOTIFY_MASK);\n\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_enter_notify_event(move |widget, _evt| {\n                if _evt.detail() != NotifyType::Inferior {\n                    let display = gdk::Display::default();\n                    let gdk_window = widget.window();\n                    if let (Some(display), Some(gdk_window)) = (display, gdk_window) {\n                        gdk_window.set_cursor(gdk::Cursor::from_name(&display, &cursor).as_ref());\n                    }\n                }\n                glib::Propagation::Proceed\n            }));\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_leave_notify_event(move |widget, _evt| {\n                if _evt.detail() != NotifyType::Inferior {\n                    let gdk_window = widget.window();\n                    if let Some(gdk_window) = gdk_window {\n                        gdk_window.set_cursor(None);\n                    }\n                }\n                glib::Propagation::Proceed\n            }));\n        },\n        // @prop timeout - timeout of the command. Default: \"200ms\"\n        // @prop ondropped - Command to execute when something is dropped on top of this element. The placeholder `{}` used in the command will be replaced with the uri to the dropped thing.\n        prop(timeout: as_duration = Duration::from_millis(200), ondropped: as_string) {\n            gtk_widget.drag_dest_set(\n                DestDefaults::ALL,\n                &[\n                    TargetEntry::new(\"text/uri-list\", gtk::TargetFlags::OTHER_APP | gtk::TargetFlags::OTHER_WIDGET, 0),\n                    TargetEntry::new(\"text/plain\", gtk::TargetFlags::OTHER_APP | gtk::TargetFlags::OTHER_WIDGET, 0)\n                ],\n                gdk::DragAction::COPY,\n            );\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_drag_data_received(move |_, _, _x, _y, selection_data, _target_type, _timestamp| {\n                if let Some(data) = selection_data.uris().first(){\n                    run_command(timeout, &ondropped, &[data.to_string(), \"file\".to_string()]);\n                } else if let Some(data) = selection_data.text(){\n                    run_command(timeout, &ondropped, &[data.to_string(), \"text\".to_string()]);\n                }\n            }));\n        },\n\n        // @prop dragvalue - URI that will be provided when dragging from this widget\n        // @prop dragtype - Type of value that should be dragged from this widget. Possible values: $dragtype\n        prop(dragvalue: as_string, dragtype: as_string = \"file\") {\n            let dragtype = parse_dragtype(&dragtype)?;\n            if dragvalue.is_empty() {\n                gtk_widget.drag_source_unset();\n            } else {\n                let target_entry = match dragtype {\n                    DragEntryType::File => TargetEntry::new(\"text/uri-list\", gtk::TargetFlags::OTHER_APP | gtk::TargetFlags::OTHER_WIDGET, 0),\n                    DragEntryType::Text => TargetEntry::new(\"text/plain\", gtk::TargetFlags::OTHER_APP | gtk::TargetFlags::OTHER_WIDGET, 0),\n                };\n                gtk_widget.drag_source_set(\n                    ModifierType::BUTTON1_MASK,\n                    &[target_entry.clone()],\n                    gdk::DragAction::COPY | gdk::DragAction::MOVE,\n                );\n                gtk_widget.drag_source_set_target_list(Some(&TargetList::new(&[target_entry])));\n            }\n\n            connect_signal_handler!(gtk_widget, if !dragvalue.is_empty(), gtk_widget.connect_drag_data_get(move |_, _, data, _, _| {\n                match dragtype {\n                    DragEntryType::File => data.set_uris(&[&dragvalue]),\n                    DragEntryType::Text => data.set_text(&dragvalue),\n                };\n            }));\n        },\n        prop(\n            // @prop timeout - timeout of the command. Default: \"200ms\"\n            timeout: as_duration = Duration::from_millis(200),\n            // @prop onclick - command to run when the widget is clicked\n            onclick: as_string = \"\",\n            // @prop onmiddleclick - command to run when the widget is middleclicked\n            onmiddleclick: as_string = \"\",\n            // @prop onrightclick - command to run when the widget is rightclicked\n            onrightclick: as_string = \"\"\n        ) {\n            gtk_widget.add_events(gdk::EventMask::BUTTON_PRESS_MASK);\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_button_release_event(move |_, evt| {\n                match evt.button() {\n                    1 => run_command(timeout, &onclick, &[] as &[&str]),\n                    2 => run_command(timeout, &onmiddleclick, &[] as &[&str]),\n                    3 => run_command(timeout, &onrightclick, &[] as &[&str]),\n                    _ => {},\n                }\n                glib::Propagation::Proceed\n            }));\n        }\n    });\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_LABEL: &str = \"label\";\n/// @widget label\n/// @desc A text widget giving you more control over how the text is displayed\nfn build_gtk_label(bargs: &mut BuilderArgs) -> Result<gtk::Label> {\n    let gtk_widget = gtk::Label::new(None);\n\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop text - the text to display\n        // @prop truncate - whether to truncate text (or pango markup). If `show-truncated` is `false`, or if `limit-width` has a value, this property has no effect and truncation is enabled.\n        // @prop limit-width - maximum count of characters to display\n        // @prop truncate-left - whether to truncate on the left side\n        // @prop show-truncated - show whether the text was truncated. Disabling it will also disable dynamic truncation (the labels won't be truncated more than `limit-width`, even if there is not enough space for them), and will completly disable truncation on pango markup.\n        // @prop unindent - whether to remove leading spaces\n        prop(text: as_string, truncate: as_bool = false, limit_width: as_i32 = i32::MAX, truncate_left: as_bool = false, show_truncated: as_bool = true, unindent: as_bool = true) {\n            let text = if show_truncated {\n                // gtk does weird thing if we set max_width_chars to i32::MAX\n                if limit_width == i32::MAX {\n                    gtk_widget.set_max_width_chars(-1);\n                } else {\n                    gtk_widget.set_max_width_chars(limit_width);\n                }\n                if truncate || limit_width != i32::MAX {\n                    if truncate_left {\n                        gtk_widget.set_ellipsize(pango::EllipsizeMode::Start);\n                    } else {\n                        gtk_widget.set_ellipsize(pango::EllipsizeMode::End);\n                    }\n                } else {\n                    gtk_widget.set_ellipsize(pango::EllipsizeMode::None);\n                }\n\n                text\n            } else {\n                gtk_widget.set_ellipsize(pango::EllipsizeMode::None);\n\n                let limit_width = limit_width as usize;\n                let char_count = text.chars().count();\n                if char_count > limit_width {\n                    if truncate_left {\n                        text.chars().skip(char_count - limit_width).collect()\n                    } else {\n                        text.chars().take(limit_width).collect()\n                    }\n                } else {\n                    text\n                }\n            };\n\n            let text = unescape::unescape(&text).context(format!(\"Failed to unescape label text {}\", &text))?;\n            let text = if unindent { util::unindent(&text) } else { text };\n            gtk_widget.set_text(&text);\n        },\n        // @prop markup - Pango markup to display\n        // @prop truncate - whether to truncate text (or pango markup). If `show-truncated` is `false`, or if `limit-width` has a value, this property has no effect and truncation is enabled.\n        // @prop limit-width - maximum count of characters to display\n        // @prop truncate-left - whether to truncate on the left side\n        // @prop show-truncated - show whether the text was truncated. Disabling it will also disable dynamic truncation (the labels won't be truncated more than `limit-width`, even if there is not enough space for them), and will completly disable truncation on pango markup.\n        prop(markup: as_string, truncate: as_bool = false, limit_width: as_i32 = i32::MAX, truncate_left: as_bool = false, show_truncated: as_bool = true) {\n            if (truncate || limit_width != i32::MAX) && show_truncated {\n                // gtk does weird thing if we set max_width_chars to i32::MAX\n                if limit_width == i32::MAX {\n                    gtk_widget.set_max_width_chars(-1);\n                } else {\n                    gtk_widget.set_max_width_chars(limit_width);\n                }\n\n                if truncate_left {\n                    gtk_widget.set_ellipsize(pango::EllipsizeMode::Start);\n                } else {\n                    gtk_widget.set_ellipsize(pango::EllipsizeMode::End);\n                }\n            } else {\n                gtk_widget.set_ellipsize(pango::EllipsizeMode::None);\n            }\n\n            gtk_widget.set_markup(&markup);\n        },\n        // @prop wrap - Wrap the text. This mainly makes sense if you set the width of this widget.\n        prop(wrap: as_bool) { gtk_widget.set_line_wrap(wrap) },\n        // @prop angle - the angle of rotation for the label (between 0 - 360)\n        prop(angle: as_f64 = 0) { gtk_widget.set_angle(angle) },\n        // @prop gravity - the gravity of the string (south, east, west, north, auto). Text will want to face the direction of gravity.\n        prop(gravity: as_string = \"south\") {\n            gtk_widget.pango_context().set_base_gravity(parse_gravity(&gravity)?);\n        },\n        // @prop xalign - the alignment of the label text on the x axis (between 0 - 1, 0 -> left, 0.5 -> center, 1 -> right)\n        prop(xalign: as_f64 = 0.5) { gtk_widget.set_xalign(xalign as f32) },\n        // @prop yalign - the alignment of the label text on the y axis (between 0 - 1, 0 -> bottom, 0.5 -> center, 1 -> top)\n        prop(yalign: as_f64 = 0.5) { gtk_widget.set_yalign(yalign as f32) },\n        // @prop justify - the justification of the label text (left, right, center, fill)\n        prop(justify: as_string = \"left\") {\n            gtk_widget.set_justify(parse_justification(&justify)?);\n        },\n        // @prop wrap-mode - how text is wrapped. possible options: $wrap_mode\n        prop(wrap_mode: as_string = \"word\") {\n            gtk_widget.set_wrap_mode(parse_wrap_mode(&wrap_mode)?);\n        },\n        // @prop lines - maximum number of lines to display (only works when `limit-width` has a value). A value of -1 (default) disables the limit.\n        prop(lines: as_i32 = -1) {\n            gtk_widget.set_lines(lines);\n        }\n    });\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_LITERAL: &str = \"literal\";\n/// @widget literal\n/// @desc A widget that allows you to render arbitrary yuck.\nfn build_gtk_literal(bargs: &mut BuilderArgs) -> Result<gtk::Box> {\n    let gtk_widget = gtk::Box::new(gtk::Orientation::Vertical, 0);\n    gtk_widget.set_widget_name(\"literal\");\n\n    // TODO these clones here are dumdum\n    let literal_use_span = bargs.widget_use.span;\n\n    // the file id the literal-content has been stored under, for error reporting.\n    let literal_file_id: Rc<RefCell<Option<usize>>> = Rc::new(RefCell::new(None));\n\n    let widget_defs = bargs.widget_defs.clone();\n    let calling_scope = bargs.calling_scope;\n\n    def_widget!(bargs, scope_graph, gtk_widget, {\n        // @prop content - inline yuck that will be rendered as a widget.\n        prop(content: as_string) {\n            gtk_widget.children().iter().for_each(|w| gtk_widget.remove(w));\n            if !content.is_empty() {\n                let content_widget_use: DiagResult<_> = (||{\n                    let ast = {\n                        let mut yuck_files = error_handling_ctx::FILE_DATABASE.write().unwrap();\n                        let (span, asts) = yuck_files.load_yuck_str(\"<literal-content>\".to_string(), content)?;\n                        if let Some(file_id) = literal_file_id.replace(Some(span.2)) {\n                            yuck_files.unload(file_id);\n                        }\n                        yuck::parser::require_single_toplevel(span, asts)?\n                    };\n\n                    yuck::config::widget_use::WidgetUse::from_ast(ast)\n                })();\n                let content_widget_use = content_widget_use?;\n\n                // TODO a literal should create a new scope, that I'm not even sure should inherit from root\n                let child_widget = build_gtk_widget(scope_graph, widget_defs.clone(), calling_scope, content_widget_use, None)\n                    .map_err(|e| {\n                        let diagnostic = error_handling_ctx::anyhow_err_to_diagnostic(&e)\n                            .unwrap_or_else(|| gen_diagnostic!(e))\n                            .with_label(span_to_secondary_label(literal_use_span).with_message(\"Error in the literal used here\"));\n                        DiagError(diagnostic)\n                    })?;\n                gtk_widget.add(&child_widget);\n                child_widget.show();\n            }\n        }\n    });\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_CALENDAR: &str = \"calendar\";\n/// @widget calendar\n/// @desc A widget that displays a calendar\nfn build_gtk_calendar(bargs: &mut BuilderArgs) -> Result<gtk::Calendar> {\n    let gtk_widget = gtk::Calendar::new();\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop day - the selected day\n        prop(day: as_f64) {\n            if !(1f64..=31f64).contains(&day) {\n                log::warn!(\"Calendar day is not a number between 1 and 31\");\n            } else {\n                gtk_widget.set_day(day as i32)\n            }\n        },\n        // @prop month - the selected month\n        prop(month: as_f64) {\n            if !(1f64..=12f64).contains(&month) {\n                log::warn!(\"Calendar month is not a number between 1 and 12\");\n            } else {\n                gtk_widget.set_month(month as i32 - 1)\n            }\n        },\n        // @prop year - the selected year\n        prop(year: as_f64) { gtk_widget.set_year(year as i32) },\n        // @prop show-details - show details\n        prop(show_details: as_bool) { gtk_widget.set_show_details(show_details) },\n        // @prop show-heading - show heading line\n        prop(show_heading: as_bool) { gtk_widget.set_show_heading(show_heading) },\n        // @prop show-day-names - show names of days\n        prop(show_day_names: as_bool) { gtk_widget.set_show_day_names(show_day_names) },\n        // @prop show-week-numbers - show week numbers\n        prop(show_week_numbers: as_bool) { gtk_widget.set_show_week_numbers(show_week_numbers) },\n        // @prop onclick - command to run when the user selects a date. The `{0}` placeholder will be replaced by the selected day, `{1}` will be replaced by the month, and `{2}` by the year.\n        // @prop timeout - timeout of the command. Default: \"200ms\"\n        prop(timeout: as_duration = Duration::from_millis(200), onclick: as_string) {\n            connect_signal_handler!(gtk_widget, gtk_widget.connect_day_selected(move |w| {\n                run_command(\n                    timeout,\n                    &onclick,\n                    &[w.day(), w.month(), w.year()]\n                )\n            }));\n        }\n\n    });\n\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_STACK: &str = \"stack\";\n/// @widget stack\n/// @desc A widget that displays one of its children at a time\nfn build_gtk_stack(bargs: &mut BuilderArgs) -> Result<gtk::Stack> {\n    let gtk_widget = gtk::Stack::new();\n\n    if bargs.widget_use.children.is_empty() {\n        return Err(DiagError(gen_diagnostic!(\"stack must contain at least one element\", bargs.widget_use.span)).into());\n    }\n\n    let children = bargs.widget_use.children.iter().map(|child| {\n        build_gtk_widget(\n            bargs.scope_graph,\n            bargs.widget_defs.clone(),\n            bargs.calling_scope,\n            child.clone(),\n            bargs.custom_widget_invocation.clone(),\n        )\n    });\n\n    for (i, child) in children.enumerate() {\n        let child = child?;\n        gtk_widget.add_named(&child, &i.to_string());\n        child.show();\n    }\n\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop selected - index of child which should be shown\n        prop(selected: as_i32) { gtk_widget.set_visible_child_name(&selected.to_string()); },\n        // @prop transition - the name of the transition. Possible values: $transition\n        prop(transition: as_string = \"crossfade\") { gtk_widget.set_transition_type(parse_stack_transition(&transition)?); },\n        // @prop same-size - sets whether all children should be the same size\n        prop(same_size: as_bool = false) { gtk_widget.set_homogeneous(same_size); }\n    });\n\n    Ok(gtk_widget)\n}\n\nconst WIDGET_NAME_TRANSFORM: &str = \"transform\";\n/// @widget transform\n/// @desc A widget that applies transformations to its content. They are applied in the following order: rotate -> translate -> scale\nfn build_transform(bargs: &mut BuilderArgs) -> Result<Transform> {\n    let w = Transform::new();\n    def_widget!(bargs, _g, w, {\n        // @prop rotate - the percentage to rotate\n        prop(rotate: as_f64) { w.set_property(\"rotate\", rotate); },\n        // @prop transform-origin-x - x coordinate of origin of transformation (px or %)\n        prop(transform_origin_x: as_string) { w.set_property(\"transform-origin-x\", transform_origin_x) },\n        // @prop transform-origin-y - y coordinate of origin of transformation (px or %)\n        prop(transform_origin_y: as_string) { w.set_property(\"transform-origin-y\", transform_origin_y) },\n        // @prop translate-x - the amount to translate in the x direction (px or %)\n        prop(translate_x: as_string) { w.set_property(\"translate-x\", translate_x); },\n        // @prop translate-y - the amount to translate in the y direction (px or %)\n        prop(translate_y: as_string) { w.set_property(\"translate-y\", translate_y); },\n        // @prop scale-x - the amount to scale in the x direction (px or %)\n        prop(scale_x: as_string) { w.set_property(\"scale-x\", scale_x); },\n        // @prop scale-y - the amount to scale in the y direction (px or %)\n        prop(scale_y: as_string) { w.set_property(\"scale-y\", scale_y); },\n    });\n    Ok(w)\n}\n\nconst WIDGET_NAME_CIRCULAR_PROGRESS: &str = \"circular-progress\";\n/// @widget circular-progress\n/// @desc A widget that displays a circular progress bar\nfn build_circular_progress_bar(bargs: &mut BuilderArgs) -> Result<CircProg> {\n    let w = CircProg::new();\n    def_widget!(bargs, _g, w, {\n        // @prop value - the value, between 0 - 100\n        prop(value: as_f64) { w.set_property(\"value\", value.clamp(0.0, 100.0)); },\n        // @prop start-at - the percentage that the circle should start at\n        prop(start_at: as_f64) { w.set_property(\"start-at\", start_at.clamp(0.0, 100.0)); },\n        // @prop thickness - the thickness of the circle\n        prop(thickness: as_f64) { w.set_property(\"thickness\", thickness); },\n        // @prop clockwise - wether the progress bar spins clockwise or counter clockwise\n        prop(clockwise: as_bool) { w.set_property(\"clockwise\", clockwise); },\n    });\n    Ok(w)\n}\n\nconst WIDGET_NAME_GRAPH: &str = \"graph\";\n/// @widget graph\n/// @desc A widget that displays a graph showing how a given value changes over time\nfn build_graph(bargs: &mut BuilderArgs) -> Result<super::graph::Graph> {\n    let w = super::graph::Graph::new();\n    def_widget!(bargs, _g, w, {\n        // @prop value - the value, between 0 - 100\n        prop(value: as_f64) {\n            if value.is_nan() || value.is_infinite() {\n                return Err(DiagError(gen_diagnostic!(\n                    format!(\"Graph's value should never be NaN or infinite\")\n                )).into());\n            }\n            w.set_property(\"value\", value);\n        },\n        // @prop thickness - the thickness of the line\n        prop(thickness: as_f64) { w.set_property(\"thickness\", thickness); },\n        // @prop time-range - the range of time to show\n        prop(time_range: as_duration) { w.set_property(\"time-range\", time_range.as_millis() as u64); },\n        // @prop min - the minimum value to show (defaults to 0 if value_max is provided)\n        // @prop max - the maximum value to show\n        prop(min: as_f64 = 0, max: as_f64 = 100) {\n            if min > max {\n                return Err(DiagError(gen_diagnostic!(\n                    format!(\"Graph's min ({min}) should never be higher than max ({max})\")\n                )).into());\n            }\n            w.set_property(\"min\", min);\n            w.set_property(\"max\", max);\n        },\n        // @prop dynamic - whether the y range should dynamically change based on value\n        prop(dynamic: as_bool) { w.set_property(\"dynamic\", dynamic); },\n        // @prop line-style - changes the look of the edges in the graph. Values: \"miter\" (default), \"round\",\n        // \"bevel\"\n        prop(line_style: as_string) { w.set_property(\"line-style\", line_style); },\n        // @prop flip-x - whether the x axis should go from high to low\n        prop(flip_x: as_bool) { w.set_property(\"flip-x\", flip_x); },\n        // @prop flip-y - whether the y axis should go from high to low\n        prop(flip_y: as_bool) { w.set_property(\"flip-y\", flip_y); },\n        // @prop vertical - if set to true, the x and y axes will be exchanged\n        prop(vertical: as_bool) { w.set_property(\"vertical\", vertical); },\n    });\n    Ok(w)\n}\n\nconst WIDGET_NAME_SYSTRAY: &str = \"systray\";\n/// @widget systray\n/// @desc Tray for system notifier icons\nfn build_systray(bargs: &mut BuilderArgs) -> Result<gtk::Box> {\n    let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0);\n    let props = Rc::new(systray::Props::new());\n    let props_clone = props.clone(); // copies for def_widget\n    let props_clone2 = props.clone(); // copies for def_widget\n\n    def_widget!(bargs, _g, gtk_widget, {\n        // @prop spacing - spacing between elements\n        prop(spacing: as_i32 = 0) { gtk_widget.set_spacing(spacing) },\n        // @prop orientation - orientation of the box. possible values: $orientation\n        prop(orientation: as_string) { gtk_widget.set_orientation(parse_orientation(&orientation)?) },\n        // @prop space-evenly - space the widgets evenly.\n        prop(space_evenly: as_bool = true) { gtk_widget.set_homogeneous(space_evenly) },\n        // @prop icon-size - size of icons in the tray\n        prop(icon_size: as_i32) {\n            if icon_size <= 0 {\n                log::warn!(\"Icon size is not a positive number\");\n            } else {\n                props.icon_size(icon_size);\n            }\n        },\n        // @prop prepend-new - prepend new icons.\n        prop(prepend_new: as_bool = true) {\n            *props_clone2.prepend_new.borrow_mut() = prepend_new;\n        },\n    });\n\n    systray::spawn_systray(&gtk_widget, &props_clone);\n\n    Ok(gtk_widget)\n}\n\n/// @var orientation - \"vertical\", \"v\", \"horizontal\", \"h\"\nfn parse_orientation(o: &str) -> Result<gtk::Orientation> {\n    enum_parse! { \"orientation\", o,\n        \"vertical\" | \"v\" => gtk::Orientation::Vertical,\n        \"horizontal\" | \"h\" => gtk::Orientation::Horizontal,\n    }\n}\n\nenum DragEntryType {\n    File,\n    Text,\n}\n\n/// @var dragtype - \"file\", \"text\"\nfn parse_dragtype(o: &str) -> Result<DragEntryType> {\n    enum_parse! { \"dragtype\", o,\n        \"file\" => DragEntryType::File,\n        \"text\" => DragEntryType::Text,\n    }\n}\n\n/// @var transition - \"slideright\", \"slideleft\", \"slideup\", \"slidedown\", \"crossfade\", \"none\"\nfn parse_revealer_transition(t: &str) -> Result<gtk::RevealerTransitionType> {\n    enum_parse! { \"transition\", t,\n        \"slideright\" => gtk::RevealerTransitionType::SlideRight,\n        \"slideleft\" => gtk::RevealerTransitionType::SlideLeft,\n        \"slideup\" => gtk::RevealerTransitionType::SlideUp,\n        \"slidedown\" => gtk::RevealerTransitionType::SlideDown,\n        \"fade\" | \"crossfade\" => gtk::RevealerTransitionType::Crossfade,\n        \"none\" => gtk::RevealerTransitionType::None,\n    }\n}\n\n/// @var transition - \"slideright\", \"slideleft\", \"slideup\", \"slidedown\", \"crossfade\", \"none\"\nfn parse_stack_transition(t: &str) -> Result<gtk::StackTransitionType> {\n    enum_parse! { \"transition\", t,\n        \"slideright\" => gtk::StackTransitionType::SlideRight,\n        \"slideleft\" => gtk::StackTransitionType::SlideLeft,\n        \"slideup\" => gtk::StackTransitionType::SlideUp,\n        \"slidedown\" => gtk::StackTransitionType::SlideDown,\n        \"fade\" | \"crossfade\" => gtk::StackTransitionType::Crossfade,\n        \"none\" => gtk::StackTransitionType::None,\n    }\n}\n\n/// @var alignment - \"fill\", \"baseline\", \"center\", \"start\", \"end\"\nfn parse_align(o: &str) -> Result<gtk::Align> {\n    enum_parse! { \"alignment\", o,\n        \"fill\" => gtk::Align::Fill,\n        \"baseline\" => gtk::Align::Baseline,\n        \"center\" => gtk::Align::Center,\n        \"start\" => gtk::Align::Start,\n        \"end\" => gtk::Align::End,\n    }\n}\n\n/// @var justification - \"left\", \"right\", \"center\", \"fill\"\nfn parse_justification(j: &str) -> Result<gtk::Justification> {\n    enum_parse! { \"justification\", j,\n        \"left\" => gtk::Justification::Left,\n        \"right\" => gtk::Justification::Right,\n        \"center\" => gtk::Justification::Center,\n        \"fill\" => gtk::Justification::Fill,\n    }\n}\n\n/// @var position - \"left\", \"right\", \"top\", \"bottom\"\nfn parse_position_type(g: &str) -> Result<gtk::PositionType> {\n    enum_parse! { \"position\", g,\n        \"left\" => gtk::PositionType::Left,\n        \"right\" => gtk::PositionType::Right,\n        \"top\" => gtk::PositionType::Top,\n        \"bottom\" => gtk::PositionType::Bottom,\n    }\n}\n\n/// @var gravity - \"south\", \"east\", \"west\", \"north\", \"auto\"\nfn parse_gravity(g: &str) -> Result<gtk::pango::Gravity> {\n    enum_parse! { \"gravity\", g,\n        \"south\" => gtk::pango::Gravity::South,\n        \"east\" => gtk::pango::Gravity::East,\n        \"west\" => gtk::pango::Gravity::West,\n        \"north\" => gtk::pango::Gravity::North,\n        \"auto\" => gtk::pango::Gravity::Auto,\n    }\n}\n\n/// @var wrap_mode - \"word\", \"char\", \"wordchar\"\nfn parse_wrap_mode(w: &str) -> Result<gtk::pango::WrapMode> {\n    enum_parse! { \"wrap-mode\", w,\n        \"word\" => gtk::pango::WrapMode::Word,\n        \"char\" => gtk::pango::WrapMode::Char,\n        \"wordchar\" => gtk::pango::WrapMode::WordChar\n    }\n}\n\n/// Connect a function to the first map event of a widget. After that first map, the handler will get disconnected.\nfn connect_first_map<W: IsA<gtk::Widget>, F: Fn(&W) + 'static>(widget: &W, func: F) {\n    let signal_handler_id = std::rc::Rc::new(std::cell::RefCell::new(None));\n\n    signal_handler_id.borrow_mut().replace(widget.connect_map({\n        let signal_handler_id = signal_handler_id.clone();\n        move |w| {\n            if let Some(signal_handler_id) = signal_handler_id.borrow_mut().take() {\n                w.disconnect(signal_handler_id);\n            }\n            func(w)\n        }\n    }));\n}\n"
  },
  {
    "path": "crates/eww/src/widgets/window.rs",
    "content": "use gtk::glib::{self, object_subclass, wrapper, Properties};\nuse gtk::{prelude::*, subclass::prelude::*};\nuse std::cell::RefCell;\n\nwrapper! {\n    pub struct Window(ObjectSubclass<WindowPriv>)\n    @extends gtk::Window, gtk::Bin, gtk::Container, gtk::Widget, @implements gtk::Buildable;\n}\n\n#[derive(Properties)]\n#[properties(wrapper_type = Window)]\npub struct WindowPriv {\n    #[property(get, name = \"x\", nick = \"X\", blurb = \"Global x coordinate\", default = 0)]\n    x: RefCell<i32>,\n\n    #[property(get, name = \"y\", nick = \"Y\", blurb = \"Global y coordinate\", default = 0)]\n    y: RefCell<i32>,\n}\n\n// This should match the default values from the ParamSpecs\nimpl Default for WindowPriv {\n    fn default() -> Self {\n        WindowPriv { x: RefCell::new(0), y: RefCell::new(0) }\n    }\n}\n\n#[object_subclass]\nimpl ObjectSubclass for WindowPriv {\n    type ParentType = gtk::Window;\n    type Type = Window;\n\n    const NAME: &'static str = \"WindowEww\";\n}\n\nimpl Default for Window {\n    fn default() -> Self {\n        glib::Object::new::<Self>()\n    }\n}\n\nimpl Window {\n    pub fn new(type_: gtk::WindowType, x_: i32, y_: i32) -> Self {\n        let w: Self = glib::Object::builder().property(\"type\", type_).build();\n        let priv_ = w.imp();\n        priv_.x.replace(x_);\n        priv_.y.replace(y_);\n        w\n    }\n}\n\nimpl ObjectImpl for WindowPriv {\n    fn properties() -> &'static [glib::ParamSpec] {\n        Self::derived_properties()\n    }\n\n    fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {\n        self.derived_property(id, pspec)\n    }\n}\nimpl WindowImpl for WindowPriv {}\nimpl BinImpl for WindowPriv {}\nimpl ContainerImpl for WindowPriv {}\nimpl WidgetImpl for WindowPriv {}\n"
  },
  {
    "path": "crates/eww/src/window_arguments.rs",
    "content": "use anyhow::{bail, Context, Result};\nuse eww_shared_util::VarName;\nuse simplexpr::dynval::DynVal;\nuse std::{\n    collections::{HashMap, HashSet},\n    str::FromStr,\n};\nuse yuck::{\n    config::{monitor::MonitorIdentifier, window_definition::WindowDefinition, window_geometry::AnchorPoint},\n    value::Coords,\n};\n\nfn parse_value_from_args<T: FromStr>(name: &str, args: &mut HashMap<VarName, DynVal>) -> Result<Option<T>, T::Err> {\n    args.remove(&VarName(name.to_string())).map(|x| FromStr::from_str(&x.as_string().unwrap())).transpose()\n}\n\n/// This stores the arguments given in the command line to create a window\n/// While creating a window, we combine this with information from the\n/// [`WindowDefinition`] to create a [WindowInitiator](`crate::window_initiator::WindowInitiator`), which stores all the\n/// information required to start a window\n#[derive(Debug, Clone)]\npub struct WindowArguments {\n    /// Name of the window as defined in the eww config\n    pub window_name: String,\n    /// Instance ID of the window\n    pub instance_id: String,\n    pub anchor: Option<AnchorPoint>,\n    pub args: HashMap<VarName, DynVal>,\n    pub duration: Option<std::time::Duration>,\n    pub monitor: Option<MonitorIdentifier>,\n    pub pos: Option<Coords>,\n    pub size: Option<Coords>,\n}\n\nimpl WindowArguments {\n    pub fn new_from_args(id: String, config_name: String, mut args: HashMap<VarName, DynVal>) -> Result<Self> {\n        let initiator = WindowArguments {\n            window_name: config_name,\n            instance_id: id,\n            pos: parse_value_from_args::<Coords>(\"pos\", &mut args)?,\n            size: parse_value_from_args::<Coords>(\"size\", &mut args)?,\n            monitor: parse_value_from_args::<MonitorIdentifier>(\"screen\", &mut args)?,\n            anchor: parse_value_from_args::<AnchorPoint>(\"anchor\", &mut args)?,\n            duration: parse_value_from_args::<DynVal>(\"duration\", &mut args)?\n                .map(|x| x.as_duration())\n                .transpose()\n                .context(\"Not a valid duration\")?,\n            args,\n        };\n\n        Ok(initiator)\n    }\n\n    /// Return a hashmap of all arguments the window was passed and expected, returning\n    /// an error in case required arguments are missing or unexpected arguments are passed.\n    pub fn get_local_window_variables(&self, window_def: &WindowDefinition) -> Result<HashMap<VarName, DynVal>> {\n        let expected_args: HashSet<&String> = window_def.expected_args.iter().map(|x| &x.name.0).collect();\n        let mut local_variables: HashMap<VarName, DynVal> = HashMap::new();\n\n        // Ensure that the arguments passed to the window that are already interpreted by eww (id, screen)\n        // are set to the correct values\n        if expected_args.contains(&String::from(\"id\")) {\n            local_variables.insert(VarName::from(\"id\"), DynVal::from(self.instance_id.clone()));\n        }\n        if self.monitor.is_some() && expected_args.contains(&String::from(\"screen\")) {\n            let mon_dyn = DynVal::from(&self.monitor.clone().unwrap());\n            local_variables.insert(VarName::from(\"screen\"), mon_dyn);\n        }\n\n        local_variables.extend(self.args.clone());\n\n        for attr in &window_def.expected_args {\n            let name = VarName::from(attr.name.clone());\n            if !local_variables.contains_key(&name) && !attr.optional {\n                bail!(\"Error, missing argument '{}' when creating window with id '{}'\", attr.name, self.instance_id);\n            }\n        }\n\n        if local_variables.len() != window_def.expected_args.len() {\n            let unexpected_vars: Vec<_> = local_variables.keys().filter(|&n| !expected_args.contains(&n.0)).cloned().collect();\n            bail!(\n                \"variables {} unexpectedly defined when creating window with id '{}'\",\n                unexpected_vars.join(\", \"),\n                self.instance_id,\n            );\n        }\n\n        Ok(local_variables)\n    }\n}\n"
  },
  {
    "path": "crates/eww/src/window_initiator.rs",
    "content": "use anyhow::Result;\nuse eww_shared_util::{AttrName, VarName};\nuse simplexpr::dynval::DynVal;\nuse std::collections::HashMap;\nuse yuck::config::{\n    backend_window_options::BackendWindowOptions,\n    monitor::MonitorIdentifier,\n    window_definition::{WindowDefinition, WindowStacking},\n    window_geometry::WindowGeometry,\n};\n\nuse crate::window_arguments::WindowArguments;\n\n/// This stores all the information required to create a window and is created\n/// via combining information from the [`WindowDefinition`] and the [`WindowInitiator`]\n#[derive(Debug, Clone)]\npub struct WindowInitiator {\n    pub backend_options: BackendWindowOptions,\n    pub geometry: Option<WindowGeometry>,\n    pub local_variables: HashMap<VarName, DynVal>,\n    pub monitor: Option<MonitorIdentifier>,\n    pub name: String,\n    pub resizable: bool,\n    pub stacking: WindowStacking,\n}\n\nimpl WindowInitiator {\n    pub fn new(window_def: &WindowDefinition, args: &WindowArguments) -> Result<Self> {\n        let vars = args.get_local_window_variables(window_def)?;\n\n        let geometry = match &window_def.geometry {\n            Some(geo) => Some(geo.eval(&vars)?.override_if_given(args.anchor, args.pos, args.size)),\n            None => None,\n        };\n        let monitor = if args.monitor.is_none() { window_def.eval_monitor(&vars)? } else { args.monitor.clone() };\n        Ok(WindowInitiator {\n            backend_options: window_def.backend_options.eval(&vars)?,\n            geometry,\n            monitor,\n            name: window_def.name.clone(),\n            resizable: window_def.eval_resizable(&vars)?,\n            stacking: window_def.eval_stacking(&vars)?,\n            local_variables: vars,\n        })\n    }\n\n    pub fn get_scoped_vars(&self) -> HashMap<AttrName, DynVal> {\n        self.local_variables.iter().map(|(k, v)| (AttrName::from(k.clone()), v.clone())).collect()\n    }\n}\n"
  },
  {
    "path": "crates/eww_shared_util/Cargo.toml",
    "content": "[package]\nname = \"eww_shared_util\"\nversion = \"0.1.0\"\nauthors = [\"elkowar <5300871+elkowar@users.noreply.github.com>\"]\nedition = \"2021\"\nlicense = \"MIT\"\ndescription = \"Utility crate used in eww\"\nrepository = \"https://github.com/elkowar/eww\"\nhomepage = \"https://github.com/elkowar/eww\"\n\n[dependencies]\nserde.workspace = true\nderive_more.workspace = true\nref-cast.workspace = true\nchrono = { workspace = true, features = [\"unstable-locales\"] }\n"
  },
  {
    "path": "crates/eww_shared_util/src/lib.rs",
    "content": "pub mod locale;\npub mod span;\npub mod wrappers;\n\npub use locale::*;\npub use span::*;\npub use wrappers::*;\n\n#[macro_export]\nmacro_rules! snapshot_debug {\n    ( $($name:ident => $test:expr),* $(,)?) => {\n        $(\n            #[test]\n            fn $name() { ::insta::assert_debug_snapshot!($test); }\n        )*\n    };\n}\n#[macro_export]\nmacro_rules! snapshot_string {\n    ( $($name:ident => $test:expr),* $(,)?) => {\n        $(\n            #[test]\n            fn $name() { ::insta::assert_snapshot!($test); }\n        )*\n    };\n}\n\n#[macro_export]\nmacro_rules! snapshot_ron {\n    ( $($name:ident => $test:expr),* $(,)?) => {\n        $(\n            #[test]\n            fn $name() {\n                ::insta::with_settings!({sort_maps => true}, {\n                    ::insta::assert_ron_snapshot!($test);\n                });\n            }\n        )*\n    };\n}\n"
  },
  {
    "path": "crates/eww_shared_util/src/locale.rs",
    "content": "use chrono::Locale;\nuse std::env::var;\n\n/// Returns the `Locale` enum based on the `LC_ALL`, `LC_TIME`, and `LANG` environment variables in\n/// that order, which is the precedence order prescribed by Section 8.2 of POSIX.1-2017.\n/// If the environment variable is not defined or is malformed use the POSIX locale.\npub fn get_locale() -> Locale {\n    var(\"LC_ALL\")\n        .or_else(|_| var(\"LC_TIME\"))\n        .or_else(|_| var(\"LANG\"))\n        .map_or(Locale::POSIX, |v| v.split('.').next().and_then(|x| x.try_into().ok()).unwrap_or_default())\n}\n"
  },
  {
    "path": "crates/eww_shared_util/src/span.rs",
    "content": "/// A span is made up of\n/// - the start location\n/// - the end location\n/// - the file id\n#[derive(Eq, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]\npub struct Span(pub usize, pub usize, pub usize);\n\nimpl Span {\n    pub const DUMMY: Span = Span(usize::MAX, usize::MAX, usize::MAX);\n\n    pub fn point(loc: usize, file_id: usize) -> Self {\n        Span(loc, loc, file_id)\n    }\n\n    /// Get the span that includes this and the other span completely.\n    /// Will panic if the spans are from different file_ids.\n    pub fn to(mut self, other: Span) -> Self {\n        assert!(other.2 == self.2);\n        self.1 = other.1;\n        self\n    }\n\n    pub fn ending_at(mut self, end: usize) -> Self {\n        self.1 = end;\n        self\n    }\n\n    /// Turn this span into a span only highlighting the point it starts at, setting the length to 0.\n    pub fn point_span(mut self) -> Self {\n        self.1 = self.0;\n        self\n    }\n\n    /// Turn this span into a span only highlighting the point it ends at, setting the length to 0.\n    pub fn point_span_at_end(mut self) -> Self {\n        self.0 = self.1;\n        self\n    }\n\n    pub fn shifted(mut self, n: isize) -> Self {\n        self.0 = isize::max(0, self.0 as isize + n) as usize;\n        self.1 = isize::max(0, self.0 as isize + n) as usize;\n        self\n    }\n\n    pub fn new_relative(mut self, other_start: usize, other_end: usize) -> Self {\n        self.0 += other_start;\n        self.1 += other_end;\n        self\n    }\n\n    pub fn is_dummy(&self) -> bool {\n        *self == Self::DUMMY\n    }\n}\n\nimpl std::fmt::Display for Span {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        if self.is_dummy() {\n            write!(f, \"DUMMY\")\n        } else {\n            write!(f, \"{}..{}\", self.0, self.1)\n        }\n    }\n}\n\nimpl std::fmt::Debug for Span {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self)\n    }\n}\n\npub trait Spanned {\n    fn span(&self) -> Span;\n}\n"
  },
  {
    "path": "crates/eww_shared_util/src/wrappers.rs",
    "content": "use derive_more::{Debug, *};\nuse ref_cast::RefCast;\nuse serde::{Deserialize, Serialize};\n\n/// The name of a variable\n#[repr(transparent)]\n#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, Debug, RefCast)]\n#[debug(\"VarName({})\", _0)]\npub struct VarName(pub String);\n\nimpl std::borrow::Borrow<str> for VarName {\n    fn borrow(&self) -> &str {\n        &self.0\n    }\n}\n\nimpl AttrName {\n    pub fn to_attr_name_ref(&self) -> &AttrName {\n        AttrName::ref_cast(&self.0)\n    }\n}\n\nimpl From<&str> for VarName {\n    fn from(s: &str) -> Self {\n        VarName(s.to_owned())\n    }\n}\n\nimpl From<AttrName> for VarName {\n    fn from(x: AttrName) -> Self {\n        VarName(x.0)\n    }\n}\n\n/// The name of an attribute\n#[repr(transparent)]\n#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, Debug, RefCast)]\n#[debug(\"AttrName({})\", _0)]\npub struct AttrName(pub String);\n\nimpl AttrName {\n    pub fn to_var_name_ref(&self) -> &VarName {\n        VarName::ref_cast(&self.0)\n    }\n}\n\nimpl std::borrow::Borrow<str> for AttrName {\n    fn borrow(&self) -> &str {\n        &self.0\n    }\n}\n\nimpl From<&str> for AttrName {\n    fn from(s: &str) -> Self {\n        AttrName(s.to_owned())\n    }\n}\n\nimpl From<VarName> for AttrName {\n    fn from(x: VarName) -> Self {\n        AttrName(x.0)\n    }\n}\n"
  },
  {
    "path": "crates/notifier_host/Cargo.toml",
    "content": "[package]\nname = \"notifier_host\"\nversion = \"0.1.0\"\nauthors = [\"elkowar <5300871+elkowar@users.noreply.github.com>\"]\nedition = \"2021\"\nlicense = \"MIT\"\ndescription = \"SystemNotifierHost implementation\"\nrepository = \"https://github.com/elkowar/eww\"\nhomepage = \"https://github.com/elkowar/eww\"\n\n[dependencies]\ndbusmenu-gtk3 = \"0.1.0\"\nquick-xml = { version = \"0.37.1\", features = [\"serialize\"] }\nserde = \"1.0.215\"\n\ngtk.workspace = true\nlog.workspace = true\nthiserror.workspace = true\ntokio = { workspace = true, features = [\"full\"] }\nzbus = { workspace = true, default-features = false, features = [\"tokio\"] }\n"
  },
  {
    "path": "crates/notifier_host/src/host.rs",
    "content": "use crate::*;\n\nuse zbus::export::ordered_stream::{self, OrderedStreamExt};\n\n/// Trait for system tray implementations, to be notified of changes to what items are in the tray.\npub trait Host {\n    /// Called when an item is added to the tray. This is also called for all existing items when\n    /// starting [`run_host`].\n    fn add_item(&mut self, id: &str, item: Item);\n\n    /// Called when an item is removed from the tray.\n    fn remove_item(&mut self, id: &str);\n}\n\n// TODO We aren't really thinking about what happens when we shut down a host. Currently, we don't\n// provide a way to unregister as a host.\n//\n// It would also be good to combine `register_as_host` and `run_host`, so that we're only\n// registered while we're running.\n\n/// Register this DBus connection as a StatusNotifierHost (i.e. system tray).\n///\n/// This associates with the DBus connection new name of the format\n/// `org.freedesktop.StatusNotifierHost-{pid}-{nr}`, and registers it to active\n/// StatusNotifierWatcher. The name and the StatusNotifierWatcher proxy are returned.\n///\n/// You still need to call [`run_host`] to have the instance of [`Host`] be notified of new and\n/// removed items.\npub async fn register_as_host(\n    con: &zbus::Connection,\n) -> zbus::Result<(zbus::names::WellKnownName<'static>, proxy::StatusNotifierWatcherProxy<'static>)> {\n    let snw = proxy::StatusNotifierWatcherProxy::new(con).await?;\n\n    // get a well-known name\n    let pid = std::process::id();\n    let mut i = 0;\n    let wellknown = loop {\n        use zbus::fdo::RequestNameReply::*;\n\n        i += 1;\n        let wellknown = format!(\"org.freedesktop.StatusNotifierHost-{}-{}\", pid, i);\n        let wellknown: zbus::names::WellKnownName = wellknown.try_into().expect(\"generated well-known name is invalid\");\n\n        let flags = [zbus::fdo::RequestNameFlags::DoNotQueue];\n        match con.request_name_with_flags(&wellknown, flags.into_iter().collect()).await? {\n            PrimaryOwner => break wellknown,\n            Exists => {}\n            AlreadyOwner => {}\n            InQueue => unreachable!(\"request_name_with_flags returned InQueue even though we specified DoNotQueue\"),\n        };\n    };\n\n    // register it to the StatusNotifierWatcher, so that they know there is a systray on the system\n    snw.register_status_notifier_host(&wellknown).await?;\n\n    Ok((wellknown, snw))\n}\n\n/// Run the Host forever, calling its methods as signals are received from the StatusNotifierWatcher.\n///\n/// Before calling this, you should have called [`register_as_host`] (which returns an instance of\n/// [`proxy::StatusNotifierWatcherProxy`]).\n///\n/// This async function runs forever, and only returns if it gets an error! As such, it is\n/// recommended to call this via something like `tokio::spawn` that runs this in the\n/// background.\npub async fn run_host(host: &mut dyn Host, snw: &proxy::StatusNotifierWatcherProxy<'static>) -> zbus::Error {\n    // Replacement for ? operator since we're not returning a Result.\n    macro_rules! try_ {\n        ($e:expr) => {\n            match $e {\n                Ok(x) => x,\n                Err(e) => return e,\n            }\n        };\n    }\n\n    enum ItemEvent {\n        NewItem(proxy::StatusNotifierItemRegistered),\n        GoneItem(proxy::StatusNotifierItemUnregistered),\n    }\n\n    // start listening to these streams\n    let new_items = try_!(snw.receive_status_notifier_item_registered().await);\n    let gone_items = try_!(snw.receive_status_notifier_item_unregistered().await);\n\n    let mut item_names = std::collections::HashSet::new();\n\n    // initial items first\n    for svc in try_!(snw.registered_status_notifier_items().await) {\n        match Item::from_address(snw.connection(), &svc).await {\n            Ok(item) => {\n                item_names.insert(svc.to_owned());\n                host.add_item(&svc, item);\n            }\n            Err(e) => {\n                log::warn!(\"Could not create StatusNotifierItem from address {:?}: {:?}\", svc, e);\n            }\n        }\n    }\n\n    let mut ev_stream = ordered_stream::join(\n        OrderedStreamExt::map(new_items, ItemEvent::NewItem),\n        OrderedStreamExt::map(gone_items, ItemEvent::GoneItem),\n    );\n    while let Some(ev) = ev_stream.next().await {\n        match ev {\n            ItemEvent::NewItem(sig) => {\n                let svc = try_!(sig.args()).service;\n                if item_names.contains(svc) {\n                    log::info!(\"Got duplicate new item: {:?}\", svc);\n                } else {\n                    match Item::from_address(snw.connection(), svc).await {\n                        Ok(item) => {\n                            item_names.insert(svc.to_owned());\n                            host.add_item(svc, item);\n                        }\n                        Err(e) => {\n                            log::warn!(\"Could not create StatusNotifierItem from address {:?}: {:?}\", svc, e);\n                        }\n                    }\n                }\n            }\n            ItemEvent::GoneItem(sig) => {\n                let svc = try_!(sig.args()).service;\n                if item_names.remove(svc) {\n                    host.remove_item(svc);\n                }\n            }\n        }\n    }\n\n    // I do not know whether this is possible to reach or not.\n    unreachable!(\"StatusNotifierWatcher stopped producing events\")\n}\n"
  },
  {
    "path": "crates/notifier_host/src/icon.rs",
    "content": "use crate::*;\n\nuse gtk::{self, prelude::*};\n\n#[derive(thiserror::Error, Debug)]\nenum IconError {\n    #[error(\"while fetching icon name: {0}\")]\n    DBusIconName(#[source] zbus::Error),\n    #[error(\"while fetching icon theme path: {0}\")]\n    DBusTheme(#[source] zbus::Error),\n    #[error(\"while fetching pixmap: {0}\")]\n    DBusPixmap(#[source] zbus::Error),\n    #[error(\"loading icon from file {path:?}\")]\n    LoadIconFromFile {\n        path: String,\n        #[source]\n        source: gtk::glib::Error,\n    },\n    #[error(\"loading icon {icon_name:?} from theme {}\", .theme_path.as_ref().unwrap_or(&\"(default)\".to_owned()))]\n    LoadIconFromTheme {\n        icon_name: String,\n        theme_path: Option<String>,\n        #[source]\n        source: gtk::glib::Error,\n    },\n    #[error(\"no icon available\")]\n    NotAvailable,\n}\n\n/// Get the fallback GTK icon, as a final fallback if the tray item has no icon.\nasync fn fallback_icon(size: i32, scale: i32) -> Option<gtk::gdk_pixbuf::Pixbuf> {\n    let theme = gtk::IconTheme::default().expect(\"Could not get default gtk theme\");\n    match theme.load_icon_for_scale(\"image-missing\", size, scale, gtk::IconLookupFlags::FORCE_SIZE) {\n        Ok(pb) => pb,\n        Err(e) => {\n            log::error!(\"failed to load \\\"image-missing\\\" from default theme: {}\", e);\n            None\n        }\n    }\n}\n\n/// Load a pixbuf from StatusNotifierItem's [Icon format].\n///\n/// [Icon format]: https://freedesktop.org/wiki/Specifications/StatusNotifierItem/Icons/\nfn icon_from_pixmap(width: i32, height: i32, mut data: Vec<u8>) -> gtk::gdk_pixbuf::Pixbuf {\n    // We need to convert data from ARGB32 to RGBA32, since that's the only one that gdk-pixbuf\n    // understands.\n    for chunk in data.chunks_exact_mut(4) {\n        let a = chunk[0];\n        let r = chunk[1];\n        let g = chunk[2];\n        let b = chunk[3];\n        chunk[0] = r;\n        chunk[1] = g;\n        chunk[2] = b;\n        chunk[3] = a;\n    }\n\n    gtk::gdk_pixbuf::Pixbuf::from_bytes(\n        &gtk::glib::Bytes::from_owned(data),\n        gtk::gdk_pixbuf::Colorspace::Rgb,\n        true,\n        8,\n        width,\n        height,\n        width * 4,\n    )\n}\n\n/// From a list of pixmaps, create an icon from the most appropriately sized one.\n///\n/// This function returns None if and only if no pixmaps are provided.\nfn icon_from_pixmaps(pixmaps: Vec<(i32, i32, Vec<u8>)>, size: i32) -> Option<gtk::gdk_pixbuf::Pixbuf> {\n    pixmaps\n        .into_iter()\n        .max_by(|(w1, h1, _), (w2, h2, _)| {\n            // take smallest one bigger than requested size, otherwise take biggest\n            let a = size * size;\n            let a1 = w1 * h1;\n            let a2 = w2 * h2;\n            match (a1 >= a, a2 >= a) {\n                (true, true) => a2.cmp(&a1),\n                (true, false) => std::cmp::Ordering::Greater,\n                (false, true) => std::cmp::Ordering::Less,\n                (false, false) => a1.cmp(&a2),\n            }\n        })\n        .and_then(|(w, h, d)| {\n            let pixbuf = icon_from_pixmap(w, h, d);\n            if w != size || h != size {\n                pixbuf.scale_simple(size, size, gtk::gdk_pixbuf::InterpType::Bilinear)\n            } else {\n                Some(pixbuf)\n            }\n        })\n}\n\n/// Load an icon with a given name from either the default (if `theme_path` is `None`), or from the\n/// theme at a path.\nfn icon_from_name(\n    icon_name: &str,\n    theme_path: Option<&str>,\n    size: i32,\n    scale: i32,\n) -> std::result::Result<gtk::gdk_pixbuf::Pixbuf, IconError> {\n    let theme = if let Some(path) = theme_path {\n        let theme = gtk::IconTheme::new();\n        theme.prepend_search_path(path);\n        theme\n    } else {\n        gtk::IconTheme::default().expect(\"Could not get default gtk theme\")\n    };\n\n    match theme.load_icon_for_scale(icon_name, size, scale, gtk::IconLookupFlags::FORCE_SIZE) {\n        Ok(pb) => Ok(pb.expect(\"no pixbuf from theme.load_icon despite no error\")),\n        Err(e) => Err(IconError::LoadIconFromTheme {\n            icon_name: icon_name.to_owned(),\n            theme_path: theme_path.map(str::to_owned),\n            source: e,\n        }),\n    }\n}\n\npub async fn load_icon_from_sni(\n    sni: &proxy::StatusNotifierItemProxy<'_>,\n    size: i32,\n    scale: i32,\n) -> Option<gtk::gdk_pixbuf::Pixbuf> {\n    // \"Visualizations are encouraged to prefer icon names over icon pixmaps if both are\n    // available.\"\n\n    let scaled_size = size * scale;\n\n    // First, see if we can get an icon from the name they provide, using either the theme they\n    // specify or the default.\n    let icon_from_name: std::result::Result<gtk::gdk_pixbuf::Pixbuf, IconError> = (async {\n        // fetch icon name\n        let icon_name = sni.icon_name().await;\n        log::debug!(\"dbus: {} icon_name -> {:?}\", sni.destination(), icon_name);\n        let icon_name = match icon_name {\n            Ok(s) if s.is_empty() => return Err(IconError::NotAvailable),\n            Ok(s) => s,\n            Err(e) => return Err(IconError::DBusIconName(e)),\n        };\n\n        // interpret it as an absolute path if we can\n        let icon_path = std::path::Path::new(&icon_name);\n        if icon_path.is_absolute() && icon_path.is_file() {\n            return gtk::gdk_pixbuf::Pixbuf::from_file_at_size(icon_path, scaled_size, scaled_size)\n                .map_err(|e| IconError::LoadIconFromFile { path: icon_name, source: e });\n        }\n\n        // otherwise, fetch icon theme and lookup using icon_from_name\n        let icon_theme_path = sni.icon_theme_path().await;\n        log::debug!(\"dbus: {} icon_theme_path -> {:?}\", sni.destination(), icon_theme_path);\n        let icon_theme_path = match icon_theme_path {\n            Ok(p) if p.is_empty() => None,\n            Ok(p) => Some(p),\n            // treat property not existing as the same as it being empty i.e. to use the default\n            // system theme\n            Err(zbus::Error::FDO(e)) => match *e {\n                zbus::fdo::Error::UnknownProperty(_) | zbus::fdo::Error::InvalidArgs(_) => None,\n                // this error is reported by discord, blueman-applet\n                zbus::fdo::Error::Failed(msg) if msg == \"error occurred in Get\" => None,\n                _ => return Err(IconError::DBusTheme(zbus::Error::FDO(e))),\n            },\n            Err(e) => return Err(IconError::DBusTheme(e)),\n        };\n\n        let icon_theme_path: Option<&str> = match &icon_theme_path {\n            // this looks weird but this converts &String to &str\n            Some(s) => Some(s),\n            None => None,\n        };\n        icon_from_name(&icon_name, icon_theme_path, size, scale)\n    })\n    .await;\n\n    match icon_from_name {\n        Ok(p) => return Some(p),           // got an icon!\n        Err(IconError::NotAvailable) => {} // this error is expected, don't log\n        Err(e) => log::warn!(\"failed to get icon by name for {}: {}\", sni.destination(), e),\n    };\n\n    // Can't get it from name + theme, try the pixmap\n    let icon_from_pixmaps = match sni.icon_pixmap().await {\n        Ok(ps) => match icon_from_pixmaps(ps, scaled_size) {\n            Some(p) => Ok(p),\n            None => Err(IconError::NotAvailable),\n        },\n        Err(zbus::Error::FDO(e)) => match *e {\n            // property not existing is an expected error\n            zbus::fdo::Error::UnknownProperty(_) | zbus::fdo::Error::InvalidArgs(_) => Err(IconError::NotAvailable),\n\n            _ => Err(IconError::DBusPixmap(zbus::Error::FDO(e))),\n        },\n        Err(e) => Err(IconError::DBusPixmap(e)),\n    };\n    match icon_from_pixmaps {\n        Ok(p) => return Some(p),\n        Err(IconError::NotAvailable) => {}\n        Err(e) => log::warn!(\"failed to get icon pixmap for {}: {}\", sni.destination(), e),\n    };\n\n    // Tray didn't provide a valid icon so use the default fallback one.\n    fallback_icon(size, scale).await\n}\n"
  },
  {
    "path": "crates/notifier_host/src/item.rs",
    "content": "use crate::*;\n\nuse gtk::{self, prelude::*};\nuse serde::Deserialize;\nuse zbus::fdo::IntrospectableProxy;\n\n/// Recognised values of [`org.freedesktop.StatusNotifierItem.Status`].\n///\n/// [`org.freedesktop.StatusNotifierItem.Status`]: https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierItem/#org.freedesktop.statusnotifieritem.status\n#[derive(Debug, Clone, Copy)]\npub enum Status {\n    /// The item doesn't convey important information to the user, it can be considered an \"idle\"\n    /// status and is likely that visualizations will chose to hide it.\n    Passive,\n    /// The item is active, is more important that the item will be shown in some way to the user.\n    Active,\n    /// The item carries really important information for the user, such as battery charge running\n    /// out and is wants to incentive the direct user intervention. Visualizations should emphasize\n    /// in some way the items with NeedsAttention status.\n    NeedsAttention,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\n#[non_exhaustive]\npub struct ParseStatusError;\n\nimpl std::str::FromStr for Status {\n    type Err = ParseStatusError;\n\n    fn from_str(s: &str) -> std::result::Result<Self, ParseStatusError> {\n        match s {\n            \"Passive\" => Ok(Status::Passive),\n            \"Active\" => Ok(Status::Active),\n            \"NeedsAttention\" => Ok(Status::NeedsAttention),\n            _ => Err(ParseStatusError),\n        }\n    }\n}\n\n/// A StatusNotifierItem (SNI).\n///\n/// At the moment, this does not wrap much of the SNI's properties and methods. As such, you should\n/// directly access the `sni` member as needed for functionalty that is not provided.\npub struct Item {\n    /// The StatusNotifierItem that is wrapped by this instance.\n    pub sni: proxy::StatusNotifierItemProxy<'static>,\n    gtk_menu: Option<dbusmenu_gtk3::Menu>,\n}\n\nimpl Item {\n    /// Create an instance from the service's address.\n    ///\n    /// The format of `addr` is `{bus}{object_path}` (e.g.\n    /// `:1.50/org/ayatana/NotificationItem/nm_applet`), which is the format that is used for\n    /// StatusNotifierWatcher's [RegisteredStatusNotifierItems property][rsni]).\n    ///\n    /// [rsni]: https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/#registeredstatusnotifieritems\n    pub async fn from_address(con: &zbus::Connection, service: &str) -> zbus::Result<Self> {\n        let (addr, path) = {\n            // Based on <https://github.com/oknozor/stray/blob/main/stray/src/notifier_watcher/notifier_address.rs>\n            //\n            // TODO is the service name format actually documented anywhere?\n            if let Some((addr, path)) = service.split_once('/') {\n                (addr.to_owned(), format!(\"/{}\", path))\n            } else if service.starts_with(':') {\n                (\n                    service.to_owned(),\n                    resolve_pathless_address(con, service, \"/\".to_owned())\n                        .await?\n                        .ok_or_else(|| zbus::Error::Failure(format!(\"no StatusNotifierItem found for {service}\")))?,\n                )\n            } else {\n                return Err(zbus::Error::Address(service.to_owned()));\n            }\n        };\n\n        let sni = proxy::StatusNotifierItemProxy::builder(con).destination(addr)?.path(path)?.build().await?;\n\n        Ok(Self { sni, gtk_menu: None })\n    }\n\n    /// Get the current status of the item.\n    pub async fn status(&self) -> zbus::Result<Status> {\n        let status = self.sni.status().await?;\n        match status.parse() {\n            Ok(s) => Ok(s),\n            Err(_) => Err(zbus::Error::Failure(format!(\"Invalid status {:?}\", status))),\n        }\n    }\n\n    pub async fn set_menu(&mut self, widget: &gtk::EventBox) -> zbus::Result<()> {\n        let menu = dbusmenu_gtk3::Menu::new(self.sni.destination(), &self.sni.menu().await?);\n        menu.set_attach_widget(Some(widget));\n        self.gtk_menu = Some(menu);\n        Ok(())\n    }\n\n    pub async fn popup_menu(&self, event: &gtk::gdk::EventButton, x: i32, y: i32) -> zbus::Result<()> {\n        if let Some(menu) = &self.gtk_menu {\n            menu.popup_at_pointer(event.downcast_ref::<gtk::gdk::Event>());\n            Ok(())\n        } else {\n            self.sni.context_menu(x, y).await\n        }\n    }\n\n    /// Get the current icon.\n    pub async fn icon(&self, size: i32, scale: i32) -> Option<gtk::gdk_pixbuf::Pixbuf> {\n        // TODO explain what size and scale mean here\n\n        // see icon.rs\n        load_icon_from_sni(&self.sni, size, scale).await\n    }\n}\n\n#[derive(Deserialize)]\nstruct DBusNode {\n    #[serde(default)]\n    interface: Vec<DBusInterface>,\n\n    #[serde(default)]\n    node: Vec<DBusNode>,\n\n    #[serde(rename = \"@name\")]\n    name: Option<String>,\n}\n\n#[derive(Deserialize)]\nstruct DBusInterface {\n    #[serde(rename = \"@name\")]\n    name: String,\n}\n\nasync fn resolve_pathless_address(con: &zbus::Connection, service: &str, path: String) -> zbus::Result<Option<String>> {\n    let introspection_xml =\n        IntrospectableProxy::builder(con).destination(service)?.path(path.as_str())?.build().await?.introspect().await?;\n\n    let dbus_node =\n        quick_xml::de::from_str::<DBusNode>(&introspection_xml).map_err(|err| zbus::Error::Failure(err.to_string()))?;\n\n    if dbus_node.interface.iter().any(|interface| interface.name == \"org.kde.StatusNotifierItem\") {\n        // This item implements the desired interface, so bubble it back up\n        Ok(Some(path))\n    } else {\n        for node in dbus_node.node {\n            if let Some(name) = node.name {\n                if name == \"StatusNotifierItem\" {\n                    // If this exists, then there's a good chance DBus may not think anything\n                    // implements the desired interface, so just bubble this up instead.\n                    return Ok(Some(join_to_path(&path, name)));\n                }\n\n                let path = Box::pin(resolve_pathless_address(con, service, join_to_path(&path, name))).await?;\n\n                if path.is_some() {\n                    // Return the first item found from a child\n                    return Ok(path);\n                }\n            }\n        }\n\n        // No children had the item we want...\n        Ok(None)\n    }\n}\n\nfn join_to_path(path: &str, name: String) -> String {\n    // Make sure we don't double-up on the leading slash\n    format!(\"{path}/{name}\", path = if path == \"/\" { \"\" } else { path })\n}\n"
  },
  {
    "path": "crates/notifier_host/src/lib.rs",
    "content": "//! The system tray side of the [notifier host DBus\n//! protocols](https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierHost/),\n//! implementing most of the relevant DBus protocol logic so system tray implementations (e.g. eww)\n//! don't need to care about them.\n//!\n//! This crate does not implement the tray icon side of the protocol. For that, see, for example,\n//! the [ksni](https://crates.io/crates/ksni) crate.\n//!\n//! # Overview / Notes for Contributors\n//!\n//! This crate makes extensive use of the `zbus` library to interact with DBus. You should read\n//! through the [zbus tutorial](https://dbus2.github.io/zbus/) if you aren't familiar with DBus or\n//! `zbus`.\n//!\n//! There are two separate services that are required for the tray side of the protocol:\n//!\n//! - `StatusNotifierWatcher`, a service which tracks what items and trays there are but doesn't do\n//!     any rendering. This is implemented by [`Watcher`] (see that for further details), and\n//!     should always be started alongside the `StatusNotifierHost`.\n//!\n//! - `StatusNotifierHost`, the actual tray, which registers itself to the StatusNotifierHost and\n//!     subscribes to its signals to know what items exist. This DBus service has a completely\n//!     empty interface, but is mainly by StatusNotifierWatcher to know when trays disappear. This\n//!     is represented by the [`Host`] trait.\n//!\n//! The actual tray implements the [`Host`] trait to be notified of when items (called\n//! `StatusNotifierItem` in the spec and represented by [`Item`]) appear and disappear, then calls\n//! [`run_host`] to run the DBus side of the protocol.\n//!\n//! If there are multiple trays running on the system, there can be multiple `StatusNotifierHost`s,\n//! but only one `StatusNotifierWatcher` (usually from whatever tray was started first).\n\npub mod proxy;\n\nmod host;\npub use host::*;\n\nmod icon;\npub use icon::*;\n\nmod item;\npub use item::*;\n\nmod watcher;\npub use watcher::*;\n\npub(crate) mod names {\n    pub const WATCHER_BUS: &str = \"org.kde.StatusNotifierWatcher\";\n    pub const WATCHER_OBJECT: &str = \"/StatusNotifierWatcher\";\n\n    pub const ITEM_OBJECT: &str = \"/StatusNotifierItem\";\n}\n"
  },
  {
    "path": "crates/notifier_host/src/proxy/dbus_menu.xml",
    "content": "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n<node>\n<interface name=\"com.canonical.dbusmenu\">\n    <!-- Properties -->\n    <property name=\"Version\" type=\"u\" access=\"read\" />\n    <property name=\"TextDirection\" type=\"s\" access=\"read\" />\n    <property name=\"Status\" type=\"s\" access=\"read\" />\n    <property name=\"IconThemePath\" type=\"as\" access=\"read\" />\n\n    <!-- Functions -->\n    <method name=\"GetLayout\">\n        <arg type=\"i\" name=\"parentId\" direction=\"in\" />\n        <arg type=\"i\" name=\"recursionDepth\" direction=\"in\" />\n        <arg type=\"as\" name=\"propertyNames\" direction=\"in\" />\n        <arg type=\"u\" name=\"revision\" direction=\"out\" />\n        <arg type=\"(ia{sv}av)\" name=\"layout\" direction=\"out\" />\n    </method>\n\n    <method name=\"GetGroupProperties\">\n        <arg type=\"ai\" name=\"ids\" direction=\"in\" />\n        <arg type=\"as\" name=\"propertyNames\" direction=\"in\" />\n        <arg type=\"a(ia{sv})\" name=\"properties\" direction=\"out\" />\n    </method>\n\n    <method name=\"GetProperty\">\n        <arg type=\"i\" name=\"id\" direction=\"in\" />\n        <arg type=\"s\" name=\"name\" direction=\"in\" />\n        <arg type=\"v\" name=\"value\" direction=\"out\" />\n    </method>\n\n    <method name=\"Event\">\n        <arg type=\"i\" name=\"id\" direction=\"in\" />\n        <arg type=\"s\" name=\"eventId\" direction=\"in\" />\n        <arg type=\"v\" name=\"data\" direction=\"in\" />\n        <arg type=\"u\" name=\"timestamp\" direction=\"in\" />\n    </method>\n\n    <method name=\"EventGroup\">\n        <arg type=\"a(isvu)\" name=\"events\" direction=\"in\" />\n        <arg type=\"ai\" name=\"idErrors\" direction=\"out\" />\n    </method>\n\n    <method name=\"AboutToShow\">\n        <arg type=\"i\" name=\"id\" direction=\"in\" />\n        <arg type=\"b\" name=\"needUpdate\" direction=\"out\" />\n    </method>\n\n    <method name=\"AboutToShowGroup\">\n        <arg type=\"ai\" name=\"ids\" direction=\"in\" />\n        <arg type=\"ai\" name=\"updatesNeeded\" direction=\"out\" />\n        <arg type=\"ai\" name=\"idErrors\" direction=\"out\" />\n    </method>\n\n    <!-- Signals -->\n    <signal name=\"ItemsPropertiesUpdated\">\n        <arg type=\"a(ia{sv})\" name=\"updatedProps\" direction=\"out\" />\n        <arg type=\"a(ias)\" name=\"removedProps\" direction=\"out\" />\n    </signal>\n    <signal name=\"LayoutUpdated\">\n        <arg type=\"u\" name=\"revision\" direction=\"out\" />\n        <arg type=\"i\" name=\"parent\" direction=\"out\" />\n    </signal>\n    <signal name=\"ItemActivationRequested\">\n        <arg type=\"i\" name=\"id\" direction=\"out\" />\n        <arg type=\"u\" name=\"timestamp\" direction=\"out\" />\n    </signal>\n</interface>\n</node>"
  },
  {
    "path": "crates/notifier_host/src/proxy/dbus_status_notifier_item.rs",
    "content": "//! # DBus interface proxy for: `org.kde.StatusNotifierItem`\n//!\n//! This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data.\n//! Source: `dbus-status-notifier-item.xml`.\n//!\n//! You may prefer to adapt it, instead of using it verbatim.\n//!\n//! More information can be found in the\n//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html)\n//! section of the zbus documentation.\n\n// suppress warning from generated code\n#![allow(clippy::type_complexity)]\n\nuse zbus::dbus_proxy;\n\n#[dbus_proxy(interface = \"org.kde.StatusNotifierItem\", assume_defaults = true)]\ntrait StatusNotifierItem {\n    /// Activate method\n    fn activate(&self, x: i32, y: i32) -> zbus::Result<()>;\n\n    /// ContextMenu method\n    fn context_menu(&self, x: i32, y: i32) -> zbus::Result<()>;\n\n    /// Scroll method\n    fn scroll(&self, delta: i32, orientation: &str) -> zbus::Result<()>;\n\n    /// SecondaryActivate method\n    fn secondary_activate(&self, x: i32, y: i32) -> zbus::Result<()>;\n\n    /// NewAttentionIcon signal\n    #[dbus_proxy(signal)]\n    fn new_attention_icon(&self) -> zbus::Result<()>;\n\n    /// NewIcon signal\n    #[dbus_proxy(signal)]\n    fn new_icon(&self) -> zbus::Result<()>;\n\n    /// NewOverlayIcon signal\n    #[dbus_proxy(signal)]\n    fn new_overlay_icon(&self) -> zbus::Result<()>;\n\n    /// NewStatus signal\n    #[dbus_proxy(signal)]\n    fn new_status(&self, status: &str) -> zbus::Result<()>;\n\n    /// NewTitle signal\n    #[dbus_proxy(signal)]\n    fn new_title(&self) -> zbus::Result<()>;\n\n    /// NewToolTip signal\n    #[dbus_proxy(signal)]\n    fn new_tool_tip(&self) -> zbus::Result<()>;\n\n    /// AttentionIconName property\n    #[dbus_proxy(property)]\n    fn attention_icon_name(&self) -> zbus::Result<String>;\n\n    /// AttentionIconPixmap property\n    #[dbus_proxy(property)]\n    fn attention_icon_pixmap(&self) -> zbus::Result<Vec<(i32, i32, Vec<u8>)>>;\n\n    /// AttentionMovieName property\n    #[dbus_proxy(property)]\n    fn attention_movie_name(&self) -> zbus::Result<String>;\n\n    /// Category property\n    #[dbus_proxy(property)]\n    fn category(&self) -> zbus::Result<String>;\n\n    /// IconName property\n    #[dbus_proxy(property(emits_changed_signal = \"false\"))]\n    fn icon_name(&self) -> zbus::Result<String>;\n\n    /// IconPixmap property\n    #[dbus_proxy(property(emits_changed_signal = \"false\"))]\n    fn icon_pixmap(&self) -> zbus::Result<Vec<(i32, i32, Vec<u8>)>>;\n\n    /// IconThemePath property\n    #[dbus_proxy(property)]\n    fn icon_theme_path(&self) -> zbus::Result<String>;\n\n    /// Id property\n    #[dbus_proxy(property)]\n    fn id(&self) -> zbus::Result<String>;\n\n    /// ItemIsMenu property\n    #[dbus_proxy(property)]\n    fn item_is_menu(&self) -> zbus::Result<bool>;\n\n    /// Menu property\n    #[dbus_proxy(property)]\n    fn menu(&self) -> zbus::Result<zbus::zvariant::OwnedObjectPath>;\n\n    /// OverlayIconName property\n    #[dbus_proxy(property)]\n    fn overlay_icon_name(&self) -> zbus::Result<String>;\n\n    /// OverlayIconPixmap property\n    #[dbus_proxy(property)]\n    fn overlay_icon_pixmap(&self) -> zbus::Result<Vec<(i32, i32, Vec<u8>)>>;\n\n    /// Status property\n    #[dbus_proxy(property)]\n    fn status(&self) -> zbus::Result<String>;\n\n    /// Title property\n    #[dbus_proxy(property)]\n    fn title(&self) -> zbus::Result<String>;\n\n    /// ToolTip property\n    #[dbus_proxy(property)]\n    fn tool_tip(&self) -> zbus::Result<(String, Vec<(i32, i32, Vec<u8>)>)>;\n}\n"
  },
  {
    "path": "crates/notifier_host/src/proxy/dbus_status_notifier_item.xml",
    "content": "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n<node>\n  <interface name='org.kde.StatusNotifierItem'>\n    <annotation name=\"org.gtk.GDBus.C.Name\" value=\"Item\" />\n    <method name='ContextMenu'>\n      <arg type='i' direction='in' name='x'/>\n      <arg type='i' direction='in' name='y'/>\n    </method>\n    <method name='Activate'>\n      <arg type='i' direction='in' name='x'/>\n      <arg type='i' direction='in' name='y'/>\n    </method>\n    <method name='SecondaryActivate'>\n      <arg type='i' direction='in' name='x'/>\n      <arg type='i' direction='in' name='y'/>\n    </method>\n    <method name='Scroll'>\n      <arg type='i' direction='in' name='delta'/>\n      <arg type='s' direction='in' name='orientation'/>\n    </method>\n    <signal name='NewTitle'/>\n    <signal name='NewIcon'/>\n    <signal name='NewAttentionIcon'/>\n    <signal name='NewOverlayIcon'/>\n    <signal name='NewToolTip'/>\n    <signal name='NewStatus'>\n      <arg type='s' name='status'/>\n    </signal>\n    <property name='Category' type='s' access='read'/>\n    <property name='Id' type='s' access='read'/>\n    <property name='Title' type='s' access='read'/>\n    <property name='Status' type='s' access='read'/>\n    <!-- See discussion on pull #536\n    <property name='WindowId' type='u' access='read'/>\n    -->\n    <property name='IconThemePath' type='s' access='read'/>\n    <property name='IconName' type='s' access='read'/>\n    <property name='IconPixmap' type='a(iiay)' access='read'/>\n    <property name='OverlayIconName' type='s' access='read'/>\n    <property name='OverlayIconPixmap' type='a(iiay)' access='read'/>\n    <property name='AttentionIconName' type='s' access='read'/>\n    <property name='AttentionIconPixmap' type='a(iiay)' access='read'/>\n    <property name='AttentionMovieName' type='s' access='read'/>\n    <property name='ToolTip' type='(sa(iiay)ss)' access='read'/>\n    <property name='Menu' type='o' access='read'/>\n    <property name='ItemIsMenu' type='b' access='read'/>\n  </interface>\n</node>\n"
  },
  {
    "path": "crates/notifier_host/src/proxy/dbus_status_notifier_watcher.rs",
    "content": "//! # DBus interface proxy for: `org.kde.StatusNotifierWatcher`\n//!\n//! This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data.\n//! Source: `dbus-status-notifier-watcher.xml`.\n//!\n//! You may prefer to adapt it, instead of using it verbatim.\n//!\n//! More information can be found in the\n//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html)\n//! section of the zbus documentation.\n\nuse zbus::dbus_proxy;\n\n#[dbus_proxy(\n    default_service = \"org.kde.StatusNotifierWatcher\",\n    interface = \"org.kde.StatusNotifierWatcher\",\n    default_path = \"/StatusNotifierWatcher\"\n)]\ntrait StatusNotifierWatcher {\n    /// RegisterStatusNotifierHost method\n    fn register_status_notifier_host(&self, service: &str) -> zbus::Result<()>;\n\n    /// RegisterStatusNotifierItem method\n    fn register_status_notifier_item(&self, service: &str) -> zbus::Result<()>;\n\n    /// StatusNotifierHostRegistered signal\n    #[dbus_proxy(signal)]\n    fn status_notifier_host_registered(&self) -> zbus::Result<()>;\n\n    /// StatusNotifierHostUnregistered signal\n    #[dbus_proxy(signal)]\n    fn status_notifier_host_unregistered(&self) -> zbus::Result<()>;\n\n    /// StatusNotifierItemRegistered signal\n    #[dbus_proxy(signal)]\n    fn status_notifier_item_registered(&self, service: &str) -> zbus::Result<()>;\n\n    /// StatusNotifierItemUnregistered signal\n    #[dbus_proxy(signal)]\n    fn status_notifier_item_unregistered(&self, service: &str) -> zbus::Result<()>;\n\n    /// IsStatusNotifierHostRegistered property\n    #[dbus_proxy(property)]\n    fn is_status_notifier_host_registered(&self) -> zbus::Result<bool>;\n\n    /// ProtocolVersion property\n    #[dbus_proxy(property)]\n    fn protocol_version(&self) -> zbus::Result<i32>;\n\n    /// RegisteredStatusNotifierItems property\n    #[dbus_proxy(property)]\n    fn registered_status_notifier_items(&self) -> zbus::Result<Vec<String>>;\n}\n"
  },
  {
    "path": "crates/notifier_host/src/proxy/dbus_status_notifier_watcher.xml",
    "content": "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n<node>\n  <interface name=\"org.kde.StatusNotifierWatcher\">\n    <annotation name=\"org.gtk.GDBus.C.Name\" value=\"Watcher\" />\n\n    <!-- methods -->\n    <method name=\"RegisterStatusNotifierItem\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"RegisterItem\" />\n      <arg name=\"service\" type=\"s\" direction=\"in\"/>\n    </method>\n\n    <method name=\"RegisterStatusNotifierHost\">\n        <annotation name=\"org.gtk.GDBus.C.Name\" value=\"RegisterHost\" />\n        <arg name=\"service\" type=\"s\" direction=\"in\"/>\n    </method>\n\n\n    <!-- properties -->\n\n    <property name=\"RegisteredStatusNotifierItems\" type=\"as\" access=\"read\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"RegisteredItems\" />\n      <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"QStringList\"/>\n    </property>\n\n    <property name=\"IsStatusNotifierHostRegistered\" type=\"b\" access=\"read\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"IsHostRegistered\" />\n    </property>\n\n    <property name=\"ProtocolVersion\" type=\"i\" access=\"read\"/>\n\n\n    <!-- signals -->\n\n    <signal name=\"StatusNotifierItemRegistered\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"ItemRegistered\" />\n      <arg type=\"s\" direction=\"out\" name=\"service\" />\n    </signal>\n\n    <signal name=\"StatusNotifierItemUnregistered\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"ItemUnregistered\" />\n      <arg type=\"s\" direction=\"out\" name=\"service\" />\n    </signal>\n\n    <signal name=\"StatusNotifierHostRegistered\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"HostRegistered\" />\n    </signal>\n\n    <signal name=\"StatusNotifierHostUnregistered\">\n      <annotation name=\"org.gtk.GDBus.C.Name\" value=\"HostUnregistered\" />\n    </signal>\n  </interface>\n</node>"
  },
  {
    "path": "crates/notifier_host/src/proxy/mod.rs",
    "content": "//! Proxies for DBus services, so we can call them.\n//!\n//! The interface XML files were taken from\n//! [Waybar](https://github.com/Alexays/Waybar/tree/master/protocol), and the proxies were\n//! generated with [zbus-xmlgen](https://docs.rs/crate/zbus_xmlgen/latest) by running\n//! `zbus-xmlgen file crates/notifier_host/src/proxy/dbus_status_notifier_item.xml` and\n//! `zbus-xmlgen file crates/notifier_host/src/proxy/dbus_status_notifier_watcher.xml`.\n//!\n//! Note that the `dbus_status_notifier_watcher.rs` file has been slightly adjusted, the\n//! default arguments to the [proxy](https://docs.rs/zbus/4.4.0/zbus/attr.proxy.html)\n//! macro need some adjusting.\n//!\n//! At the moment, `dbus_menu.xml` isn't used.\n//!\n//! For more information, see [\"Writing a client proxy\" in the zbus\n//! tutorial](https://dbus2.github.io/zbus/).\n\nmod dbus_status_notifier_item;\npub use dbus_status_notifier_item::*;\n\nmod dbus_status_notifier_watcher;\npub use dbus_status_notifier_watcher::*;\n"
  },
  {
    "path": "crates/notifier_host/src/watcher.rs",
    "content": "use crate::names;\nuse zbus::{dbus_interface, export::ordered_stream::OrderedStreamExt, Interface};\n\n/// An instance of [`org.kde.StatusNotifierWatcher`]. It only tracks what tray items and trays\n/// exist, and doesn't have any logic for displaying items (for that, see [`Host`][`crate::Host`]).\n///\n/// While this is usually run alongside the tray, it can also be used standalone.\n///\n/// [`org.kde.StatusNotifierWatcher`]: https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/\n#[derive(Debug, Default)]\npub struct Watcher {\n    tasks: tokio::task::JoinSet<()>,\n\n    // Intentionally using std::sync::Mutex instead of tokio's async mutex, since we don't need to\n    // hold the mutex across an await.\n    //\n    // See <https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-use>\n    hosts: std::sync::Arc<std::sync::Mutex<std::collections::HashSet<String>>>,\n    items: std::sync::Arc<std::sync::Mutex<std::collections::HashSet<String>>>,\n}\n\n/// Implementation of the `StatusNotifierWatcher` service.\n///\n/// Methods and properties correspond to methods and properties on the DBus service that can be\n/// used by others, while signals are events that we generate that other services listen to.\n#[dbus_interface(name = \"org.kde.StatusNotifierWatcher\")]\nimpl Watcher {\n    /// RegisterStatusNotifierHost method\n    async fn register_status_notifier_host(\n        &mut self,\n        service: &str,\n        #[zbus(header)] hdr: zbus::MessageHeader<'_>,\n        #[zbus(connection)] con: &zbus::Connection,\n        #[zbus(signal_context)] ctxt: zbus::SignalContext<'_>,\n    ) -> zbus::fdo::Result<()> {\n        // TODO right now, we convert everything to the unique bus name (something like :1.234).\n        // However, it might make more sense to listen to the actual name they give us, so that if\n        // the connection dissociates itself from the org.kde.StatusNotifierHost-{pid}-{nr} name\n        // but still remains around, we drop them as a host.\n        //\n        // (This also applies to RegisterStatusNotifierItem)\n\n        let (service, _) = parse_service(service, hdr, con).await?;\n        log::info!(\"new host: {}\", service);\n\n        let added_first = {\n            // scoped around locking of hosts\n            let mut hosts = self.hosts.lock().unwrap(); // unwrap: mutex poisoning is okay\n            if !hosts.insert(service.to_string()) {\n                // we're already tracking them\n                return Ok(());\n            }\n            hosts.len() == 1\n        };\n\n        if added_first {\n            self.is_status_notifier_host_registered_changed(&ctxt).await?;\n        }\n        Watcher::status_notifier_host_registered(&ctxt).await?;\n\n        self.tasks.spawn({\n            let hosts = self.hosts.clone();\n            let ctxt = ctxt.to_owned();\n            let con = con.to_owned();\n            async move {\n                if let Err(e) = wait_for_service_exit(&con, service.as_ref().into()).await {\n                    log::error!(\"failed to wait for service exit: {}\", e);\n                }\n                log::info!(\"lost host: {}\", service);\n\n                let removed_last = {\n                    let mut hosts = hosts.lock().unwrap(); // unwrap: mutex poisoning is okay\n                    let did_remove = hosts.remove(service.as_str());\n                    did_remove && hosts.is_empty()\n                };\n\n                if removed_last {\n                    if let Err(e) = Watcher::is_status_notifier_host_registered_refresh(&ctxt).await {\n                        log::error!(\"failed to signal Watcher: {}\", e);\n                    }\n                }\n                if let Err(e) = Watcher::status_notifier_host_unregistered(&ctxt).await {\n                    log::error!(\"failed to signal Watcher: {}\", e);\n                }\n            }\n        });\n\n        Ok(())\n    }\n\n    /// StatusNotifierHostRegistered signal.\n    #[dbus_interface(signal)]\n    async fn status_notifier_host_registered(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()>;\n\n    /// StatusNotifierHostUnregistered signal\n    #[dbus_interface(signal)]\n    async fn status_notifier_host_unregistered(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()>;\n\n    /// IsStatusNotifierHostRegistered property\n    #[dbus_interface(property)]\n    async fn is_status_notifier_host_registered(&self) -> bool {\n        let hosts = self.hosts.lock().unwrap(); // unwrap: mutex poisoning is okay\n        !hosts.is_empty()\n    }\n\n    // ------------------------------------------------------------------------\n\n    /// RegisterStatusNotifierItem method\n    async fn register_status_notifier_item(\n        &mut self,\n        service: &str,\n        #[zbus(header)] hdr: zbus::MessageHeader<'_>,\n        #[zbus(connection)] con: &zbus::Connection,\n        #[zbus(signal_context)] ctxt: zbus::SignalContext<'_>,\n    ) -> zbus::fdo::Result<()> {\n        let (service, objpath) = parse_service(service, hdr, con).await?;\n        let service = zbus::names::BusName::Unique(service);\n\n        let item = format!(\"{}{}\", service, objpath);\n\n        {\n            let mut items = self.items.lock().unwrap(); // unwrap: mutex poisoning is okay\n            if !items.insert(item.clone()) {\n                // we're already tracking them\n                log::info!(\"new item: {} (duplicate)\", item);\n                return Ok(());\n            }\n        }\n        log::info!(\"new item: {}\", item);\n\n        self.registered_status_notifier_items_changed(&ctxt).await?;\n        Watcher::status_notifier_item_registered(&ctxt, item.as_ref()).await?;\n\n        self.tasks.spawn({\n            let items = self.items.clone();\n            let ctxt = ctxt.to_owned();\n            let con = con.to_owned();\n            async move {\n                if let Err(e) = wait_for_service_exit(&con, service.as_ref()).await {\n                    log::error!(\"failed to wait for service exit: {}\", e);\n                }\n                println!(\"gone item: {}\", &item);\n\n                {\n                    let mut items = items.lock().unwrap(); // unwrap: mutex poisoning is okay\n                    items.remove(&item);\n                }\n\n                if let Err(e) = Watcher::registered_status_notifier_items_refresh(&ctxt).await {\n                    log::error!(\"failed to signal Watcher: {}\", e);\n                }\n                if let Err(e) = Watcher::status_notifier_item_unregistered(&ctxt, item.as_ref()).await {\n                    log::error!(\"failed to signal Watcher: {}\", e);\n                }\n            }\n        });\n\n        Ok(())\n    }\n\n    /// StatusNotifierItemRegistered signal\n    #[dbus_interface(signal)]\n    async fn status_notifier_item_registered(ctxt: &zbus::SignalContext<'_>, service: &str) -> zbus::Result<()>;\n\n    /// StatusNotifierItemUnregistered signal\n    #[dbus_interface(signal)]\n    async fn status_notifier_item_unregistered(ctxt: &zbus::SignalContext<'_>, service: &str) -> zbus::Result<()>;\n\n    /// RegisteredStatusNotifierItems property\n    #[dbus_interface(property)]\n    async fn registered_status_notifier_items(&self) -> Vec<String> {\n        let items = self.items.lock().unwrap(); // unwrap: mutex poisoning is okay\n        items.iter().cloned().collect()\n    }\n\n    // ------------------------------------------------------------------------\n\n    /// ProtocolVersion property\n    #[dbus_interface(property)]\n    fn protocol_version(&self) -> i32 {\n        0\n    }\n}\n\nimpl Watcher {\n    /// Create a new Watcher.\n    pub fn new() -> Watcher {\n        Default::default()\n    }\n\n    /// Attach and run the Watcher (in the background) on a connection.\n    pub async fn attach_to(self, con: &zbus::Connection) -> zbus::Result<()> {\n        if !con.object_server().at(names::WATCHER_OBJECT, self).await? {\n            return Err(zbus::Error::Failure(format!(\n                \"Object already exists at {} on this connection -- is StatusNotifierWatcher already running?\",\n                names::WATCHER_OBJECT\n            )));\n        }\n\n        // not AllowReplacement, not ReplaceExisting, not DoNotQueue\n        let flags: [zbus::fdo::RequestNameFlags; 0] = [];\n        match con.request_name_with_flags(names::WATCHER_BUS, flags.into_iter().collect()).await {\n            Ok(zbus::fdo::RequestNameReply::PrimaryOwner) => Ok(()),\n            Ok(_) | Err(zbus::Error::NameTaken) => Ok(()), // defer to existing\n            Err(e) => Err(e),\n        }\n    }\n\n    /// Equivalent to `is_status_notifier_host_registered_invalidate`, but without requiring\n    /// `self`.\n    async fn is_status_notifier_host_registered_refresh(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()> {\n        zbus::fdo::Properties::properties_changed(\n            ctxt,\n            Self::name(),\n            &std::collections::HashMap::new(),\n            &[\"IsStatusNotifierHostRegistered\"],\n        )\n        .await\n    }\n\n    /// Equivalent to `registered_status_notifier_items_invalidate`, but without requiring `self`.\n    async fn registered_status_notifier_items_refresh(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()> {\n        zbus::fdo::Properties::properties_changed(\n            ctxt,\n            Self::name(),\n            &std::collections::HashMap::new(),\n            &[\"RegisteredStatusNotifierItems\"],\n        )\n        .await\n    }\n}\n\n/// Decode the service name that others give to us, into the [bus\n/// name](https://dbus2.github.io/zbus/concepts.html#bus-name--service-name) and the [object\n/// path](https://dbus2.github.io/zbus/concepts.html#objects-and-object-paths) within the\n/// connection.\n///\n/// The freedesktop.org specification has the format of this be just the bus name, however some\n/// status items pass non-conforming values. One common one is just the object path.\nasync fn parse_service<'a>(\n    service: &'a str,\n    hdr: zbus::MessageHeader<'_>,\n    con: &zbus::Connection,\n) -> zbus::fdo::Result<(zbus::names::UniqueName<'static>, &'a str)> {\n    if service.starts_with('/') {\n        // they sent us just the object path\n        if let Some(sender) = hdr.sender()? {\n            Ok((sender.to_owned(), service))\n        } else {\n            log::warn!(\"unknown sender\");\n            Err(zbus::fdo::Error::InvalidArgs(\"Unknown bus address\".into()))\n        }\n    } else {\n        // parse the bus name they gave us\n        let busname: zbus::names::BusName = match service.try_into() {\n            Ok(x) => x,\n            Err(e) => {\n                log::warn!(\"received invalid bus name {:?}: {}\", service, e);\n                return Err(zbus::fdo::Error::InvalidArgs(e.to_string()));\n            }\n        };\n\n        if let zbus::names::BusName::Unique(unique) = busname {\n            Ok((unique.to_owned(), names::ITEM_OBJECT))\n        } else {\n            // they gave us a \"well-known name\" like org.kde.StatusNotifierHost-81830-0, we need to\n            // convert this into the actual identifier for their bus (e.g. :1.234), so that even if\n            // they remove that well-known name it's fine.\n            let dbus = zbus::fdo::DBusProxy::new(con).await?;\n            match dbus.get_name_owner(busname).await {\n                Ok(owner) => Ok((owner.into_inner(), names::ITEM_OBJECT)),\n                Err(e) => {\n                    log::warn!(\"failed to get owner of {:?}: {}\", service, e);\n                    Err(e)\n                }\n            }\n        }\n    }\n}\n\n/// Wait for a DBus service to disappear\nasync fn wait_for_service_exit(con: &zbus::Connection, service: zbus::names::BusName<'_>) -> zbus::fdo::Result<()> {\n    let dbus = zbus::fdo::DBusProxy::new(con).await?;\n    let mut owner_changes = dbus.receive_name_owner_changed_with_args(&[(0, &service)]).await?;\n\n    if !dbus.name_has_owner(service.as_ref()).await? {\n        // service has already disappeared\n        return Ok(());\n    }\n\n    while let Some(sig) = owner_changes.next().await {\n        let args = sig.args()?;\n        if args.new_owner().is_none() {\n            break;\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/simplexpr/Cargo.toml",
    "content": "[package]\nname = \"simplexpr\"\nversion = \"0.1.0\"\nauthors = [\"elkowar <5300871+elkowar@users.noreply.github.com>\"]\nedition = \"2021\"\nlicense = \"MIT\"\ndescription = \"A simple expression language, used as a part of eww\"\nrepository = \"https://github.com/elkowar/eww\"\nhomepage = \"https://github.com/elkowar/eww\"\n\n\nbuild = \"build.rs\"\n\n[dependencies]\neww_shared_util.workspace = true\n\nbytesize.workspace = true\ncached.workspace = true\nchrono-tz.workspace = true\nchrono = { workspace = true, features = [\"unstable-locales\"] }\nitertools.workspace = true\njaq-core.workspace = true\njaq-parse.workspace = true\njaq-std.workspace = true\njaq-interpret.workspace = true\njaq-syn.workspace = true\nlalrpop-util.workspace = true\nonce_cell.workspace = true\nregex.workspace = true\nserde_json.workspace = true\nserde = { workspace = true, features = [\"derive\"] }\nstatic_assertions.workspace = true\nstrsim.workspace = true\nstrum = { workspace = true, features = [\"derive\"] }\nthiserror.workspace = true\n\n\n[build-dependencies]\nlalrpop.workspace = true\n\n[dev-dependencies]\ninsta.workspace = true\n"
  },
  {
    "path": "crates/simplexpr/README.md",
    "content": "# simplexpr\n\nsimplexpr is a parser and interpreter for a simple expression syntax that can be embedded into other applications or crates.\nIt is being developed to be used in [eww](https://github.com/elkowar/eww), but may also other uses.\n\nFor now, this is highly experimental, unstable, and ugly. You most definitely do not want to use this crate.\n"
  },
  {
    "path": "crates/simplexpr/build.rs",
    "content": "extern crate lalrpop;\nfn main() {\n    lalrpop::Configuration::new().log_verbose().process_current_dir().unwrap();\n}\n"
  },
  {
    "path": "crates/simplexpr/src/ast.rs",
    "content": "use crate::dynval::DynVal;\nuse eww_shared_util::{Span, Spanned};\nuse itertools::Itertools;\nuse serde::{Deserialize, Serialize};\n\nuse eww_shared_util::VarName;\n\n#[rustfmt::skip]\n#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, strum::EnumString, strum::Display)]\npub enum BinOp {\n    #[strum(serialize = \"+\") ] Plus,\n    #[strum(serialize = \"-\") ] Minus,\n    #[strum(serialize = \"*\") ] Times,\n    #[strum(serialize = \"/\") ] Div,\n    #[strum(serialize = \"%\") ] Mod,\n    #[strum(serialize = \"==\")] Equals,\n    #[strum(serialize = \"!=\")] NotEquals,\n    #[strum(serialize = \"&&\")] And,\n    #[strum(serialize = \"||\")] Or,\n    #[strum(serialize = \">=\") ] GE,\n    #[strum(serialize = \"<=\") ] LE,\n    #[strum(serialize = \">\") ] GT,\n    #[strum(serialize = \"<\") ] LT,\n    #[strum(serialize = \"?:\")] Elvis,\n    #[strum(serialize = \"=~\")] RegexMatch,\n}\n\n#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, strum::EnumString, strum::Display)]\npub enum UnaryOp {\n    #[strum(serialize = \"!\")]\n    Not,\n    #[strum(serialize = \"-\")]\n    Negative,\n}\n\n/// Differenciates between regular field access (`foo.bar`) and null-safe field access (`foo?.bar`)\n#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]\npub enum AccessType {\n    Normal,\n    Safe,\n}\n\n#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum SimplExpr {\n    Literal(DynVal),\n    JsonArray(Span, Vec<SimplExpr>),\n    JsonObject(Span, Vec<(SimplExpr, SimplExpr)>),\n    Concat(Span, Vec<SimplExpr>),\n    VarRef(Span, VarName),\n    BinOp(Span, Box<SimplExpr>, BinOp, Box<SimplExpr>),\n    UnaryOp(Span, UnaryOp, Box<SimplExpr>),\n    IfElse(Span, Box<SimplExpr>, Box<SimplExpr>, Box<SimplExpr>),\n    JsonAccess(Span, AccessType, Box<SimplExpr>, Box<SimplExpr>),\n    FunctionCall(Span, String, Vec<SimplExpr>),\n}\n\nimpl std::fmt::Display for SimplExpr {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            SimplExpr::Literal(x) => write!(f, \"\\\"{}\\\"\", x),\n            SimplExpr::Concat(_, elems) => {\n                let text = elems\n                    .iter()\n                    .map(|x| match x {\n                        SimplExpr::Literal(lit) => lit.to_string(),\n                        other => format!(\"${{{}}}\", other),\n                    })\n                    .join(\"\");\n                write!(f, \"\\\"{}\\\"\", text)\n            }\n            SimplExpr::VarRef(_, x) => write!(f, \"{}\", x),\n            SimplExpr::BinOp(_, l, op, r) => write!(f, \"({} {} {})\", l, op, r),\n            SimplExpr::UnaryOp(_, op, x) => write!(f, \"{}{}\", op, x),\n            SimplExpr::IfElse(_, a, b, c) => write!(f, \"({} ? {} : {})\", a, b, c),\n            SimplExpr::JsonAccess(_, AccessType::Normal, value, index) => write!(f, \"{}[{}]\", value, index),\n            SimplExpr::JsonAccess(_, AccessType::Safe, value, index) => write!(f, \"{}?.[{}]\", value, index),\n            SimplExpr::FunctionCall(_, function_name, args) => {\n                write!(f, \"{}({})\", function_name, args.iter().join(\", \"))\n            }\n            SimplExpr::JsonArray(_, values) => write!(f, \"[{}]\", values.iter().join(\", \")),\n            SimplExpr::JsonObject(_, entries) => {\n                write!(f, \"{{{}}}\", entries.iter().map(|(k, v)| format!(\"{}: {}\", k, v)).join(\", \"))\n            }\n        }\n    }\n}\nimpl SimplExpr {\n    pub fn literal(span: Span, s: String) -> Self {\n        Self::Literal(DynVal(s, span))\n    }\n\n    /// Construct a synthetic simplexpr from a literal string, without adding any relevant span information (uses [`Span::DUMMY`])\n    pub fn synth_string(s: impl Into<String>) -> Self {\n        Self::Literal(DynVal(s.into(), Span::DUMMY))\n    }\n\n    /// Construct a synthetic simplexpr from a literal dynval, without adding any relevant span information (uses [`Span::DUMMY`])\n    pub fn synth_literal<T: Into<DynVal>>(s: T) -> Self {\n        Self::Literal(s.into())\n    }\n\n    pub fn var_ref(span: Span, n: impl Into<VarName>) -> Self {\n        Self::VarRef(span, n.into())\n    }\n\n    pub fn references_var(&self, var: &VarName) -> bool {\n        use SimplExpr::*;\n        match self {\n            Literal(_) => false,\n            Concat(_, x) | FunctionCall(_, _, x) | JsonArray(_, x) => x.iter().any(|x| x.references_var(var)),\n            JsonObject(_, x) => x.iter().any(|(k, v)| k.references_var(var) || v.references_var(var)),\n            JsonAccess(_, _, a, b) | BinOp(_, a, _, b) => a.references_var(var) || b.references_var(var),\n            UnaryOp(_, _, x) => x.references_var(var),\n            IfElse(_, a, b, c) => a.references_var(var) || b.references_var(var) || c.references_var(var),\n            VarRef(_, x) => x == var,\n        }\n    }\n\n    pub fn collect_var_refs_into(&self, dest: &mut Vec<VarName>) {\n        use SimplExpr::*;\n        match self {\n            VarRef(_, x) => dest.push(x.clone()),\n            UnaryOp(_, _, x) => x.as_ref().collect_var_refs_into(dest),\n            BinOp(_, a, _, b) | JsonAccess(_, _, a, b) => {\n                a.as_ref().collect_var_refs_into(dest);\n                b.as_ref().collect_var_refs_into(dest);\n            }\n            IfElse(_, a, b, c) => {\n                a.as_ref().collect_var_refs_into(dest);\n                b.as_ref().collect_var_refs_into(dest);\n                c.as_ref().collect_var_refs_into(dest);\n            }\n            JsonArray(_, xs) | FunctionCall(_, _, xs) | Concat(_, xs) => xs.iter().for_each(|x| x.collect_var_refs_into(dest)),\n            JsonObject(_, entries) => entries.iter().for_each(|(k, v)| {\n                k.collect_var_refs_into(dest);\n                v.collect_var_refs_into(dest);\n            }),\n            Literal(_) => {}\n        };\n    }\n\n    pub fn collect_var_refs(&self) -> Vec<VarName> {\n        let mut dest = Vec::new();\n        self.collect_var_refs_into(&mut dest);\n        dest\n    }\n}\n\nimpl Spanned for SimplExpr {\n    fn span(&self) -> Span {\n        match self {\n            SimplExpr::Literal(x) => x.span(),\n            SimplExpr::JsonArray(span, _) => *span,\n            SimplExpr::JsonObject(span, _) => *span,\n            SimplExpr::Concat(span, _) => *span,\n            SimplExpr::VarRef(span, _) => *span,\n            SimplExpr::BinOp(span, ..) => *span,\n            SimplExpr::UnaryOp(span, ..) => *span,\n            SimplExpr::IfElse(span, ..) => *span,\n            SimplExpr::JsonAccess(span, ..) => *span,\n            SimplExpr::FunctionCall(span, ..) => *span,\n        }\n    }\n}\n\nimpl std::fmt::Debug for SimplExpr {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self)\n    }\n}\n"
  },
  {
    "path": "crates/simplexpr/src/dynval.rs",
    "content": "use eww_shared_util::{Span, Spanned};\nuse itertools::Itertools;\nuse serde::{Deserialize, Serialize};\nuse std::{convert::TryFrom, fmt, iter::FromIterator, str::FromStr};\n\npub type Result<T> = std::result::Result<T, ConversionError>;\n\n#[derive(Debug, thiserror::Error)]\n#[error(\"Failed to turn `{value}` into a value of type {target_type}\")]\npub struct ConversionError {\n    pub value: DynVal,\n    pub target_type: &'static str,\n    pub source: Option<Box<dyn std::error::Error + Sync + Send + 'static>>,\n}\n\n#[derive(Debug, thiserror::Error)]\n#[error(\"Failed to parse duration. Must be a number of milliseconds, or a string like \\\"150ms\\\"\")]\npub struct DurationParseError;\n\nimpl ConversionError {\n    pub fn new(value: DynVal, target_type: &'static str, source: impl std::error::Error + 'static + Sync + Send) -> Self {\n        ConversionError { value, target_type, source: Some(Box::new(source)) }\n    }\n}\nimpl Spanned for ConversionError {\n    fn span(&self) -> Span {\n        self.value.1\n    }\n}\n\n#[derive(Clone, Deserialize, Serialize, Eq)]\npub struct DynVal(pub String, pub Span);\n\nimpl From<String> for DynVal {\n    fn from(s: String) -> Self {\n        DynVal(s, Span::DUMMY)\n    }\n}\n\nimpl fmt::Display for DynVal {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{}\", self.0)\n    }\n}\nimpl fmt::Debug for DynVal {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"\\\"{}\\\"\", self.0)\n    }\n}\n\n/// Manually implement equality, to allow for values in different formats (i.e. \"1\" and \"1.0\") to still be considered as equal.\nimpl std::cmp::PartialEq<Self> for DynVal {\n    fn eq(&self, other: &Self) -> bool {\n        if let (Ok(a), Ok(b)) = (self.as_f64(), other.as_f64()) {\n            a == b\n        } else {\n            self.0 == other.0\n        }\n    }\n}\n\nimpl FromIterator<DynVal> for DynVal {\n    fn from_iter<T: IntoIterator<Item = DynVal>>(iter: T) -> Self {\n        DynVal(iter.into_iter().join(\"\"), Span::DUMMY)\n    }\n}\n\nimpl std::str::FromStr for DynVal {\n    type Err = ConversionError;\n\n    /// parses the value, trying to turn it into a number and a boolean first,\n    /// before deciding that it is a string.\n    fn from_str(s: &str) -> Result<Self> {\n        Ok(DynVal::from_string(s.to_string()))\n    }\n}\n\npub trait FromDynVal: Sized {\n    type Err;\n    fn from_dynval(x: &DynVal) -> std::result::Result<Self, Self::Err>;\n}\n\nimpl<E, T: FromStr<Err = E>> FromDynVal for T {\n    type Err = E;\n\n    fn from_dynval(x: &DynVal) -> std::result::Result<Self, Self::Err> {\n        x.0.parse()\n    }\n}\n\nmacro_rules! impl_dynval_from {\n    ($($t:ty),*) => {\n        $(impl From<$t> for DynVal {\n            fn from(x: $t) -> Self { DynVal(x.to_string(), Span::DUMMY) }\n        })*\n    };\n}\n\nimpl_dynval_from!(bool, i32, u32, f32, u8, f64, &str);\n\nimpl TryFrom<serde_json::Value> for DynVal {\n    type Error = serde_json::Error;\n\n    fn try_from(value: serde_json::Value) -> std::result::Result<Self, Self::Error> {\n        Ok(DynVal(serde_json::to_string(&value)?, Span::DUMMY))\n    }\n}\n\nimpl From<Vec<DynVal>> for DynVal {\n    fn from(v: Vec<DynVal>) -> Self {\n        let span = if let (Some(first), Some(last)) = (v.first(), v.last()) { first.span().to(last.span()) } else { Span::DUMMY };\n        let elements = v.into_iter().map(|x| x.as_string().unwrap()).collect::<Vec<_>>();\n        DynVal(serde_json::to_string(&elements).unwrap(), span)\n    }\n}\n\nimpl From<std::time::Duration> for DynVal {\n    fn from(d: std::time::Duration) -> Self {\n        DynVal(format!(\"{}ms\", d.as_millis()), Span::DUMMY)\n    }\n}\n\nimpl From<&serde_json::Value> for DynVal {\n    fn from(v: &serde_json::Value) -> Self {\n        DynVal(\n            v.as_str()\n                .map(|x| x.to_string())\n                .or_else(|| serde_json::to_string(v).ok())\n                .unwrap_or_else(|| \"<invalid json value>\".to_string()),\n            Span::DUMMY,\n        )\n    }\n}\n\nimpl Spanned for DynVal {\n    fn span(&self) -> Span {\n        self.1\n    }\n}\n\nimpl DynVal {\n    pub fn at(mut self, span: Span) -> Self {\n        self.1 = span;\n        self\n    }\n\n    pub fn at_if_dummy(mut self, span: Span) -> Self {\n        if self.1.is_dummy() {\n            self.1 = span;\n        }\n        self\n    }\n\n    pub fn from_string(s: String) -> Self {\n        DynVal(s, Span::DUMMY)\n    }\n\n    pub fn read_as<E, T: FromDynVal<Err = E>>(&self) -> std::result::Result<T, E> {\n        T::from_dynval(self)\n    }\n\n    pub fn into_inner(self) -> String {\n        self.0\n    }\n\n    /// This will never fail\n    pub fn as_string(&self) -> Result<String> {\n        Ok(self.0.to_owned())\n    }\n\n    pub fn as_f64(&self) -> Result<f64> {\n        self.0.parse().map_err(|e| ConversionError::new(self.clone(), \"f64\", e))\n    }\n\n    pub fn as_i32(&self) -> Result<i32> {\n        self.0.parse().map_err(|e| ConversionError::new(self.clone(), \"i32\", e))\n    }\n\n    pub fn as_i64(&self) -> Result<i64> {\n        self.0.parse().map_err(|e| ConversionError::new(self.clone(), \"i64\", e))\n    }\n\n    pub fn as_bool(&self) -> Result<bool> {\n        self.0.parse().map_err(|e| ConversionError::new(self.clone(), \"bool\", e))\n    }\n\n    pub fn as_duration(&self) -> Result<std::time::Duration> {\n        use std::time::Duration;\n        let s = &self.0;\n        if s.ends_with(\"ms\") {\n            Ok(Duration::from_millis(\n                s.trim_end_matches(\"ms\").parse().map_err(|e| ConversionError::new(self.clone(), \"integer\", e))?,\n            ))\n        } else if s.ends_with('s') {\n            let secs = s.trim_end_matches('s').parse::<f64>().map_err(|e| ConversionError::new(self.clone(), \"number\", e))?;\n            Ok(Duration::from_millis(f64::floor(secs * 1000f64) as u64))\n        } else if s.ends_with('m') || s.ends_with(\"min\") {\n            let minutes = s\n                .trim_end_matches(\"min\")\n                .trim_end_matches('m')\n                .parse::<f64>()\n                .map_err(|e| ConversionError::new(self.clone(), \"number\", e))?;\n            Ok(Duration::from_secs(f64::floor(minutes * 60f64) as u64))\n        } else if s.ends_with('h') {\n            let hours = s.trim_end_matches('h').parse::<f64>().map_err(|e| ConversionError::new(self.clone(), \"number\", e))?;\n            Ok(Duration::from_secs(f64::floor(hours * 60f64 * 60f64) as u64))\n        } else if let Ok(millis) = s.parse() {\n            Ok(Duration::from_millis(millis))\n        } else {\n            Err(ConversionError { value: self.clone(), target_type: \"duration\", source: Some(Box::new(DurationParseError)) })\n        }\n    }\n\n    // TODO this should return Result<Vec<DynVal>> and use json parsing\n    pub fn as_vec(&self) -> Result<Vec<String>> {\n        if self.0.is_empty() {\n            Ok(Vec::new())\n        } else {\n            match self.0.strip_prefix('[').and_then(|x| x.strip_suffix(']')) {\n                Some(content) => {\n                    let mut items: Vec<String> = content.split(',').map(|x: &str| x.to_string()).collect();\n                    let mut removed = 0;\n                    for times_ran in 0..items.len() {\n                        // escapes `,` if there's a `\\` before em\n                        if items[times_ran - removed].ends_with('\\\\') {\n                            items[times_ran - removed].pop();\n                            let it = items.remove((times_ran + 1) - removed);\n                            items[times_ran - removed] += \",\";\n                            items[times_ran - removed] += &it;\n                            removed += 1;\n                        }\n                    }\n                    Ok(items)\n                }\n                None => Err(ConversionError { value: self.clone(), target_type: \"vec\", source: None }),\n            }\n        }\n    }\n\n    pub fn as_json_value(&self) -> Result<serde_json::Value> {\n        serde_json::from_str::<serde_json::Value>(&self.0)\n            .map_err(|e| ConversionError::new(self.clone(), \"json-value\", Box::new(e)))\n    }\n\n    pub fn as_json_array(&self) -> Result<Vec<serde_json::Value>> {\n        serde_json::from_str::<serde_json::Value>(&self.0)\n            .map_err(|e| ConversionError::new(self.clone(), \"json-value\", Box::new(e)))?\n            .as_array()\n            .cloned()\n            .ok_or_else(|| ConversionError { value: self.clone(), target_type: \"json-array\", source: None })\n    }\n\n    pub fn as_json_object(&self) -> Result<serde_json::Map<String, serde_json::Value>> {\n        serde_json::from_str::<serde_json::Value>(&self.0)\n            .map_err(|e| ConversionError::new(self.clone(), \"json-value\", Box::new(e)))?\n            .as_object()\n            .cloned()\n            .ok_or_else(|| ConversionError { value: self.clone(), target_type: \"json-object\", source: None })\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    #[test]\n    fn test_parse_vec() {\n        insta::assert_debug_snapshot!(DynVal::from(\"[]\").as_vec());\n        insta::assert_debug_snapshot!(DynVal::from(\"[hi]\").as_vec());\n        insta::assert_debug_snapshot!(DynVal::from(\"[hi,ho,hu]\").as_vec());\n        insta::assert_debug_snapshot!(DynVal::from(\"[hi\\\\,ho]\").as_vec());\n        insta::assert_debug_snapshot!(DynVal::from(\"[hi\\\\,ho,hu]\").as_vec());\n        insta::assert_debug_snapshot!(DynVal::from(\"\").as_vec());\n        insta::assert_debug_snapshot!(DynVal::from(\"[a,b\").as_vec());\n        insta::assert_debug_snapshot!(DynVal::from(\"a]\").as_vec());\n    }\n\n    #[test]\n    fn test_parse_duration() {\n        insta::assert_debug_snapshot!(DynVal::from(\"100ms\").as_duration());\n        insta::assert_debug_snapshot!(DynVal::from(\"1s\").as_duration());\n        insta::assert_debug_snapshot!(DynVal::from(\"0.1s\").as_duration());\n        insta::assert_debug_snapshot!(DynVal::from(\"5m\").as_duration());\n        insta::assert_debug_snapshot!(DynVal::from(\"5min\").as_duration());\n        insta::assert_debug_snapshot!(DynVal::from(\"0.5m\").as_duration());\n        insta::assert_debug_snapshot!(DynVal::from(\"1h\").as_duration());\n        insta::assert_debug_snapshot!(DynVal::from(\"0.5h\").as_duration());\n    }\n}\n"
  },
  {
    "path": "crates/simplexpr/src/error.rs",
    "content": "use crate::parser::lexer::{self, LexicalError};\nuse eww_shared_util::{Span, Spanned};\n\n#[derive(thiserror::Error, Debug)]\n#[error(\"Error parsing expression: {source}\")]\npub struct ParseError {\n    pub file_id: usize,\n    pub source: lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError>,\n}\n\nimpl ParseError {\n    pub fn from_parse_error(file_id: usize, err: lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError>) -> Self {\n        Self { file_id, source: err }\n    }\n}\n\nimpl Spanned for ParseError {\n    fn span(&self) -> Span {\n        match &self.source {\n            lalrpop_util::ParseError::InvalidToken { location } => Span(*location, *location, self.file_id),\n            lalrpop_util::ParseError::UnrecognizedEof { location, expected: _ } => Span(*location, *location, self.file_id),\n            lalrpop_util::ParseError::UnrecognizedToken { token, expected: _ } => Span(token.0, token.2, self.file_id),\n            lalrpop_util::ParseError::ExtraToken { token } => Span(token.0, token.2, self.file_id),\n            lalrpop_util::ParseError::User { error: LexicalError(span) } => *span,\n        }\n    }\n}\n\n#[macro_export]\nmacro_rules! spanned {\n    ($err:ty, $span:expr, $block:expr) => {{\n        let span = $span;\n        let result: Result<_, $err> = try { $block };\n        result.at(span)\n    }};\n}\n"
  },
  {
    "path": "crates/simplexpr/src/eval.rs",
    "content": "use bytesize::ByteSize;\nuse cached::proc_macro::cached;\nuse chrono::{Local, LocalResult, TimeZone};\nuse itertools::Itertools;\nuse jaq_interpret::FilterT;\n\nuse crate::{\n    ast::{AccessType, BinOp, SimplExpr, UnaryOp},\n    dynval::{ConversionError, DynVal},\n};\nuse eww_shared_util::{get_locale, Span, Spanned, VarName};\nuse std::{\n    collections::HashMap,\n    convert::{Infallible, TryFrom, TryInto},\n    str::FromStr,\n    sync::Arc,\n};\n\n#[derive(Debug, thiserror::Error)]\npub struct JaqParseError(pub Option<jaq_parse::Error>);\nimpl std::fmt::Display for JaqParseError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match &self.0 {\n            Some(x) => write!(f, \"Error parsing jq filter: {x}\"),\n            None => write!(f, \"Error parsing jq filter\"),\n        }\n    }\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum EvalError {\n    #[error(\"Tried to reference variable `{0}`, but we cannot access variables here\")]\n    NoVariablesAllowed(VarName),\n\n    #[error(\"Invalid regex: {0}\")]\n    InvalidRegex(#[from] regex::Error),\n\n    #[error(\"Unknown variable {0}\")]\n    UnknownVariable(VarName, Vec<VarName>),\n\n    #[error(transparent)]\n    ConversionError(#[from] ConversionError),\n\n    #[error(\"Incorrect number of arguments given to function: {0}\")]\n    WrongArgCount(String),\n\n    #[error(\"Unknown function {0}\")]\n    UnknownFunction(String),\n\n    #[error(\"Unable to index into value {0}\")]\n    CannotIndex(String),\n\n    #[error(\"Json operation failed: {0}\")]\n    SerdeError(#[from] serde_json::error::Error),\n\n    #[error(\"Error in jq function: {0}\")]\n    JaqError(String),\n\n    #[error(transparent)]\n    JaqParseError(Box<JaqParseError>),\n\n    #[error(\"Error parsing date: {0}\")]\n    ChronoError(String),\n\n    #[error(\"Error parsing byte format mode: {0}\")]\n    ByteFormatModeError(String),\n\n    #[error(\"{1}\")]\n    Spanned(Span, Box<EvalError>),\n}\n\nstatic_assertions::assert_impl_all!(EvalError: Send, Sync);\n\nimpl EvalError {\n    pub fn at(self, span: Span) -> Self {\n        match self {\n            EvalError::Spanned(..) => self,\n            _ => EvalError::Spanned(span, Box::new(self)),\n        }\n    }\n\n    pub fn map_in_span(self, f: impl FnOnce(Self) -> Self) -> Self {\n        match self {\n            EvalError::Spanned(span, err) => EvalError::Spanned(span, Box::new(err.map_in_span(f))),\n            other => f(other),\n        }\n    }\n}\n\nimpl Spanned for EvalError {\n    fn span(&self) -> Span {\n        match self {\n            EvalError::Spanned(span, _) => *span,\n            EvalError::ConversionError(err) => err.span(),\n            _ => Span::DUMMY,\n        }\n    }\n}\n\nimpl SimplExpr {\n    /// map over all of the variable references, replacing them with whatever expression the provided function returns.\n    /// Returns [Err] when the provided function fails with an [Err]\n    pub fn try_map_var_refs<E, F: Fn(Span, VarName) -> Result<SimplExpr, E> + Copy>(self, f: F) -> Result<Self, E> {\n        use SimplExpr::*;\n        Ok(match self {\n            BinOp(span, a, op, b) => BinOp(span, Box::new(a.try_map_var_refs(f)?), op, Box::new(b.try_map_var_refs(f)?)),\n            Concat(span, elems) => Concat(span, elems.into_iter().map(|x| x.try_map_var_refs(f)).collect::<Result<_, _>>()?),\n            UnaryOp(span, op, a) => UnaryOp(span, op, Box::new(a.try_map_var_refs(f)?)),\n            IfElse(span, a, b, c) => {\n                IfElse(span, Box::new(a.try_map_var_refs(f)?), Box::new(b.try_map_var_refs(f)?), Box::new(c.try_map_var_refs(f)?))\n            }\n            JsonAccess(span, safe, a, b) => {\n                JsonAccess(span, safe, Box::new(a.try_map_var_refs(f)?), Box::new(b.try_map_var_refs(f)?))\n            }\n            FunctionCall(span, name, args) => {\n                FunctionCall(span, name, args.into_iter().map(|x| x.try_map_var_refs(f)).collect::<Result<_, _>>()?)\n            }\n            VarRef(span, name) => f(span, name)?,\n            JsonArray(span, values) => {\n                JsonArray(span, values.into_iter().map(|x| x.try_map_var_refs(f)).collect::<Result<_, _>>()?)\n            }\n            JsonObject(span, entries) => JsonObject(\n                span,\n                entries\n                    .into_iter()\n                    .map(|(k, v)| Ok((k.try_map_var_refs(f)?, v.try_map_var_refs(f)?)))\n                    .collect::<Result<_, _>>()?,\n            ),\n            x @ Literal(..) => x,\n        })\n    }\n\n    pub fn map_var_refs(self, f: impl Fn(Span, VarName) -> SimplExpr) -> Self {\n        self.try_map_var_refs(|span, var| Ok::<_, Infallible>(f(span, var))).unwrap()\n    }\n\n    /// resolve partially.\n    /// If a var-ref links to another var-ref, that other var-ref is used.\n    /// If a referenced variable is not found in the given hashmap, returns the var-ref unchanged.\n    pub fn resolve_one_level(self, variables: &HashMap<VarName, SimplExpr>) -> Self {\n        self.map_var_refs(|span, name| variables.get(&name).cloned().unwrap_or(Self::VarRef(span, name)))\n    }\n\n    /// resolve variable references in the expression. Fails if a variable cannot be resolved.\n    pub fn resolve_refs(self, variables: &HashMap<VarName, DynVal>) -> Result<Self, EvalError> {\n        use SimplExpr::*;\n        self.try_map_var_refs(|span, name| match variables.get(&name) {\n            Some(value) => Ok(Literal(value.clone())),\n            None => {\n                let similar_ish = variables.keys().filter(|key| strsim::levenshtein(&key.0, &name.0) < 3).cloned().collect_vec();\n                Err(EvalError::UnknownVariable(name.clone(), similar_ish).at(span))\n            }\n        })\n    }\n\n    pub fn var_refs_with_span(&self) -> Vec<(Span, &VarName)> {\n        use SimplExpr::*;\n        match self {\n            Literal(..) => Vec::new(),\n            VarRef(span, name) => vec![(*span, name)],\n            Concat(_, elems) => elems.iter().flat_map(|x| x.var_refs_with_span().into_iter()).collect(),\n            BinOp(_, a, _, b) | JsonAccess(_, _, a, b) => {\n                let mut refs = a.var_refs_with_span();\n                refs.extend(b.var_refs_with_span().iter());\n                refs\n            }\n            UnaryOp(_, _, x) => x.var_refs_with_span(),\n            IfElse(_, a, b, c) => {\n                let mut refs = a.var_refs_with_span();\n                refs.extend(b.var_refs_with_span().iter());\n                refs.extend(c.var_refs_with_span().iter());\n                refs\n            }\n            FunctionCall(_, _, args) => args.iter().flat_map(|a| a.var_refs_with_span()).collect(),\n            JsonArray(_, values) => values.iter().flat_map(|v| v.var_refs_with_span()).collect(),\n            JsonObject(_, entries) => {\n                entries.iter().flat_map(|(k, v)| k.var_refs_with_span().into_iter().chain(v.var_refs_with_span())).collect()\n            }\n        }\n    }\n\n    pub fn eval_no_vars(&self) -> Result<DynVal, EvalError> {\n        match self.eval(&HashMap::new()) {\n            Ok(x) => Ok(x),\n            Err(x) => Err(x.map_in_span(|err| match err {\n                EvalError::UnknownVariable(name, _) => EvalError::NoVariablesAllowed(name),\n                other => other,\n            })),\n        }\n    }\n\n    pub fn eval(&self, values: &HashMap<VarName, DynVal>) -> Result<DynVal, EvalError> {\n        let span = self.span();\n        let value = match self {\n            SimplExpr::Literal(x) => Ok(x.clone()),\n            SimplExpr::Concat(span, elems) => {\n                let mut output = String::new();\n                for elem in elems {\n                    let result = elem.eval(values)?;\n                    output.push_str(&result.0);\n                }\n                Ok(DynVal(output, *span))\n            }\n            SimplExpr::VarRef(span, ref name) => {\n                let similar_ish = values.keys().filter(|keys| strsim::levenshtein(&keys.0, &name.0) < 3).cloned().collect_vec();\n                Ok(values\n                    .get(name)\n                    .cloned()\n                    .ok_or_else(|| EvalError::UnknownVariable(name.clone(), similar_ish).at(*span))?\n                    .at(*span))\n            }\n            SimplExpr::BinOp(span, a, op, b) => {\n                let a = a.eval(values)?;\n                let b = || b.eval(values);\n                // Lazy operators\n                let dynval = match op {\n                    BinOp::And => DynVal::from(a.as_bool()? && b()?.as_bool()?),\n                    BinOp::Or => DynVal::from(a.as_bool()? || b()?.as_bool()?),\n                    BinOp::Elvis => {\n                        let is_null = matches!(serde_json::from_str(&a.0), Ok(serde_json::Value::Null));\n                        if a.0.is_empty() || is_null {\n                            b()?\n                        } else {\n                            a\n                        }\n                    }\n                    // Eager operators\n                    _ => {\n                        let b = b()?;\n                        match op {\n                            BinOp::Equals => DynVal::from(a == b),\n                            BinOp::NotEquals => DynVal::from(a != b),\n                            BinOp::Plus => match (a.as_f64(), b.as_f64()) {\n                                (Ok(a), Ok(b)) => DynVal::from(a + b),\n                                _ => DynVal::from(format!(\"{}{}\", a.as_string()?, b.as_string()?)),\n                            },\n                            BinOp::Minus => DynVal::from(a.as_f64()? - b.as_f64()?),\n                            BinOp::Times => DynVal::from(a.as_f64()? * b.as_f64()?),\n                            BinOp::Div => DynVal::from(a.as_f64()? / b.as_f64()?),\n                            BinOp::Mod => DynVal::from(a.as_f64()? % b.as_f64()?),\n                            BinOp::GT => DynVal::from(a.as_f64()? > b.as_f64()?),\n                            BinOp::LT => DynVal::from(a.as_f64()? < b.as_f64()?),\n                            BinOp::GE => DynVal::from(a.as_f64()? >= b.as_f64()?),\n                            BinOp::LE => DynVal::from(a.as_f64()? <= b.as_f64()?),\n                            BinOp::RegexMatch => {\n                                let regex = regex::Regex::new(&b.as_string()?)?;\n                                DynVal::from(regex.is_match(&a.as_string()?))\n                            }\n                            _ => unreachable!(\"Lazy operators already handled\"),\n                        }\n                    }\n                };\n                Ok(dynval.at(*span))\n            }\n            SimplExpr::UnaryOp(span, op, a) => {\n                let a = a.eval(values)?;\n                Ok(match op {\n                    UnaryOp::Not => DynVal::from(!a.as_bool()?).at(*span),\n                    UnaryOp::Negative => DynVal::from(-a.as_f64()?).at(*span),\n                })\n            }\n            SimplExpr::IfElse(_, cond, yes, no) => {\n                if cond.eval(values)?.as_bool()? {\n                    yes.eval(values)\n                } else {\n                    no.eval(values)\n                }\n            }\n            SimplExpr::JsonAccess(span, safe, val, index) => {\n                let val = val.eval(values)?;\n                let index = index.eval(values)?;\n\n                let is_safe = *safe == AccessType::Safe;\n\n                // Needs to be done first as `as_json_value` fails on empty string\n                if is_safe && val.as_string()?.is_empty() {\n                    return Ok(DynVal::from(&serde_json::Value::Null).at(*span));\n                }\n                match val.as_json_value()? {\n                    serde_json::Value::Array(val) => {\n                        let index = index.as_i32()?;\n                        let indexed_value = val.get(index as usize).unwrap_or(&serde_json::Value::Null);\n                        Ok(DynVal::from(indexed_value).at(*span))\n                    }\n                    serde_json::Value::Object(val) => {\n                        let indexed_value = val\n                            .get(&index.as_string()?)\n                            .or_else(|| val.get(&index.as_i32().ok()?.to_string()))\n                            .unwrap_or(&serde_json::Value::Null);\n                        Ok(DynVal::from(indexed_value).at(*span))\n                    }\n                    serde_json::Value::Null if is_safe => Ok(DynVal::from(&serde_json::Value::Null).at(*span)),\n                    _ => Err(EvalError::CannotIndex(format!(\"{}\", val)).at(*span)),\n                }\n            }\n            SimplExpr::FunctionCall(span, function_name, args) => {\n                let args = args.iter().map(|a| a.eval(values)).collect::<Result<_, EvalError>>()?;\n                call_expr_function(function_name, args).map(|x| x.at(*span)).map_err(|e| e.at(*span))\n            }\n            SimplExpr::JsonArray(span, entries) => {\n                let entries = entries\n                    .iter()\n                    .map(|v| Ok(serde_json::Value::String(v.eval(values)?.as_string()?)))\n                    .collect::<Result<_, EvalError>>()?;\n                Ok(DynVal::try_from(serde_json::Value::Array(entries))?.at(*span))\n            }\n            SimplExpr::JsonObject(span, entries) => {\n                let entries = entries\n                    .iter()\n                    .map(|(k, v)| Ok((k.eval(values)?.as_string()?, serde_json::Value::String(v.eval(values)?.as_string()?))))\n                    .collect::<Result<_, EvalError>>()?;\n                Ok(DynVal::try_from(serde_json::Value::Object(entries))?.at(*span))\n            }\n        };\n        Ok(value?.at(span))\n    }\n}\n\nfn call_expr_function(name: &str, args: Vec<DynVal>) -> Result<DynVal, EvalError> {\n    match name {\n        \"get_env\" => match args.as_slice() {\n            [var_name] => {\n                let var = std::env::var(var_name.as_string()?).unwrap_or_default();\n                Ok(DynVal::from(var))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"round\" => match args.as_slice() {\n            [num, digits] => {\n                let num = num.as_f64()?;\n                let digits = digits.as_i32()?;\n                Ok(DynVal::from(format!(\"{:.1$}\", num, digits as usize)))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"floor\" => match args.as_slice() {\n            [num] => {\n                let num = num.as_f64()?;\n                Ok(DynVal::from(num.floor()))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"ceil\" => match args.as_slice() {\n            [num] => {\n                let num = num.as_f64()?;\n                Ok(DynVal::from(num.ceil()))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"min\" => match args.as_slice() {\n            [a, b] => {\n                let a = a.as_f64()?;\n                let b = b.as_f64()?;\n                Ok(DynVal::from(f64::min(a, b)))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"max\" => match args.as_slice() {\n            [a, b] => {\n                let a = a.as_f64()?;\n                let b = b.as_f64()?;\n                Ok(DynVal::from(f64::max(a, b)))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"powi\" => match args.as_slice() {\n            [num, n] => {\n                let num = num.as_f64()?;\n                let n = n.as_i32()?;\n                Ok(DynVal::from(f64::powi(num, n)))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"powf\" => match args.as_slice() {\n            [num, n] => {\n                let num = num.as_f64()?;\n                let n = n.as_f64()?;\n                Ok(DynVal::from(f64::powf(num, n)))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"sin\" => match args.as_slice() {\n            [num] => {\n                let num = num.as_f64()?;\n                Ok(DynVal::from(num.sin()))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"cos\" => match args.as_slice() {\n            [num] => {\n                let num = num.as_f64()?;\n                Ok(DynVal::from(num.cos()))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"tan\" => match args.as_slice() {\n            [num] => {\n                let num = num.as_f64()?;\n                Ok(DynVal::from(num.tan()))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"cot\" => match args.as_slice() {\n            [num] => {\n                let num = num.as_f64()?;\n                Ok(DynVal::from(1.0 / num.tan()))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"degtorad\" => match args.as_slice() {\n            [num] => {\n                let num = num.as_f64()?;\n                Ok(DynVal::from(num.to_radians()))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"radtodeg\" => match args.as_slice() {\n            [num] => {\n                let num = num.as_f64()?;\n                Ok(DynVal::from(num.to_degrees()))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"matches\" => match args.as_slice() {\n            [string, pattern] => {\n                let string = string.as_string()?;\n                let pattern = regex::Regex::new(&pattern.as_string()?)?;\n                Ok(DynVal::from(pattern.is_match(&string)))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"replace\" => match args.as_slice() {\n            [string, pattern, replacement] => {\n                let string = string.as_string()?;\n                let pattern = regex::Regex::new(&pattern.as_string()?)?;\n                let replacement = replacement.as_string()?;\n                Ok(DynVal::from(pattern.replace_all(&string, replacement.replace('$', \"$$\").replace('\\\\', \"$\")).into_owned()))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"substring\" => match args.as_slice() {\n            [string, start, len] => {\n                let result: String = string\n                    .as_string()?\n                    .chars()\n                    .skip(start.as_i32()?.max(0) as usize)\n                    .take(len.as_i32()?.max(0) as usize)\n                    .collect();\n                Ok(DynVal::from(result))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"search\" => match args.as_slice() {\n            [string, pattern] => {\n                use serde_json::Value;\n                let string = string.as_string()?;\n                let pattern = regex::Regex::new(&pattern.as_string()?)?;\n                Ok(Value::Array(pattern.find_iter(&string).map(|x| Value::String(x.as_str().to_string())).collect())\n                    .try_into()?)\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"captures\" => match args.as_slice() {\n            [string, pattern] => {\n                use serde_json::Value;\n                let string = string.as_string()?;\n                let pattern = regex::Regex::new(&pattern.as_string()?)?;\n                Ok(Value::Array(\n                    pattern\n                        .captures_iter(&string)\n                        .map(|captures| {\n                            Value::Array(captures.iter().flatten().map(|x| Value::String(x.as_str().to_string())).collect())\n                        })\n                        .collect(),\n                )\n                .try_into()?)\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"strlength\" => match args.as_slice() {\n            [string] => Ok(DynVal::from(string.as_string()?.len() as i32)),\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"arraylength\" => match args.as_slice() {\n            [json] => Ok(DynVal::from(json.as_json_array()?.len() as i32)),\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"objectlength\" => match args.as_slice() {\n            [json] => Ok(DynVal::from(json.as_json_object()?.len() as i32)),\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"jq\" => match args.as_slice() {\n            [json, code] => run_jaq_function(json.as_json_value()?, code.as_string()?, \"\")\n                .map_err(|e| EvalError::Spanned(code.span(), Box::new(e))),\n            [json, code, args] => run_jaq_function(json.as_json_value()?, code.as_string()?, &args.as_string()?)\n                .map_err(|e| EvalError::Spanned(code.span(), Box::new(e))),\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"formattime\" => match args.as_slice() {\n            [timestamp, format, timezone] => {\n                let timezone = match chrono_tz::Tz::from_str(&timezone.as_string()?) {\n                    Ok(x) => x,\n                    Err(_) => return Err(EvalError::ChronoError(\"Invalid timezone\".to_string())),\n                };\n\n                Ok(DynVal::from(match timezone.timestamp_opt(timestamp.as_i64()?, 0) {\n                    LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => {\n                        let format = format.as_string()?;\n                        let delayed_format = t.format_localized(&format, get_locale());\n                        let mut buffer = String::new();\n                        if delayed_format.write_to(&mut buffer).is_err() {\n                            return Err(EvalError::ChronoError(\"Invalid time formatting string: \".to_string() + &format));\n                        }\n                        buffer\n                    }\n                    LocalResult::None => return Err(EvalError::ChronoError(\"Invalid UNIX timestamp\".to_string())),\n                }))\n            }\n            [timestamp, format] => Ok(DynVal::from(match Local.timestamp_opt(timestamp.as_i64()?, 0) {\n                LocalResult::Single(t) | LocalResult::Ambiguous(t, _) => {\n                    let format = format.as_string()?;\n                    let delayed_format = t.format_localized(&format, get_locale());\n                    let mut buffer = String::new();\n                    if delayed_format.write_to(&mut buffer).is_err() {\n                        return Err(EvalError::ChronoError(\"Invalid time formatting string: \".to_string() + &format));\n                    }\n                    buffer\n                }\n                LocalResult::None => return Err(EvalError::ChronoError(\"Invalid UNIX timestamp\".to_string())),\n            })),\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"log\" => match args.as_slice() {\n            [num, n] => {\n                let num = num.as_f64()?;\n                let n = n.as_f64()?;\n                Ok(DynVal::from(f64::log(num, n)))\n            }\n            _ => Err(EvalError::WrongArgCount(name.to_string())),\n        },\n        \"formatbytes\" => {\n            let (bytes, short, mode) = match args.as_slice() {\n                [bytes] => (bytes.as_i64()?, false, \"iec\".to_owned()),\n                [bytes, short] => (bytes.as_i64()?, short.as_bool()?, \"iec\".to_owned()),\n                [bytes, short, mode] => (bytes.as_i64()?, short.as_bool()?, mode.as_string()?),\n                _ => return Err(EvalError::WrongArgCount(name.to_string())),\n            };\n            let neg = bytes < 0;\n            let disp = ByteSize(bytes.abs() as u64).display();\n            let disp = match mode.as_str() {\n                \"iec\" => {\n                    if short {\n                        disp.iec_short()\n                    } else {\n                        disp.iec()\n                    }\n                }\n                \"si\" => {\n                    if short {\n                        disp.si_short()\n                    } else {\n                        disp.si()\n                    }\n                }\n                _ => return Err(EvalError::ByteFormatModeError(mode)),\n            };\n            Ok(DynVal::from(if neg { format!(\"-{disp}\") } else { disp.to_string() }))\n        }\n\n        _ => Err(EvalError::UnknownFunction(name.to_string())),\n    }\n}\n\n#[cached(size = 10, result = true, sync_writes = true)]\nfn prepare_jaq_filter(code: String) -> Result<Arc<jaq_interpret::Filter>, EvalError> {\n    let (filter, mut errors) = jaq_parse::parse(&code, jaq_parse::main());\n    let filter = match filter {\n        Some(x) => x,\n        None => return Err(EvalError::JaqParseError(Box::new(JaqParseError(errors.pop())))),\n    };\n    let mut defs = jaq_interpret::ParseCtx::new(Vec::new());\n    defs.insert_natives(jaq_core::core());\n    defs.insert_defs(jaq_std::std());\n\n    let filter = defs.compile(filter);\n\n    if let Some(error) = errors.pop() {\n        return Err(EvalError::JaqParseError(Box::new(JaqParseError(Some(error)))));\n    }\n    Ok(Arc::new(filter))\n}\n\nfn run_jaq_function(json: serde_json::Value, code: String, args: &str) -> Result<DynVal, EvalError> {\n    use jaq_interpret::{Ctx, RcIter, Val};\n    prepare_jaq_filter(code)?\n        .run((Ctx::new([], &RcIter::new(std::iter::empty())), Val::from(json)))\n        .map(|r| r.map(Into::<serde_json::Value>::into))\n        .map(|x| {\n            x.map(|val| match (args, val) {\n                (\"r\", serde_json::Value::String(s)) => DynVal::from_string(s),\n                // invalid arguments are silently ignored\n                (_, v) => DynVal::from_string(serde_json::to_string(&v).unwrap()),\n            })\n        })\n        .collect::<Result<_, _>>()\n        .map_err(|e| EvalError::JaqError(e.to_string()))\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::dynval::DynVal;\n\n    macro_rules! evals_as {\n        ($name:ident($simplexpr:expr) => $expected:expr $(,)?) => {\n            #[test]\n            fn $name() {\n                let expected: Result<$crate::dynval::DynVal, $crate::eval::EvalError> = $expected;\n\n                let parsed = match $crate::parser::parse_string(0, 0, $simplexpr.into()) {\n                    Ok(it) => it,\n                    Err(e) => {\n                        panic!(\"Could not parse input as SimpleExpr\\nInput: {}\\nReason: {}\", stringify!($simplexpr), e);\n                    }\n                };\n\n                eprintln!(\"Parsed as {parsed:#?}\");\n\n                let output = parsed.eval_no_vars();\n\n                match expected {\n                    Ok(expected) => {\n                        let actual = output.expect(\"Output was not Ok(_)\");\n\n                        assert_eq!(expected, actual);\n                    }\n                    Err(expected) => {\n                        let actual = output.expect_err(\"Output was not Err(_)\").to_string();\n                        let expected = expected.to_string();\n\n                        assert_eq!(expected, actual);\n                    }\n                }\n            }\n        };\n\n        ($name:ident($simplexpr:expr) => $expected:expr, $($tt:tt)+) => {\n            evals_as!($name($simplexpr) => $expected);\n            evals_as!($($tt)*);\n        }\n    }\n\n    evals_as! {\n        string_to_string(r#\"\"Hello\"\"#) => Ok(DynVal::from(\"Hello\".to_string())),\n        safe_access_to_existing(r#\"{ \"a\": { \"b\": 2 } }.a?.b\"#) => Ok(DynVal::from(2)),\n        safe_access_to_missing(r#\"{ \"a\": { \"b\": 2 } }.b?.b\"#) => Ok(DynVal::from(&serde_json::Value::Null)),\n        safe_access_to_empty(r#\"\"\"?.test\"#) => Ok(DynVal::from(&serde_json::Value::Null)),\n        safe_access_to_empty_json_string(r#\"'\"\"'?.test\"#) => Err(super::EvalError::CannotIndex(\"\\\"\\\"\".to_string())),\n        safe_access_index_to_existing(r#\"[1, 2]?.[1]\"#) => Ok(DynVal::from(2)),\n        safe_access_index_to_missing(r#\"\"null\"?.[1]\"#) => Ok(DynVal::from(&serde_json::Value::Null)),\n        safe_access_index_to_non_indexable(r#\"32?.[1]\"#) => Err(super::EvalError::CannotIndex(\"32\".to_string())),\n        normal_access_to_existing(r#\"{ \"a\": { \"b\": 2 } }.a.b\"#) => Ok(DynVal::from(2)),\n        normal_access_to_missing(r#\"{ \"a\": { \"b\": 2 } }.b.b\"#) => Err(super::EvalError::CannotIndex(\"null\".to_string())),\n        lazy_evaluation_and(r#\"false && \"null\".test\"#) => Ok(DynVal::from(false)),\n        lazy_evaluation_or(r#\"true || \"null\".test\"#) => Ok(DynVal::from(true)),\n        lazy_evaluation_elvis(r#\"\"test\"?: \"null\".test\"#) => Ok(DynVal::from(\"test\")),\n        jq_basic_index(r#\"jq(\"[7,8,9]\", \".[0]\")\"#) => Ok(DynVal::from(7)),\n        jq_raw_arg(r#\"jq(\"[ \\\"foo\\\" ]\", \".[0]\", \"r\")\"#) => Ok(DynVal::from(\"foo\")),\n        jq_empty_arg(r#\"jq(\"[ \\\"foo\\\" ]\", \".[0]\", \"\")\"#) => Ok(DynVal::from(r#\"\"foo\"\"#)),\n        jq_invalid_arg(r#\"jq(\"[ \\\"foo\\\" ]\", \".[0]\", \"hello\")\"#) => Ok(DynVal::from(r#\"\"foo\"\"#)),\n        jq_no_arg(r#\"jq(\"[ \\\"foo\\\" ]\", \".[0]\")\"#) => Ok(DynVal::from(r#\"\"foo\"\"#)),\n    }\n}\n"
  },
  {
    "path": "crates/simplexpr/src/lib.rs",
    "content": "pub mod ast;\npub mod dynval;\npub mod error;\npub mod eval;\npub mod parser;\n\npub use ast::SimplExpr;\n\nuse lalrpop_util::lalrpop_mod;\n\nlalrpop_mod!(\n    #[allow(clippy::all)]\n    pub simplexpr_parser\n);\n\npub use parser::parse_string;\n"
  },
  {
    "path": "crates/simplexpr/src/parser/lalrpop_helpers.rs",
    "content": "use eww_shared_util::Span;\n\nuse crate::SimplExpr;\n\nuse super::lexer::{LexicalError, Sp, StrLitSegment, Token};\n\npub fn b<T>(x: T) -> Box<T> {\n    Box::new(x)\n}\n\npub fn parse_stringlit(\n    span: Span,\n    mut segs: Vec<Sp<StrLitSegment>>,\n) -> Result<SimplExpr, lalrpop_util::ParseError<usize, Token, LexicalError>> {\n    let file_id = span.2;\n    let parser = crate::simplexpr_parser::ExprParser::new();\n\n    if segs.len() == 1 {\n        let (lo, seg, hi) = segs.remove(0);\n        let span = Span(lo, hi, file_id);\n        match seg {\n            StrLitSegment::Literal(lit) => Ok(SimplExpr::literal(span, lit)),\n            StrLitSegment::Interp(toks) => {\n                let token_stream = toks.into_iter().map(Ok);\n                parser.parse(file_id, token_stream)\n            }\n        }\n    } else {\n        let elems = segs\n            .into_iter()\n            .filter_map(|(lo, segment, hi)| {\n                let span = Span(lo, hi, file_id);\n                match segment {\n                    StrLitSegment::Literal(lit) if lit.is_empty() => None,\n                    StrLitSegment::Literal(lit) => Some(Ok(SimplExpr::literal(span, lit))),\n                    StrLitSegment::Interp(toks) => {\n                        let token_stream = toks.into_iter().map(Ok);\n                        Some(parser.parse(file_id, token_stream))\n                    }\n                }\n            })\n            .collect::<Result<Vec<SimplExpr>, _>>()?;\n        Ok(SimplExpr::Concat(span, elems))\n    }\n}\n"
  },
  {
    "path": "crates/simplexpr/src/parser/lexer.rs",
    "content": "use eww_shared_util::{Span, Spanned};\nuse once_cell::sync::Lazy;\nuse regex::{Regex, RegexSet};\n\npub type Sp<T> = (usize, T, usize);\n\n#[derive(Debug, PartialEq, Eq, Clone, strum::Display, strum::EnumString)]\npub enum StrLitSegment {\n    Literal(String),\n    Interp(Vec<Sp<Token>>),\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, strum::Display, strum::EnumString)]\npub enum Token {\n    Plus,\n    Minus,\n    Times,\n    Div,\n    Mod,\n    Equals,\n    NotEquals,\n    And,\n    Or,\n    GE,\n    LE,\n    GT,\n    LT,\n    Elvis,\n    SafeAccess,\n    RegexMatch,\n\n    Not,\n    Negative,\n\n    Comma,\n    Question,\n    Colon,\n    LPren,\n    RPren,\n    LCurl,\n    RCurl,\n    LBrack,\n    RBrack,\n    Dot,\n    True,\n    False,\n\n    Ident(String),\n    NumLit(String),\n\n    StringLit(Vec<Sp<StrLitSegment>>),\n\n    Comment,\n    Skip,\n}\n\nmacro_rules! regex_rules {\n    ($( $regex:literal => $token:expr),*) => {\n        static LEXER_REGEX_SET: Lazy<RegexSet> = Lazy::new(|| { RegexSet::new(&[\n            $(concat!(\"^\", $regex)),*\n        ]).unwrap()});\n        static LEXER_REGEXES: Lazy<Vec<Regex>> = Lazy::new(|| { vec![\n            $(Regex::new(concat!(\"^\", $regex)).unwrap()),*\n        ]});\n        static LEXER_FNS: Lazy<Vec<Box<dyn Fn(String) -> Token + Sync + Send>>> = Lazy::new(|| { vec![\n            $(Box::new($token)),*\n        ]});\n    }\n}\n\nstatic ESCAPE_REPLACE_REGEX: Lazy<regex::Regex> = Lazy::new(|| Regex::new(r\"\\\\(.)\").unwrap());\npub static STR_INTERPOLATION_START: &str = \"${\";\npub static STR_INTERPOLATION_END: &str = \"}\";\n\nregex_rules! {\n    r\"\\+\"     => |_| Token::Plus,\n    r\"-\"     => |_| Token::Minus,\n    r\"\\*\"     => |_| Token::Times,\n    r\"/\"     => |_| Token::Div,\n    r\"%\"     => |_| Token::Mod,\n    r\"==\"    => |_| Token::Equals,\n    r\"!=\"    => |_| Token::NotEquals,\n    r\"&&\"    => |_| Token::And,\n    r\"\\|\\|\"    => |_| Token::Or,\n    r\">=\"    => |_| Token::GE,\n    r\"<=\"    => |_| Token::LE,\n    r\">\"     => |_| Token::GT,\n    r\"<\"     => |_| Token::LT,\n    r\"\\?:\"    => |_| Token::Elvis,\n    r\"\\?\\.\"    => |_| Token::SafeAccess,\n    r\"=~\"    => |_| Token::RegexMatch,\n\n    r\"!\"     => |_| Token::Not,\n    r\"-\"     => |_| Token::Negative,\n\n    r\",\"     => |_| Token::Comma,\n    r\"\\?\"     => |_| Token::Question,\n    r\":\"     => |_| Token::Colon,\n    r\"\\(\"     => |_| Token::LPren,\n    r\"\\)\"     => |_| Token::RPren,\n    r\"\\[\"     => |_| Token::LBrack,\n    r\"\\]\"     => |_| Token::RBrack,\n    r\"\\{\"     => |_| Token::LCurl,\n    r\"\\}\"     => |_| Token::RCurl,\n    r\"\\.\"     => |_| Token::Dot,\n    r\"true\"  => |_| Token::True,\n    r\"false\" => |_| Token::False,\n\n    r\"\\s+\" => |_| Token::Skip,\n    r\";.*\"=> |_| Token::Comment,\n\n    r\"[a-zA-Z_][a-zA-Z0-9_-]*\" => Token::Ident,\n    r\"[+-]?(?:[0-9]+[.])?[0-9]+\" => Token::NumLit\n}\n\n#[derive(Debug)]\npub struct Lexer<'s> {\n    file_id: usize,\n    source: &'s str,\n    pos: usize,\n    failed: bool,\n    offset: usize,\n}\n\nimpl<'s> Lexer<'s> {\n    pub fn new(file_id: usize, span_offset: usize, source: &'s str) -> Self {\n        Lexer { source, offset: span_offset, file_id, failed: false, pos: 0 }\n    }\n\n    fn remaining(&self) -> &'s str {\n        &self.source[self.pos..]\n    }\n\n    pub fn next_token(&mut self) -> Option<Result<Sp<Token>, LexicalError>> {\n        loop {\n            if self.failed || self.pos >= self.source.len() {\n                return None;\n            }\n            let remaining = self.remaining();\n\n            if remaining.starts_with(&['\"', '\\'', '`'][..]) {\n                return self.string_lit().map(|x| x.map(|(lo, segs, hi)| (lo, Token::StringLit(segs), hi)));\n            } else {\n                let match_set = LEXER_REGEX_SET.matches(remaining);\n                let matched_token = match_set\n                    .into_iter()\n                    .map(|i: usize| {\n                        let m = LEXER_REGEXES[i].find(remaining).unwrap();\n                        (m.end(), i)\n                    })\n                    .min_by_key(|(_, x)| *x);\n\n                let (len, i) = match matched_token {\n                    Some(x) => x,\n                    None => {\n                        self.failed = true;\n                        return Some(Err(LexicalError(Span(self.pos + self.offset, self.pos + self.offset, self.file_id))));\n                    }\n                };\n\n                let tok_str = &self.source[self.pos..self.pos + len];\n                let old_pos = self.pos;\n                self.advance_by(len)?;\n                match LEXER_FNS[i](tok_str.to_string()) {\n                    Token::Skip | Token::Comment => {}\n                    token => {\n                        return Some(Ok((old_pos + self.offset, token, self.pos + self.offset)));\n                    }\n                }\n            }\n        }\n    }\n\n    /// Advance position by the given number of characters, respecting char boundaries. Returns `None` when n exceeds the source length\n    #[must_use]\n    fn advance_by(&mut self, n: usize) -> Option<()> {\n        if self.pos + n > self.source.len() {\n            return None;\n        }\n        self.pos += n;\n        while self.pos < self.source.len() && !self.source.is_char_boundary(self.pos) {\n            self.pos += 1;\n        }\n        Some(())\n    }\n\n    fn advance_until_one_of<'a>(&mut self, pat: &[&'a str]) -> Option<&'a str> {\n        loop {\n            let remaining = self.remaining();\n            if remaining.is_empty() {\n                return None;\n            } else if let Some(matched) = pat.iter().find(|&&p| remaining.starts_with(p)) {\n                self.advance_by(matched.len())?;\n                return Some(matched);\n            } else {\n                self.advance_by(1)?;\n            }\n        }\n    }\n\n    fn advance_until_unescaped_one_of<'a>(&mut self, pat: &[&'a str]) -> Option<&'a str> {\n        let mut pattern = pat.to_vec();\n        pattern.push(\"\\\\\");\n        match self.advance_until_one_of(pattern.as_slice()) {\n            Some(\"\\\\\") => {\n                self.advance_by(1)?;\n                self.advance_until_unescaped_one_of(pat)\n            }\n            result => result,\n        }\n    }\n\n    pub fn string_lit(&mut self) -> Option<Result<Sp<Vec<Sp<StrLitSegment>>>, LexicalError>> {\n        let quote = self.remaining().chars().next()?.to_string();\n        let str_lit_start = self.pos;\n        self.advance_by(quote.len())?;\n\n        let mut elements = Vec::new();\n        let mut in_string_lit = true;\n        loop {\n            if in_string_lit {\n                let segment_start = self.pos - quote.len();\n\n                let segment_ender = self.advance_until_unescaped_one_of(&[STR_INTERPOLATION_START, &quote])?;\n                let lit_content = &self.source[segment_start + quote.len()..self.pos - segment_ender.len()];\n                let lit_content = ESCAPE_REPLACE_REGEX.replace_all(lit_content, \"$1\").to_string();\n                elements.push((segment_start + self.offset, StrLitSegment::Literal(lit_content), self.pos + self.offset));\n\n                if segment_ender == STR_INTERPOLATION_START {\n                    in_string_lit = false;\n                } else if segment_ender == quote {\n                    return Some(Ok((str_lit_start + self.offset, elements, self.pos + self.offset)));\n                }\n            } else {\n                let segment_start = self.pos;\n                let mut toks = Vec::new();\n                let mut curly_nesting = 0;\n\n                'inner: while let Some(tok) = self.next_token() {\n                    if self.pos >= self.source.len() {\n                        break 'inner;\n                    }\n\n                    let tok = match tok {\n                        Ok(x) => x,\n                        Err(e) => return Some(Err(e)),\n                    };\n                    if tok.1 == Token::LCurl {\n                        curly_nesting += 1;\n                    } else if tok.1 == Token::RCurl {\n                        curly_nesting -= 1;\n                    }\n\n                    if curly_nesting < 0 {\n                        break 'inner;\n                    } else {\n                        toks.push(tok);\n                    }\n                }\n\n                elements.push((segment_start + self.offset, StrLitSegment::Interp(toks), self.pos + self.offset - 1));\n                in_string_lit = true;\n            }\n        }\n    }\n}\n\nimpl<'s> Iterator for Lexer<'s> {\n    type Item = Result<Sp<Token>, LexicalError>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        self.next_token()\n    }\n}\n\n#[derive(Debug, Eq, PartialEq, Copy, Clone)]\npub struct LexicalError(pub Span);\n\nimpl Spanned for LexicalError {\n    fn span(&self) -> Span {\n        self.0\n    }\n}\n\nimpl std::fmt::Display for LexicalError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Lexical error at {}\", self.0)\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use eww_shared_util::snapshot_string;\n    use itertools::Itertools;\n\n    macro_rules! v {\n        ($x:literal) => {\n            Lexer::new(0, 0, $x)\n                .map(|x| match x {\n                    Ok((l, x, r)) => format!(\"({}, {:?}, {})\", l, x, r),\n                    Err(err) => format!(\"{}\", err),\n                })\n                .join(\"\\n\")\n        };\n    }\n\n    snapshot_string! {\n        basic                 => v!(r#\"bar \"foo\"\"#),\n        digit                 => v!(r#\"12\"#),\n        quote_backslash_eof   => v!(r#\"\"\\\"#),\n        number_in_ident       => v!(r#\"foo_1_bar\"#),\n        interpolation_1       => v!(r#\" \"foo ${2 * 2} bar\" \"#),\n        interpolation_nested  => v!(r#\" \"foo ${(2 * 2) + \"${5 + 5}\"} bar\" \"#),\n        json_in_interpolation => v!(r#\" \"${ {1: 2} }\" \"#),\n        escaping              => v!(r#\" \"a\\\"b\\{}\" \"#),\n        comments              => v!(\"foo ; bar\"),\n        weird_char_boundaries => v!(r#\"\"   \" + music\"#),\n        symbol_spam           => v!(r#\"(foo + - \"()\" \"a\\\"b\" true false [] 12.2)\"#),\n        weird_nesting => v!(r#\"\n            \"${ {\"hi\": \"ho\"}.hi }\".hi\n        \"#),\n        empty_interpolation   => v!(r#\"\"${}\"\"#),\n        safe_interpolation   => v!(r#\"\"${ { \"key\": \"value\" }.key1?.key2 ?: \"Recovery\" }\"\"#),\n    }\n}\n"
  },
  {
    "path": "crates/simplexpr/src/parser/mod.rs",
    "content": "pub mod lalrpop_helpers;\npub mod lexer;\n\nuse crate::{ast::SimplExpr, error::ParseError};\n\npub fn parse_string(byte_offset: usize, file_id: usize, s: &str) -> Result<SimplExpr, ParseError> {\n    let lexer = lexer::Lexer::new(file_id, byte_offset, s);\n    let parser = crate::simplexpr_parser::ExprParser::new();\n    parser.parse(file_id, lexer).map_err(|e| ParseError::from_parse_error(file_id, e))\n}\n\n#[cfg(test)]\nmod tests {\n    macro_rules! test_parser {\n        ($($text:literal),* $(,)?) => {{\n            let p = crate::simplexpr_parser::ExprParser::new();\n            use crate::parser::lexer::Lexer;\n            ::insta::with_settings!({sort_maps => true}, {\n                $(\n                    ::insta::assert_debug_snapshot!(p.parse(0, Lexer::new(0, 0, $text)));\n                )*\n            });\n        }}\n    }\n\n    #[test]\n    fn test() {\n        test_parser!(\n            \"1\",\n            \"2 + 5\",\n            \"2 * 5 + 1 * 1 + 3\",\n            \"(1 + 2) * 2\",\n            \"1 + true ? 2 : 5\",\n            \"1 + true ? 2 : 5 + 2\",\n            \"1 + (true ? 2 : 5) + 2\",\n            \"foo(1, 2)\",\n            \"! false || ! true\",\n            \"\\\"foo\\\" + 12.4\",\n            \"hi[\\\"ho\\\"]\",\n            \"foo.bar.baz\",\n            \"foo.bar[2 + 2] * asdf[foo.bar]\",\n            r#\"[1, 2, 3 + 4, \"bla\", [blub, blo]]\"#,\n            r#\"{ \"key\": \"value\", 5: 1+2, true: false }\"#,\n            r#\"{ \"key\": \"value\" }?.key?.does_not_exist\"#,\n        );\n    }\n}\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__basic.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"bar \\\"foo\\\"\\\"#)\"\n\n---\n(0, Ident(\"bar\"), 3)\n(4, StringLit([(4, Literal(\"foo\"), 9)]), 9)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__comments.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(\\\"foo ; bar\\\")\"\n\n---\n(0, Ident(\"foo\"), 3)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__digit.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"12\\\"#)\"\n\n---\n(0, NumLit(\"12\"), 2)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__empty_interpolation.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nassertion_line: 306\nexpression: \"v!(r#\\\"\\\"${}\\\"\\\"#)\"\n---\n(0, StringLit([(0, Literal(\"\"), 3), (3, Interp([]), 3), (3, Literal(\"\"), 5)]), 5)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__escaping.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\" \\\"a\\\\\\\"b\\\\{}\\\" \\\"#)\"\n\n---\n(1, StringLit([(1, Literal(\"a\\\"b{}\"), 10)]), 10)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__interpolation_1.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\" \\\"foo ${2 * 2} bar\\\" \\\"#)\"\n\n---\n(1, StringLit([(1, Literal(\"foo \"), 8), (8, Interp([(8, NumLit(\"2\"), 9), (10, Times, 11), (12, NumLit(\"2\"), 13)]), 13), (13, Literal(\" bar\"), 19)]), 19)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__interpolation_nested.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\" \\\"foo ${(2 * 2) + \\\"${5 + 5}\\\"} bar\\\" \\\"#)\"\n\n---\n(1, StringLit([(1, Literal(\"foo \"), 8), (8, Interp([(8, LPren, 9), (9, NumLit(\"2\"), 10), (11, Times, 12), (13, NumLit(\"2\"), 14), (14, RPren, 15), (16, Plus, 17), (18, StringLit([(18, Literal(\"\"), 21), (21, Interp([(21, NumLit(\"5\"), 22), (23, Plus, 24), (25, NumLit(\"5\"), 26)]), 26), (26, Literal(\"\"), 28)]), 28)]), 28), (28, Literal(\" bar\"), 34)]), 34)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__json_in_interpolation.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\" \\\"${ {1: 2} }\\\" \\\"#)\"\n\n---\n(1, StringLit([(1, Literal(\"\"), 4), (4, Interp([(5, LCurl, 6), (6, NumLit(\"1\"), 7), (7, Colon, 8), (9, NumLit(\"2\"), 10), (10, RCurl, 11)]), 12), (12, Literal(\"\"), 14)]), 14)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__number_in_ident.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"foo_1_bar\\\"#)\"\n\n---\n(0, Ident(\"foo_1_bar\"), 9)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__quote_backslash_eof.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nassertion_line: 313\nexpression: \"v!(r#\\\"\\\"\\\\\\\"#)\"\n---\n\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__safe_interpolation.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"\\\"${ { \\\"key\\\": \\\"value\\\" }.key1?.key2 ?: \\\"Recovery\\\" }\\\"\\\"#)\"\n---\n(0, StringLit([(0, Literal(\"\"), 3), (3, Interp([(4, LCurl, 5), (6, StringLit([(6, Literal(\"key\"), 11)]), 11), (11, Colon, 12), (13, StringLit([(13, Literal(\"value\"), 20)]), 20), (21, RCurl, 22), (22, Dot, 23), (23, Ident(\"key1\"), 27), (27, SafeAccess, 29), (29, Ident(\"key2\"), 33), (34, Elvis, 36), (37, StringLit([(37, Literal(\"Recovery\"), 47)]), 47)]), 48), (48, Literal(\"\"), 50)]), 50)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__simplexpr_lexer_basic.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"Lexer::new(0, 0, r#\\\"bar \\\"foo\\\"\\\"#).collect_vec()\"\n\n---\n[\n    Ok(\n        (\n            0,\n            Ident(\n                \"bar\",\n            ),\n            3,\n        ),\n    ),\n    Ok(\n        (\n            4,\n            StringLit(\n                [\n                    (\n                        4,\n                        Literal(\n                            \"foo\",\n                        ),\n                        9,\n                    ),\n                ],\n            ),\n            9,\n        ),\n    ),\n]\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate-2.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"Lexer::new(0, 0, r#\\\" \\\"foo {(2 * 2) + \\\"{5 + 5}\\\"} bar\\\" \\\"#).collect_vec()\"\n\n---\n[\n    Ok(\n        (\n            1,\n            StringLit(\n                [\n                    (\n                        1,\n                        Literal(\n                            \"foo \",\n                        ),\n                        7,\n                    ),\n                    (\n                        7,\n                        Interp(\n                            [\n                                (\n                                    7,\n                                    LPren,\n                                    8,\n                                ),\n                                (\n                                    8,\n                                    NumLit(\n                                        \"2\",\n                                    ),\n                                    9,\n                                ),\n                                (\n                                    10,\n                                    Times,\n                                    11,\n                                ),\n                                (\n                                    12,\n                                    NumLit(\n                                        \"2\",\n                                    ),\n                                    13,\n                                ),\n                                (\n                                    13,\n                                    RPren,\n                                    14,\n                                ),\n                                (\n                                    15,\n                                    Plus,\n                                    16,\n                                ),\n                                (\n                                    17,\n                                    StringLit(\n                                        [\n                                            (\n                                                17,\n                                                Literal(\n                                                    \"\",\n                                                ),\n                                                19,\n                                            ),\n                                            (\n                                                19,\n                                                Interp(\n                                                    [\n                                                        (\n                                                            19,\n                                                            NumLit(\n                                                                \"5\",\n                                                            ),\n                                                            20,\n                                                        ),\n                                                        (\n                                                            21,\n                                                            Plus,\n                                                            22,\n                                                        ),\n                                                        (\n                                                            23,\n                                                            NumLit(\n                                                                \"5\",\n                                                            ),\n                                                            24,\n                                                        ),\n                                                    ],\n                                                ),\n                                                24,\n                                            ),\n                                            (\n                                                24,\n                                                Literal(\n                                                    \"\",\n                                                ),\n                                                26,\n                                            ),\n                                        ],\n                                    ),\n                                    26,\n                                ),\n                            ],\n                        ),\n                        26,\n                    ),\n                    (\n                        26,\n                        Literal(\n                            \" bar\",\n                        ),\n                        32,\n                    ),\n                ],\n            ),\n            32,\n        ),\n    ),\n]\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate-3.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"Lexer::new(0, 0, r#\\\" \\\"a\\\\\\\"b\\\\{}\\\" \\\"#).collect_vec()\"\n\n---\n[\n    Ok(\n        (\n            1,\n            StringLit(\n                [\n                    (\n                        1,\n                        Literal(\n                            \"a\\\"b{}\",\n                        ),\n                        10,\n                    ),\n                ],\n            ),\n            10,\n        ),\n    ),\n]\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__simplexpr_lexer_str_interpolate.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"Lexer::new(0, 0, r#\\\" \\\"foo {2 * 2} bar\\\" \\\"#).collect_vec()\"\n\n---\n[\n    Ok(\n        (\n            1,\n            StringLit(\n                [\n                    (\n                        1,\n                        Literal(\n                            \"foo \",\n                        ),\n                        7,\n                    ),\n                    (\n                        7,\n                        Interp(\n                            [\n                                (\n                                    7,\n                                    NumLit(\n                                        \"2\",\n                                    ),\n                                    8,\n                                ),\n                                (\n                                    9,\n                                    Times,\n                                    10,\n                                ),\n                                (\n                                    11,\n                                    NumLit(\n                                        \"2\",\n                                    ),\n                                    12,\n                                ),\n                            ],\n                        ),\n                        12,\n                    ),\n                    (\n                        12,\n                        Literal(\n                            \" bar\",\n                        ),\n                        18,\n                    ),\n                ],\n            ),\n            18,\n        ),\n    ),\n]\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__symbol_spam.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"(foo + - \\\"()\\\" \\\"a\\\\\\\"b\\\" true false [] 12.2)\\\"#)\"\n\n---\n(0, LPren, 1)\n(1, Ident(\"foo\"), 4)\n(5, Plus, 6)\n(7, Minus, 8)\n(9, StringLit([(9, Literal(\"()\"), 13)]), 13)\n(14, StringLit([(14, Literal(\"a\\\"b\"), 20)]), 20)\n(21, True, 25)\n(26, False, 31)\n(32, LBrack, 33)\n(33, RBrack, 34)\n(35, NumLit(\"12.2\"), 39)\n(39, RPren, 40)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__weird_char_boundaries.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"\\\"   \\\" + music\\\"#)\"\n\n---\n(0, StringLit([(0, Literal(\"\\u{f001}   \"), 8)]), 8)\n(9, Plus, 10)\n(11, Ident(\"music\"), 16)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__lexer__test__weird_nesting.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/lexer.rs\nexpression: \"v!(r#\\\"\\n            \\\"${ {\\\"hi\\\": \\\"ho\\\"}.hi }\\\".hi\\n        \\\"#)\"\n\n---\n(13, StringLit([(13, Literal(\"\"), 16), (16, Interp([(17, LCurl, 18), (18, StringLit([(18, Literal(\"hi\"), 22)]), 22), (22, Colon, 23), (24, StringLit([(24, Literal(\"ho\"), 28)]), 28), (28, RCurl, 29), (29, Dot, 30), (30, Ident(\"hi\"), 32)]), 33), (33, Literal(\"\"), 35)]), 35)\n(35, Dot, 36)\n(36, Ident(\"hi\"), 38)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-10.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"\\\\\\\"foo\\\\\\\" + 12.4\\\"))\"\n\n---\nOk(\n    (\"foo\" + \"12.4\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-11.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"hi[\\\\\\\"ho\\\\\\\"]\\\"))\"\n\n---\nOk(\n    hi[\"ho\"],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-12.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"foo.bar.baz\\\"))\"\n\n---\nOk(\n    foo[\"bar\"][\"baz\"],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-13.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"foo.bar[2 + 2] * asdf[foo.bar]\\\"))\"\n\n---\nOk(\n    (foo[\"bar\"][(\"2\" + \"2\")] * asdf[foo[\"bar\"]]),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-14.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, r#\\\"[1, 2, 3 + 4, \\\"bla\\\", [blub, blo]]\\\"#))\"\n\n---\nOk(\n    [\"1\", \"2\", (\"3\" + \"4\"), \"bla\", [blub, blo]],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-15.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, r#\\\"{ \\\"key\\\": \\\"value\\\", 5: 1+2, true: false }\\\"#))\"\n\n---\nOk(\n    {\"key\": \"value\", \"5\": (\"1\" + \"2\"), \"true\": \"false\"},\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-16.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, r#\\\"{ \\\"key\\\": \\\"value\\\" }?.key?.does_not_exist\\\"#))\"\n---\nOk(\n    {\"key\": \"value\"}?.[\"key\"]?.[\"does_not_exist\"],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-2.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"2 + 5\\\"))\"\n\n---\nOk(\n    (\"2\" + \"5\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-3.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"2 * 5 + 1 * 1 + 3\\\"))\"\n\n---\nOk(\n    (((\"2\" * \"5\") + (\"1\" * \"1\")) + \"3\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-4.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"(1 + 2) * 2\\\"))\"\n\n---\nOk(\n    ((\"1\" + \"2\") * \"2\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-5.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"1 + true ? 2 : 5\\\"))\"\n\n---\nOk(\n    ((\"1\" + \"true\") ? \"2\" : \"5\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-6.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"1 + true ? 2 : 5 + 2\\\"))\"\n\n---\nOk(\n    ((\"1\" + \"true\") ? \"2\" : (\"5\" + \"2\")),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-7.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"1 + (true ? 2 : 5) + 2\\\"))\"\n\n---\nOk(\n    ((\"1\" + (\"true\" ? \"2\" : \"5\")) + \"2\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-8.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"foo(1, 2)\\\"))\"\n\n---\nOk(\n    foo(\"1\", \"2\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test-9.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"! false || ! true\\\"))\"\n\n---\nOk(\n    (!\"false\" || !\"true\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/parser/snapshots/simplexpr__parser__tests__test.snap",
    "content": "---\nsource: crates/simplexpr/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, 0, \\\"1\\\"))\"\n\n---\nOk(\n    \"1\",\n)\n"
  },
  {
    "path": "crates/simplexpr/src/simplexpr_parser.lalrpop",
    "content": "use crate::ast::{SimplExpr::{self, *}, BinOp::*, UnaryOp::*, AccessType};\nuse eww_shared_util::{Span, VarName};\nuse crate::parser::lexer::{Token, LexicalError, StrLitSegment, Sp};\nuse crate::parser::lalrpop_helpers::*;\n\n\ngrammar(fid: usize);\n\nextern {\n  type Location = usize;\n  type Error = LexicalError;\n\n  enum Token {\n    \"+\"  => Token::Plus,\n    \"-\"  => Token::Minus,\n    \"*\"  => Token::Times,\n    \"/\"  => Token::Div,\n    \"%\"  => Token::Mod,\n    \"==\" => Token::Equals,\n    \"!=\" => Token::NotEquals,\n    \"&&\" => Token::And,\n    \"||\" => Token::Or,\n    \">=\" => Token::GE,\n    \"<=\" => Token::LE,\n    \">\"  => Token::GT,\n    \"<\"  => Token::LT,\n    \"?:\" => Token::Elvis,\n    \"?.\" => Token::SafeAccess,\n    \"=~\" => Token::RegexMatch,\n\n    \"!\"  => Token::Not,\n\n    \",\"  => Token::Comma,\n    \"?\"  => Token::Question,\n    \":\"  => Token::Colon,\n    \"(\"  => Token::LPren,\n    \")\"  => Token::RPren,\n    \"[\"  => Token::LBrack,\n    \"]\"  => Token::RBrack,\n    \"{\"  => Token::LCurl,\n    \"}\"  => Token::RCurl,\n    \".\"  => Token::Dot,\n\n    \"true\"  => Token::True,\n    \"false\" => Token::False,\n\n    \"identifier\" => Token::Ident(<String>),\n    \"number\"     => Token::NumLit(<String>),\n    \"string\"     => Token::StringLit(<Vec<Sp<StrLitSegment>>>),\n\n  }\n}\n\nComma<T>: Vec<T> = {\n    <mut v:(<T> \",\")*> <e:T?> => match e {\n        None => v,\n        Some(e) => {\n            v.push(e);\n            v\n        }\n    }\n};\n\npub Expr: SimplExpr = {\n\n  #[precedence(level=\"0\")]\n  <l:@L> <x:\"string\"> <r:@R> =>? parse_stringlit(Span(l, r, fid), x),\n  <l:@L> <x:\"number\"> <r:@R> => SimplExpr::literal(Span(l, r, fid), x),\n  <l:@L> \"true\"       <r:@R> => SimplExpr::literal(Span(l, r, fid), \"true\".into()),\n  <l:@L> \"false\"      <r:@R> => SimplExpr::literal(Span(l, r, fid), \"false\".into()),\n\n  <l:@L> <ident:\"identifier\"> <r:@R> => VarRef(Span(l, r, fid), VarName(ident.to_string())),\n  \"(\" <ExprReset> \")\",\n  <l:@L> \"[\" <values: Comma<ExprReset>> \"]\" <r:@R> => SimplExpr::JsonArray(Span(l, r, fid), values),\n  <l:@L> \"{\" <values: Comma<JsonKeyValue>> \"}\" <r:@R> => SimplExpr::JsonObject(Span(l, r, fid), values),\n\n  #[precedence(level=\"1\")] #[assoc(side=\"right\")]\n  <l:@L> <ident:\"identifier\"> \"(\" <args: Comma<ExprReset>> \")\" <r:@R> => FunctionCall(Span(l, r, fid), ident, args),\n\n  <l:@L> <value:Expr>         \"[\" <index: ExprReset>       \"]\" <r:@R> => {\n    JsonAccess(Span(l, r, fid), AccessType::Normal, b(value), b(index))\n  },\n  <l:@L> <value:Expr>    \"?.\" \"[\" <index: ExprReset>       \"]\" <r:@R> => {\n    JsonAccess(Span(l, r, fid), AccessType::Safe, b(value), b(index))\n  },\n\n  <l:@L> <value:Expr> \".\" <lit_l:@L> <index:\"identifier\"> <r:@R> => {\n    JsonAccess(Span(l, r, fid), AccessType::Normal, b(value), b(Literal(index.into())))\n  },\n\n  <l:@L> <value:Expr> \"?.\" <lit_l:@L> <index:\"identifier\"> <r:@R> => {\n    JsonAccess(Span(l, r, fid), AccessType::Safe, b(value), b(Literal(index.into())))\n  },\n\n  #[precedence(level=\"2\")] #[assoc(side=\"right\")]\n  <l:@L> \"!\" <e:Expr> <r:@R> => UnaryOp(Span(l, r, fid), Not, b(e)),\n  <l:@L> \"-\" <e:Expr> <r:@R> => UnaryOp(Span(l, r, fid), Negative, b(e)),\n\n  #[precedence(level=\"3\")] #[assoc(side=\"left\")]\n  <l:@L> <le:Expr> \"*\"  <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Times,       b(re)),\n  <l:@L> <le:Expr> \"/\"  <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Div,         b(re)),\n  <l:@L> <le:Expr> \"%\"  <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Mod,         b(re)),\n\n  #[precedence(level=\"4\")] #[assoc(side=\"left\")]\n  <l:@L> <le:Expr> \"+\"  <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Plus,        b(re)),\n  <l:@L> <le:Expr> \"-\"  <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Minus,       b(re)),\n\n  #[precedence(level=\"5\")] #[assoc(side=\"left\")]\n  <l:@L> <le:Expr> \"==\" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Equals,     b(re)),\n  <l:@L> <le:Expr> \"!=\" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), NotEquals,  b(re)),\n  <l:@L> <le:Expr> \">=\" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), GE,         b(re)),\n  <l:@L> <le:Expr> \"<=\" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), LE,         b(re)),\n  <l:@L> <le:Expr> \">\"  <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), GT,         b(re)),\n  <l:@L> <le:Expr> \"<\"  <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), LT,         b(re)),\n  <l:@L> <le:Expr> \"=~\" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), RegexMatch, b(re)),\n\n  #[precedence(level=\"6\")] #[assoc(side=\"left\")]\n  <l:@L> <le:Expr> \"&&\" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), And,        b(re)),\n  <l:@L> <le:Expr> \"||\" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Or,         b(re)),\n  <l:@L> <le:Expr> \"?:\" <re:Expr> <r:@R> => BinOp(Span(l, r, fid), b(le), Elvis,      b(re)),\n\n  #[precedence(level=\"7\")] #[assoc(side=\"right\")]\n  <l:@L> <cond:Expr> \"?\" <then:ExprReset> \":\" <els:Expr> <r:@R> => {\n    IfElse(Span(l, r, fid), b(cond), b(then), b(els))\n  },\n};\n\nExprReset = <Expr>;\n\n\nJsonKeyValue = <Expr> \":\" <Expr>;\n\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-2.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"1s\\\").as_duration()\"\n\n---\nOk(\n    1s,\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-3.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"0.1s\\\").as_duration()\"\n\n---\nOk(\n    100ms,\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-4.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"5m\\\").as_duration()\"\n\n---\nOk(\n    300s,\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-5.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"5min\\\").as_duration()\"\n\n---\nOk(\n    300s,\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-6.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"0.5m\\\").as_duration()\"\n\n---\nOk(\n    30s,\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-7.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"1h\\\").as_duration()\"\n\n---\nOk(\n    3600s,\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration-8.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"0.5h\\\").as_duration()\"\n\n---\nOk(\n    1800s,\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_duration.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from(\\\"100ms\\\").as_duration()\"\n\n---\nOk(\n    100ms,\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-2.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[hi]\\\".to_string()).as_vec()\"\n\n---\nOk(\n    [\n        \"hi\",\n    ],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-3.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[hi,ho,hu]\\\".to_string()).as_vec()\"\n\n---\nOk(\n    [\n        \"hi\",\n        \"ho\",\n        \"hu\",\n    ],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-4.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[hi\\\\\\\\,ho]\\\".to_string()).as_vec()\"\n\n---\nOk(\n    [\n        \"hi,ho\",\n    ],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-5.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[hi\\\\\\\\,ho,hu]\\\".to_string()).as_vec()\"\n\n---\nOk(\n    [\n        \"hi,ho\",\n        \"hu\",\n    ],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-6.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"\\\".to_string()).as_vec()\"\n\n---\nOk(\n    [],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-7.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[a,b\\\".to_string()).as_vec()\"\n\n---\nErr(\n    ConversionError {\n        value: \"[a,b\",\n        target_type: \"vec\",\n        source: None,\n    },\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec-8.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"a]\\\".to_string()).as_vec()\"\n\n---\nErr(\n    ConversionError {\n        value: \"a]\",\n        target_type: \"vec\",\n        source: None,\n    },\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__dynval__test__parse_vec.snap",
    "content": "---\nsource: crates/simplexpr/src/dynval.rs\nexpression: \"DynVal::from_string(\\\"[]\\\".to_string()).as_vec()\"\n\n---\nOk(\n    [\n        \"\",\n    ],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-10.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"\\\\\\\"foo\\\\\\\" + 12.4\\\"))\"\n\n---\nOk(\n    (\"foo\" + \"12.4\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-11.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"hi[\\\\\\\"ho\\\\\\\"]\\\"))\"\n\n---\nOk(\n    hi[\"ho\"],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-12.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo.bar.baz\\\"))\"\n\n---\nOk(\n    foo[\"bar\"][\"baz\"],\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-13.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo.bar[2 + 2] * asdf[foo.bar]\\\"))\"\n\n---\nOk(\n    (foo[\"bar\"][(\"2\" + \"2\")] * asdf[foo[\"bar\"]]),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-14.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"1 + (true ? 2 : 5) + 2\\\"))\"\n\n---\nOk(\n    BinOp(\n        BinOp(\n            Literal(\n                \"1\",\n            ),\n            Plus,\n            IfElse(\n                Literal(\n                    \"true\",\n                ),\n                Literal(\n                    \"2\",\n                ),\n                Literal(\n                    \"5\",\n                ),\n            ),\n        ),\n        Plus,\n        Literal(\n            \"2\",\n        ),\n    ),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-15.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"foo(1, 2)\\\").filter_map(|x|\\n                                       x.ok()).map(|(_, x, _)|\\n                                                       match x {\\n                                                           Token::Ident(x) |\\n                                                           Token::NumLit(x) |\\n                                                           Token::StrLit(x) =>\\n                                                           format!(\\\"{}\\\", x),\\n                                                           x =>\\n                                                           format!(\\\"{}\\\", x),\\n                                                       }).collect::<Vec<_>>()\"\n\n---\n[\n    \"foo\",\n    \"LPren\",\n    \"1\",\n    \"Comma\",\n    \"2\",\n    \"RPren\",\n]\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-16.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo(1, 2)\\\"))\"\n\n---\nOk(\n    FunctionCall(\n        \"foo\",\n        [\n            Literal(\n                \"1\",\n            ),\n            Literal(\n                \"2\",\n            ),\n        ],\n    ),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-17.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"! false || ! true\\\").filter_map(|x|\\n                                               x.ok()).map(|(_, x, _)|\\n                                                               match x {\\n                                                                   Token::Ident(x)\\n                                                                   |\\n                                                                   Token::NumLit(x)\\n                                                                   |\\n                                                                   Token::StrLit(x)\\n                                                                   =>\\n                                                                   format!(\\\"{}\\\",\\n                                                                           x),\\n                                                                   x =>\\n                                                                   format!(\\\"{}\\\",\\n                                                                           x),\\n                                                               }).collect::<Vec<_>>()\"\n\n---\n[\n    \"!\",\n    \"False\",\n    \"||\",\n    \"!\",\n    \"True\",\n]\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-18.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"! false || ! true\\\"))\"\n\n---\nOk(\n    BinOp(\n        UnaryOp(\n            Not,\n            Literal(\n                \"false\",\n            ),\n        ),\n        Or,\n        UnaryOp(\n            Not,\n            Literal(\n                \"true\",\n            ),\n        ),\n    ),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-19.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"\\\\\\\"foo\\\\\\\" + 12.4\\\").filter_map(|x|\\n                                            x.ok()).map(|(_, x, _)|\\n                                                            match x {\\n                                                                Token::Ident(x)\\n                                                                |\\n                                                                Token::NumLit(x)\\n                                                                |\\n                                                                Token::StrLit(x)\\n                                                                =>\\n                                                                format!(\\\"{}\\\",\\n                                                                        x),\\n                                                                x =>\\n                                                                format!(\\\"{}\\\",\\n                                                                        x),\\n                                                            }).collect::<Vec<_>>()\"\n\n---\n[\n    \"\\\"foo\\\"\",\n    \"+\",\n    \"12.4\",\n]\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-2.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"2 + 5\\\"))\"\n\n---\nOk(\n    (\"2\" + \"5\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-20.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"\\\\\\\"foo\\\\\\\" + 12.4\\\"))\"\n\n---\nOk(\n    BinOp(\n        Literal(\n            \"foo\",\n        ),\n        Plus,\n        Literal(\n            \"12.4\",\n        ),\n    ),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-21.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"hi[\\\\\\\"ho\\\\\\\"]\\\").filter_map(|x|\\n                                        x.ok()).map(|(_, x, _)|\\n                                                        match x {\\n                                                            Token::Ident(x) |\\n                                                            Token::NumLit(x) |\\n                                                            Token::StrLit(x)\\n                                                            =>\\n                                                            format!(\\\"{}\\\", x),\\n                                                            x =>\\n                                                            format!(\\\"{}\\\", x),\\n                                                        }).collect::<Vec<_>>()\"\n\n---\n[\n    \"hi\",\n    \"LBrack\",\n    \"\\\"ho\\\"\",\n    \"RBrack\",\n]\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-22.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"hi[\\\\\\\"ho\\\\\\\"]\\\"))\"\n\n---\nOk(\n    JsonAccess(\n        VarRef(\n            \"hi\",\n        ),\n        Literal(\n            \"ho\",\n        ),\n    ),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-23.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"foo.bar.baz\\\").filter_map(|x|\\n                                         x.ok()).map(|(_, x, _)|\\n                                                         match x {\\n                                                             Token::Ident(x) |\\n                                                             Token::NumLit(x)\\n                                                             |\\n                                                             Token::StrLit(x)\\n                                                             =>\\n                                                             format!(\\\"{}\\\", x),\\n                                                             x =>\\n                                                             format!(\\\"{}\\\", x),\\n                                                         }).collect::<Vec<_>>()\"\n\n---\n[\n    \"foo\",\n    \"Dot\",\n    \"bar\",\n    \"Dot\",\n    \"baz\",\n]\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-24.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo.bar.baz\\\"))\"\n\n---\nOk(\n    JsonAccess(\n        JsonAccess(\n            VarRef(\n                \"foo\",\n            ),\n            Literal(\n                \"bar\",\n            ),\n        ),\n        Literal(\n            \"baz\",\n        ),\n    ),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-25.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"Lexer::new(\\\"foo.bar[2 + 2] * asdf[foo.bar]\\\").filter_map(|x|\\n                                                            x.ok()).map(|(_,\\n                                                                          x,\\n                                                                          _)|\\n                                                                            match x\\n                                                                                {\\n                                                                                Token::Ident(x)\\n                                                                                |\\n                                                                                Token::NumLit(x)\\n                                                                                |\\n                                                                                Token::StrLit(x)\\n                                                                                =>\\n                                                                                format!(\\\"{}\\\",\\n                                                                                        x),\\n                                                                                x\\n                                                                                =>\\n                                                                                format!(\\\"{}\\\",\\n                                                                                        x),\\n                                                                            }).collect::<Vec<_>>()\"\n\n---\n[\n    \"foo\",\n    \"Dot\",\n    \"bar\",\n    \"LBrack\",\n    \"2\",\n    \"+\",\n    \"2\",\n    \"RBrack\",\n    \"*\",\n    \"asdf\",\n    \"LBrack\",\n    \"foo\",\n    \"Dot\",\n    \"bar\",\n    \"RBrack\",\n]\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-26.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo.bar[2 + 2] * asdf[foo.bar]\\\"))\"\n\n---\nOk(\n    BinOp(\n        JsonAccess(\n            JsonAccess(\n                VarRef(\n                    \"foo\",\n                ),\n                Literal(\n                    \"bar\",\n                ),\n            ),\n            BinOp(\n                Literal(\n                    \"2\",\n                ),\n                Plus,\n                Literal(\n                    \"2\",\n                ),\n            ),\n        ),\n        Times,\n        JsonAccess(\n            VarRef(\n                \"asdf\",\n            ),\n            JsonAccess(\n                VarRef(\n                    \"foo\",\n                ),\n                Literal(\n                    \"bar\",\n                ),\n            ),\n        ),\n    ),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-3.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"2 * 5 + 1 * 1 + 3\\\"))\"\n\n---\nOk(\n    (((\"2\" * \"5\") + (\"1\" * \"1\")) + \"3\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-4.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"(1 + 2) * 2\\\"))\"\n\n---\nOk(\n    ((\"1\" + \"2\") * \"2\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-5.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"1 + true ? 2 : 5\\\"))\"\n\n---\nOk(\n    (if (\"1\" + \"true\") then \"2\" else \"5\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-6.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"1 + true ? 2 : 5 + 2\\\"))\"\n\n---\nOk(\n    (if (\"1\" + \"true\") then \"2\" else (\"5\" + \"2\")),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-7.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"1 + (true ? 2 : 5) + 2\\\"))\"\n\n---\nOk(\n    ((\"1\" + (if \"true\" then \"2\" else \"5\")) + \"2\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-8.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"foo(1, 2)\\\"))\"\n\n---\nOk(\n    foo(\"1\", \"2\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test-9.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"! false || ! true\\\"))\"\n\n---\nOk(\n    (!\"false\" || !\"true\"),\n)\n"
  },
  {
    "path": "crates/simplexpr/src/snapshots/simplexpr__tests__test.snap",
    "content": "---\nsource: src/lib.rs\nexpression: \"p.parse(Lexer::new(\\\"1\\\"))\"\n\n---\nOk(\n    \"1\",\n)\n"
  },
  {
    "path": "crates/yuck/Cargo.toml",
    "content": "[package]\nname = \"yuck\"\nversion = \"0.1.0\"\nauthors = [\"elkowar <5300871+elkowar@users.noreply.github.com>\"]\nedition = \"2021\"\nlicense = \"MIT\"\ndescription = \"Implementation of the yuck language, the declarative UI description language used by eww\"\nrepository = \"https://github.com/elkowar/eww\"\nhomepage = \"https://github.com/elkowar/eww\"\n\nbuild = \"build.rs\"\n\n[dependencies]\nsimplexpr.workspace = true\neww_shared_util.workspace = true\n\nanyhow.workspace = true\ncodespan-reporting.workspace = true\nderive_more.workspace = true\nitertools.workspace = true\nlalrpop-util.workspace = true\nmaplit.workspace = true\nonce_cell.workspace = true\nregex.workspace = true\nserde = {workspace = true, features = [\"derive\"]}\nsmart-default.workspace = true\nstatic_assertions.workspace = true\nstrum = { workspace = true, features = [\"derive\"] }\nthiserror.workspace = true\n\n\n[build-dependencies]\nlalrpop.workspace = true\n\n[dev-dependencies]\ninsta.workspace = true\npretty_assertions.workspace = true\n"
  },
  {
    "path": "crates/yuck/build.rs",
    "content": "extern crate lalrpop;\nfn main() {\n    lalrpop::process_root().unwrap();\n}\n"
  },
  {
    "path": "crates/yuck/src/ast_error.rs",
    "content": "use eww_shared_util::{AttrName, Span};\n\nuse crate::{format_diagnostic::ToDiagnostic, gen_diagnostic, parser::ast::AstType};\n\n/// Error type representing errors that occur when trying to access parts of the AST specifically\n#[derive(Debug, thiserror::Error)]\npub enum AstError {\n    #[error(\"Did not expect any further elements here. Make sure your format is correct\")]\n    NoMoreElementsExpected(Span),\n\n    #[error(\"Expected more elements\")]\n    TooFewElements(Span),\n\n    #[error(\"Wrong type of expression: Expected {1} but got {2}\")]\n    WrongExprType(Span, AstType, AstType),\n\n    #[error(\"'{0}' is missing a value\")]\n    DanglingKeyword(Span, AttrName),\n\n    /// May occur when we need to evaluate an expression when expecting a literal value\n    #[error(transparent)]\n    EvalError(#[from] simplexpr::eval::EvalError),\n}\n\nimpl ToDiagnostic for AstError {\n    fn to_diagnostic(&self) -> codespan_reporting::diagnostic::Diagnostic<usize> {\n        match self {\n            AstError::NoMoreElementsExpected(span) => gen_diagnostic!(self, span),\n            AstError::TooFewElements(span) => gen_diagnostic! {\n                msg = self,\n                label = span => \"Expected another element here\"\n            },\n            AstError::WrongExprType(span, expected, actual) => gen_diagnostic! {\n                msg = \"Wrong type of expression\",\n                label = span => format!(\"Expected a `{expected}` here\"),\n                note = format!(\"Expected: {expected}\\n     Got: {actual}\"),\n            },\n            AstError::DanglingKeyword(span, kw) => gen_diagnostic! {\n                msg = format!(\"{kw} is missing a value\"),\n                label = span => \"No value provided for this\",\n            },\n            AstError::EvalError(e) => e.to_diagnostic(),\n        }\n    }\n}\n\nimpl eww_shared_util::Spanned for AstError {\n    fn span(&self) -> Span {\n        match self {\n            AstError::NoMoreElementsExpected(span) => *span,\n            AstError::TooFewElements(span) => *span,\n            AstError::WrongExprType(span, ..) => *span,\n            AstError::DanglingKeyword(span, _) => *span,\n            AstError::EvalError(e) => e.span(),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/config/attributes.rs",
    "content": "use std::collections::HashMap;\n\nuse simplexpr::{dynval::FromDynVal, eval::EvalError, SimplExpr};\n\nuse crate::{\n    error::{DiagError, DiagResult},\n    parser::{ast::Ast, from_ast::FromAst},\n};\nuse eww_shared_util::{AttrName, Span, Spanned};\n\n#[derive(Debug, thiserror::Error)]\npub enum AttrError {\n    #[error(\"Missing required attribute {1}\")]\n    MissingRequiredAttr(Span, AttrName),\n\n    #[error(\"{1}\")]\n    EvaluationError(Span, EvalError),\n\n    #[error(\"{1}\")]\n    Other(Span, Box<dyn std::error::Error + Sync + Send + 'static>),\n}\n\nimpl Spanned for AttrError {\n    fn span(&self) -> Span {\n        match self {\n            AttrError::MissingRequiredAttr(span, _) => *span,\n            AttrError::EvaluationError(span, _) => *span,\n            AttrError::Other(span, _) => *span,\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]\npub struct AttrEntry {\n    pub key_span: Span,\n    pub value: Ast,\n}\n\nimpl AttrEntry {\n    pub fn new(key_span: Span, value: Ast) -> AttrEntry {\n        AttrEntry { key_span, value }\n    }\n}\n\n// TODO maybe make this generic over the contained content\n#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)]\npub struct Attributes {\n    pub span: Span,\n    pub attrs: HashMap<AttrName, AttrEntry>,\n}\n\nimpl Attributes {\n    pub fn new(span: Span, attrs: HashMap<AttrName, AttrEntry>) -> Self {\n        Attributes { span, attrs }\n    }\n\n    pub fn ast_required<T: FromAst>(&mut self, key: &str) -> Result<T, DiagError> {\n        let key = AttrName(key.to_string());\n        match self.attrs.remove(&key) {\n            Some(AttrEntry { value, .. }) => T::from_ast(value),\n            None => Err(AttrError::MissingRequiredAttr(self.span, key.clone()).into()),\n        }\n    }\n\n    pub fn ast_optional<T: FromAst>(&mut self, key: &str) -> Result<Option<T>, DiagError> {\n        match self.attrs.remove(&AttrName(key.to_string())) {\n            Some(AttrEntry { value, .. }) => T::from_ast(value).map(Some),\n            None => Ok(None),\n        }\n    }\n\n    /// Retrieve a required attribute from the set which _must not_ reference any variables,\n    /// and is thus known to be static.\n    pub fn primitive_required<T, E>(&mut self, key: &str) -> Result<T, DiagError>\n    where\n        E: std::error::Error + 'static + Sync + Send,\n        T: FromDynVal<Err = E>,\n    {\n        let ast: SimplExpr = self.ast_required(key)?;\n        Ok(ast\n            .eval_no_vars()\n            .map_err(|err| AttrError::EvaluationError(ast.span(), err))?\n            .read_as()\n            .map_err(|e| AttrError::Other(ast.span(), Box::new(e)))?)\n    }\n\n    /// Retrieve an optional attribute from the set which _must not_ reference any variables,\n    /// and is thus known to be static.\n    pub fn primitive_optional<T, E>(&mut self, key: &str) -> Result<Option<T>, DiagError>\n    where\n        E: std::error::Error + 'static + Sync + Send,\n        T: FromDynVal<Err = E>,\n    {\n        let ast: SimplExpr = match self.ast_optional(key)? {\n            Some(ast) => ast,\n            None => return Ok(None),\n        };\n        Ok(Some(\n            ast.eval_no_vars()\n                .map_err(|err| AttrError::EvaluationError(ast.span(), err))?\n                .read_as()\n                .map_err(|e| AttrError::Other(ast.span(), Box::new(e)))?,\n        ))\n    }\n\n    /// Consumes the attributes to return a list of unused attributes which may be used to emit a warning.\n    /// TODO actually use this and emit warnings\n    pub fn get_unused(self) -> impl Iterator<Item = (Span, AttrName)> {\n        self.attrs.into_iter().map(|(k, v)| (v.key_span.to(v.value.span()), k))\n    }\n}\n\n/// Specification of an argument to a widget or window\n#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]\npub struct AttrSpec {\n    pub name: AttrName,\n    pub optional: bool,\n    pub span: Span,\n}\n\nimpl FromAst for AttrSpec {\n    fn from_ast(e: Ast) -> DiagResult<Self> {\n        let span = e.span();\n        let symbol = e.as_symbol()?;\n        let (name, optional) = if let Some(name) = symbol.strip_prefix('?') { (name.to_string(), true) } else { (symbol, false) };\n        Ok(Self { name: AttrName(name), optional, span })\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/config/backend_window_options.rs",
    "content": "use std::{collections::HashMap, str::FromStr};\n\nuse anyhow::Result;\nuse simplexpr::{\n    dynval::{DynVal, FromDynVal},\n    eval::EvalError,\n    SimplExpr,\n};\n\nuse super::{attributes::Attributes, window_definition::EnumParseError};\nuse crate::{\n    enum_parse,\n    error::DiagResult,\n    parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent},\n    value::{coords, NumWithUnit},\n};\nuse eww_shared_util::{Span, VarName};\nuse simplexpr::dynval::ConversionError;\n\nuse crate::error::{DiagError, DiagResultExt};\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n    #[error(transparent)]\n    EnumParseError(#[from] EnumParseError),\n    #[error(transparent)]\n    CoordsError(#[from] coords::Error),\n    #[error(transparent)]\n    EvalError(#[from] EvalError),\n    #[error(transparent)]\n    ConversionError(#[from] ConversionError),\n}\n\n/// Backend-specific options of a window\n/// Unevaluated form of [`BackendWindowOptions`]\n#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]\npub struct BackendWindowOptionsDef {\n    pub wayland: WlBackendWindowOptionsDef,\n    pub x11: X11BackendWindowOptionsDef,\n}\n\nimpl BackendWindowOptionsDef {\n    pub fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<BackendWindowOptions, Error> {\n        Ok(BackendWindowOptions { wayland: self.wayland.eval(local_variables)?, x11: self.x11.eval(local_variables)? })\n    }\n\n    pub fn from_attrs(attrs: &mut Attributes) -> DiagResult<Self> {\n        let struts = attrs.ast_optional(\"reserve\")?;\n        let window_type = attrs.ast_optional(\"windowtype\")?;\n        let focusable = attrs.ast_optional(\"focusable\")?;\n        let x11 = X11BackendWindowOptionsDef {\n            sticky: attrs.ast_optional(\"sticky\")?,\n            struts,\n            window_type,\n            wm_ignore: attrs.ast_optional(\"wm-ignore\")?,\n        };\n        let wayland = WlBackendWindowOptionsDef {\n            exclusive: attrs.ast_optional(\"exclusive\")?,\n            focusable,\n            namespace: attrs.ast_optional(\"namespace\")?,\n        };\n\n        Ok(Self { wayland, x11 })\n    }\n}\n\n/// Backend-specific options of a window that are backend\n#[derive(Debug, Clone, serde::Serialize, PartialEq)]\npub struct BackendWindowOptions {\n    pub x11: X11BackendWindowOptions,\n    pub wayland: WlBackendWindowOptions,\n}\n\n#[derive(Debug, Clone, PartialEq, serde::Serialize)]\npub struct X11BackendWindowOptions {\n    pub wm_ignore: bool,\n    pub sticky: bool,\n    pub window_type: X11WindowType,\n    pub struts: X11StrutDefinition,\n}\n\n/// Unevaluated form of [`X11BackendWindowOptions`]\n#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]\npub struct X11BackendWindowOptionsDef {\n    pub sticky: Option<SimplExpr>,\n    pub struts: Option<X11StrutDefinitionExpr>,\n    pub window_type: Option<SimplExpr>,\n    pub wm_ignore: Option<SimplExpr>,\n}\n\nimpl X11BackendWindowOptionsDef {\n    fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<X11BackendWindowOptions, Error> {\n        Ok(X11BackendWindowOptions {\n            sticky: eval_opt_expr_as_bool(&self.sticky, true, local_variables)?,\n            struts: match &self.struts {\n                Some(expr) => expr.eval(local_variables)?,\n                None => X11StrutDefinition::default(),\n            },\n            window_type: match &self.window_type {\n                Some(expr) => X11WindowType::from_dynval(&expr.eval(local_variables)?)?,\n                None => X11WindowType::default(),\n            },\n            wm_ignore: eval_opt_expr_as_bool(\n                &self.wm_ignore,\n                self.window_type.is_none() && self.struts.is_none(),\n                local_variables,\n            )?,\n        })\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]\npub struct WlBackendWindowOptions {\n    pub exclusive: bool,\n    pub focusable: WlWindowFocusable,\n    pub namespace: Option<String>,\n}\n\n/// Unevaluated form of [`WlBackendWindowOptions`]\n#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]\npub struct WlBackendWindowOptionsDef {\n    pub exclusive: Option<SimplExpr>,\n    pub focusable: Option<SimplExpr>,\n    pub namespace: Option<SimplExpr>,\n}\n\nimpl WlBackendWindowOptionsDef {\n    fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<WlBackendWindowOptions, Error> {\n        Ok(WlBackendWindowOptions {\n            exclusive: eval_opt_expr_as_bool(&self.exclusive, false, local_variables)?,\n            focusable: match &self.focusable {\n                Some(expr) => WlWindowFocusable::from_dynval(&expr.eval(local_variables)?)?,\n                None => WlWindowFocusable::default(),\n            },\n            namespace: match &self.namespace {\n                Some(expr) => Some(expr.eval(local_variables)?.as_string()?),\n                None => None,\n            },\n        })\n    }\n}\n\nfn eval_opt_expr_as_bool(\n    opt_expr: &Option<SimplExpr>,\n    default: bool,\n    local_variables: &HashMap<VarName, DynVal>,\n) -> Result<bool, EvalError> {\n    Ok(match opt_expr {\n        Some(expr) => expr.eval(local_variables)?.as_bool()?,\n        None => default,\n    })\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)]\npub enum WlWindowFocusable {\n    #[default]\n    None,\n    Exclusive,\n    OnDemand,\n}\nimpl FromStr for WlWindowFocusable {\n    type Err = EnumParseError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        enum_parse! { \"focusable\", s,\n            \"none\" => Self::None,\n            \"exclusive\" => Self::Exclusive,\n            \"ondemand\" => Self::OnDemand,\n            // legacy support\n            \"true\" => Self::Exclusive,\n            \"false\" => Self::None,\n        }\n    }\n}\n\n/// Window type of an x11 window\n#[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)]\npub enum X11WindowType {\n    #[default]\n    Dock,\n    Dialog,\n    Toolbar,\n    Normal,\n    Utility,\n    Desktop,\n    Notification,\n}\nimpl FromStr for X11WindowType {\n    type Err = EnumParseError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        enum_parse! { \"window type\", s,\n            \"dock\" => Self::Dock,\n            \"toolbar\" => Self::Toolbar,\n            \"dialog\" => Self::Dialog,\n            \"normal\" => Self::Normal,\n            \"utility\" => Self::Utility,\n            \"desktop\" => Self::Desktop,\n            \"notification\" => Self::Notification,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, serde::Serialize)]\npub enum Side {\n    #[default]\n    Top,\n    Left,\n    Right,\n    Bottom,\n}\n\nimpl FromStr for Side {\n    type Err = EnumParseError;\n\n    fn from_str(s: &str) -> Result<Side, Self::Err> {\n        enum_parse! { \"side\", s,\n            \"l\" | \"left\" => Side::Left,\n            \"r\" | \"right\" => Side::Right,\n            \"t\" | \"top\" => Side::Top,\n            \"b\" | \"bottom\" => Side::Bottom,\n        }\n    }\n}\n\n/// Unevaluated form of [`X11StrutDefinition`]\n#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]\npub struct X11StrutDefinitionExpr {\n    pub side: Option<SimplExpr>,\n    pub distance: SimplExpr,\n}\n\nimpl X11StrutDefinitionExpr {\n    fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<X11StrutDefinition, Error> {\n        Ok(X11StrutDefinition {\n            side: match &self.side {\n                Some(expr) => Side::from_dynval(&expr.eval(local_variables)?)?,\n                None => Side::default(),\n            },\n            distance: NumWithUnit::from_dynval(&self.distance.eval(local_variables)?)?,\n        })\n    }\n}\n\nimpl FromAstElementContent for X11StrutDefinitionExpr {\n    const ELEMENT_NAME: &'static str = \"struts\";\n\n    fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {\n        let mut attrs = iter.expect_key_values()?;\n        iter.expect_done().map_err(DiagError::from).note(\"Check if you are missing a colon in front of a key\")?;\n        Ok(X11StrutDefinitionExpr { side: attrs.ast_optional(\"side\")?, distance: attrs.ast_required(\"distance\")? })\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize)]\npub struct X11StrutDefinition {\n    pub side: Side,\n    pub distance: NumWithUnit,\n}\n"
  },
  {
    "path": "crates/yuck/src/config/file_provider.rs",
    "content": "use eww_shared_util::Span;\n\nuse crate::{error::DiagError, parser::ast::Ast};\n\n#[derive(thiserror::Error, Debug)]\npub enum FilesError {\n    #[error(transparent)]\n    IoError(#[from] std::io::Error),\n\n    #[error(transparent)]\n    DiagError(#[from] DiagError),\n}\n\npub trait YuckFileProvider {\n    fn load_yuck_file(&mut self, path: std::path::PathBuf) -> Result<(Span, Vec<Ast>), FilesError>;\n    fn load_yuck_str(&mut self, name: String, content: String) -> Result<(Span, Vec<Ast>), DiagError>;\n    fn unload(&mut self, id: usize);\n}\n"
  },
  {
    "path": "crates/yuck/src/config/mod.rs",
    "content": "pub mod attributes;\npub mod backend_window_options;\npub mod file_provider;\npub mod monitor;\npub mod script_var_definition;\npub mod toplevel;\npub mod validate;\npub mod var_definition;\npub mod widget_definition;\npub mod widget_use;\npub mod window_definition;\npub mod window_geometry;\n\npub use toplevel::*;\n"
  },
  {
    "path": "crates/yuck/src/config/monitor.rs",
    "content": "use std::{\n    convert::Infallible,\n    fmt,\n    str::{self, FromStr},\n};\n\nuse serde::{Deserialize, Serialize};\nuse simplexpr::dynval::{ConversionError, DynVal};\n\n/// The type of the identifier used to select a monitor\n#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub enum MonitorIdentifier {\n    List(Vec<MonitorIdentifier>),\n    Numeric(i32),\n    Name(String),\n    Primary,\n}\n\nimpl MonitorIdentifier {\n    pub fn from_dynval(val: &DynVal) -> Result<Self, ConversionError> {\n        match val.as_json_array() {\n            Ok(arr) => Ok(MonitorIdentifier::List(\n                arr.iter().map(|x| MonitorIdentifier::from_dynval(&x.into())).collect::<Result<_, _>>()?,\n            )),\n            Err(_) => match val.as_i32() {\n                Ok(x) => Ok(MonitorIdentifier::Numeric(x)),\n                Err(_) => Ok(MonitorIdentifier::from_str(&val.as_string().unwrap()).unwrap()),\n            },\n        }\n    }\n\n    pub fn is_numeric(&self) -> bool {\n        matches!(self, Self::Numeric(_))\n    }\n}\n\nimpl From<&MonitorIdentifier> for DynVal {\n    fn from(val: &MonitorIdentifier) -> Self {\n        match val {\n            MonitorIdentifier::List(l) => l.iter().map(|x| x.into()).collect::<Vec<_>>().into(),\n            MonitorIdentifier::Numeric(n) => DynVal::from(*n),\n            MonitorIdentifier::Name(n) => DynVal::from(n.clone()),\n            MonitorIdentifier::Primary => DynVal::from(\"<primary>\"),\n        }\n    }\n}\n\nimpl fmt::Display for MonitorIdentifier {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Self::List(l) => write!(f, \"[{}]\", l.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(\" \")),\n            Self::Numeric(n) => write!(f, \"{}\", n),\n            Self::Name(n) => write!(f, \"{}\", n),\n            Self::Primary => write!(f, \"<primary>\"),\n        }\n    }\n}\n\nimpl str::FromStr for MonitorIdentifier {\n    type Err = Infallible;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s.parse::<i32>() {\n            Ok(n) => Ok(Self::Numeric(n)),\n            Err(_) => {\n                if &s.to_lowercase() == \"<primary>\" {\n                    Ok(Self::Primary)\n                } else {\n                    Ok(Self::Name(s.to_owned()))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/config/script_var_definition.rs",
    "content": "use simplexpr::{dynval::DynVal, SimplExpr};\n\nuse crate::{\n    error::{DiagError, DiagResult, DiagResultExt},\n    format_diagnostic::ToDiagnostic,\n    parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent},\n};\nuse eww_shared_util::{Span, VarName};\n\n#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]\npub enum ScriptVarDefinition {\n    Poll(PollScriptVar),\n    Listen(ListenScriptVar),\n}\n\nimpl ScriptVarDefinition {\n    pub fn name_span(&self) -> Span {\n        match self {\n            ScriptVarDefinition::Poll(x) => x.name_span,\n            ScriptVarDefinition::Listen(x) => x.name_span,\n        }\n    }\n\n    pub fn name(&self) -> &VarName {\n        match self {\n            ScriptVarDefinition::Poll(x) => &x.name,\n            ScriptVarDefinition::Listen(x) => &x.name,\n        }\n    }\n\n    pub fn command_span(&self) -> Option<Span> {\n        match self {\n            ScriptVarDefinition::Poll(x) => match x.command {\n                VarSource::Shell(span, ..) => Some(span),\n                VarSource::Function(_) => None,\n            },\n            ScriptVarDefinition::Listen(x) => Some(x.command_span),\n        }\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]\npub enum VarSource {\n    // TODO allow for other executors? (python, etc)\n    Shell(Span, String),\n    #[serde(skip)]\n    Function(fn() -> Result<DynVal, Box<dyn std::error::Error + Sync + Send + 'static>>),\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]\npub struct PollScriptVar {\n    pub name: VarName,\n    pub run_while_expr: SimplExpr,\n    pub command: VarSource,\n    pub initial_value: Option<DynVal>,\n    pub interval: std::time::Duration,\n    pub name_span: Span,\n}\n\nimpl FromAstElementContent for PollScriptVar {\n    const ELEMENT_NAME: &'static str = \"defpoll\";\n\n    fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {\n        let result: DiagResult<_> = (move || {\n            let (name_span, name) = iter.expect_symbol()?;\n            let mut attrs = iter.expect_key_values()?;\n            let initial_value = Some(attrs.primitive_optional(\"initial\")?.unwrap_or_else(|| DynVal::from_string(String::new())));\n            let interval =\n                attrs.primitive_required::<DynVal, _>(\"interval\")?.as_duration().map_err(|e| DiagError(e.to_diagnostic()))?;\n            let (script_span, script) = iter.expect_literal()?;\n\n            let run_while_expr =\n                attrs.ast_optional::<SimplExpr>(\"run-while\")?.unwrap_or_else(|| SimplExpr::Literal(DynVal::from(true)));\n\n            iter.expect_done()?;\n            Ok(Self {\n                name_span,\n                name: VarName(name),\n                run_while_expr,\n                command: VarSource::Shell(script_span, script.to_string()),\n                initial_value,\n                interval,\n            })\n        })();\n        result.note(r#\"Expected format: `(defpoll name :interval \"10s\" \"echo 'a shell script'\")`\"#)\n    }\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]\npub struct ListenScriptVar {\n    pub name: VarName,\n    pub command: String,\n    pub initial_value: DynVal,\n    pub command_span: Span,\n    pub name_span: Span,\n}\nimpl FromAstElementContent for ListenScriptVar {\n    const ELEMENT_NAME: &'static str = \"deflisten\";\n\n    fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {\n        let result: DiagResult<_> = (move || {\n            let (name_span, name) = iter.expect_symbol()?;\n            let mut attrs = iter.expect_key_values()?;\n            let initial_value = attrs.primitive_optional(\"initial\")?.unwrap_or_else(|| DynVal::from_string(String::new()));\n            let (command_span, script) = iter.expect_literal()?;\n            iter.expect_done()?;\n            Ok(Self { name_span, name: VarName(name), command: script.to_string(), initial_value, command_span })\n        })();\n        result.note(r#\"Expected format: `(deflisten name :initial \"0\" \"tail -f /tmp/example\")`\"#)\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/config/snapshots/eww_config__config__test__config.snap",
    "content": "---\nsource: src/config/test.rs\nexpression: config.unwrap()\n\n---\nConfig(\n  widget_definitions: {\n    \"bar\": WidgetDefinition(\n      name: \"bar\",\n      expected_args: [\n        AttrName(\"arg\"),\n        AttrName(\"arg2\"),\n      ],\n      widget: WidgetUse(\n        name: \"text\",\n        attrs: Attributes(\n          span: Span(99, 104, 0),\n          attrs: {\n            AttrName(\"text\"): AttrEntry(\n              key_span: Span(99, 104, 0),\n              value: Literal(Span(99, 104, 0), DynVal(\"bla\", None)),\n            ),\n          },\n        ),\n        children: [],\n        span: Span(99, 104, 0),\n      ),\n      span: Span(61, 105, 0),\n      args_span: Span(76, 86, 0),\n    ),\n    \"foo\": WidgetDefinition(\n      name: \"foo\",\n      expected_args: [\n        AttrName(\"arg\"),\n      ],\n      widget: WidgetUse(\n        name: \"text\",\n        attrs: Attributes(\n          span: Span(44, 51, 0),\n          attrs: {\n            AttrName(\"text\"): AttrEntry(\n              key_span: Span(44, 51, 0),\n              value: Literal(Span(44, 51, 0), DynVal(\"heyho\", None)),\n            ),\n          },\n        ),\n        children: [],\n        span: Span(44, 51, 0),\n      ),\n      span: Span(11, 52, 0),\n      args_span: Span(26, 31, 0),\n    ),\n  },\n  window_definitions: {\n    \"some-window\": WindowDefinition(\n      name: \"some-window\",\n      geometry: Some(WindowGeometry(\n        anchor_point: AnchorPoint(\n          x: START,\n          y: START,\n        ),\n        offset: Coords(\n          x: Pixels(0),\n          y: Pixels(0),\n        ),\n        size: Coords(\n          x: Percent(12),\n          y: Pixels(20),\n        ),\n      )),\n      stacking: Foreground,\n      monitor_number: Some(12),\n      widget: WidgetUse(\n        name: \"foo\",\n        attrs: Attributes(\n          span: Span(509, 509, 513),\n          attrs: {\n            AttrName(\"arg\"): AttrEntry(\n              key_span: Span(514, 518, 0),\n              value: Literal(Span(519, 524, 0), DynVal(\"bla\", None)),\n            ),\n          },\n        ),\n        children: [],\n        span: Span(509, 525, 0),\n      ),\n      resizable: true,\n      backend_options: X11WindowOptions(\n        wm_ignore: false,\n        sticky: true,\n        window_type: Dock,\n        struts: StrutDefinition(\n          side: Left,\n          dist: Pixels(30),\n        ),\n      ),\n    ),\n  },\n  var_definitions: {\n    VarName(\"some_var\"): VarDefinition(\n      name: VarName(\"some_var\"),\n      initial_value: DynVal(\"bla\", None),\n      span: Span(114, 137, 0),\n    ),\n  },\n  script_vars: {\n    VarName(\"stuff\"): Tail(TailScriptVar(\n      name: VarName(\"stuff\"),\n      command: \"tail -f stuff\",\n    )),\n  },\n)\n"
  },
  {
    "path": "crates/yuck/src/config/snapshots/yuck__config__test__config.snap",
    "content": "---\nsource: crates/yuck/src/config/test.rs\nexpression: config.unwrap()\n\n---\nConfig(\n  widget_definitions: {\n    \"bar\": WidgetDefinition(\n      name: \"bar\",\n      expected_args: [\n        AttrSpec(\n          name: AttrName(\"arg\"),\n          optional: false,\n          span: Span(25, 28, 0),\n        ),\n        AttrSpec(\n          name: AttrName(\"arg2\"),\n          optional: false,\n          span: Span(29, 33, 0),\n        ),\n      ],\n      widget: Basic(BasicWidgetUse(\n        name: \"foo\",\n        attrs: Attributes(\n          span: Span(51, 61, 0),\n          attrs: {\n            AttrName(\"arg\"): AttrEntry(\n              key_span: Span(52, 56, 0),\n              value: SimplExpr(Span(57, 61, 0), Literal(DynVal(\"hi\", Span(57, 61, 0)))),\n            ),\n          },\n        ),\n        children: [],\n        span: Span(47, 62, 0),\n        name_span: Span(48, 51, 0),\n      )),\n      span: Span(9, 63, 0),\n      args_span: Span(24, 34, 0),\n    ),\n  },\n  window_definitions: {\n    \"some-window\": WindowDefinition(\n      name: \"some-window\",\n      expected_args: [],\n      args_span: Span(18446744073709551615, 18446744073709551615, 18446744073709551615),\n      geometry: Some(WindowGeometry(\n        anchor_point: AnchorPoint(\n          x: START,\n          y: START,\n        ),\n        offset: Coords(\n          x: Pixels(0),\n          y: Pixels(0),\n        ),\n        size: Coords(\n          x: Percent(12),\n          y: Pixels(20),\n        ),\n      )),\n      stacking: Foreground,\n      monitor_number: Some(Literal(DynVal(\"12\", Span(278, 280, 0)))),\n      widget: Basic(BasicWidgetUse(\n        name: \"bar\",\n        attrs: Attributes(\n          span: Span(467, 478, 0),\n          attrs: {\n            AttrName(\"arg\"): AttrEntry(\n              key_span: Span(468, 472, 0),\n              value: SimplExpr(Span(473, 478, 0), Literal(DynVal(\"bla\", Span(473, 478, 0)))),\n            ),\n          },\n        ),\n        children: [],\n        span: Span(463, 479, 0),\n        name_span: Span(464, 467, 0),\n      )),\n      resizable: true,\n      backend_options: BackendWindowOptions(\n        wm_ignore: false,\n        sticky: true,\n        window_type: Dock,\n        struts: StrutDefinition(\n          side: Left,\n          dist: Pixels(30),\n        ),\n      ),\n    ),\n    \"some-window-with-args\": WindowDefinition(\n      name: \"some-window-with-args\",\n      expected_args: [\n        AttrSpec(\n          name: AttrName(\"arg\"),\n          optional: false,\n          span: Span(523, 526, 0),\n        ),\n        AttrSpec(\n          name: AttrName(\"arg2\"),\n          optional: false,\n          span: Span(527, 531, 0),\n        ),\n      ],\n      args_span: Span(522, 532, 0),\n      geometry: Some(WindowGeometry(\n        anchor_point: AnchorPoint(\n          x: START,\n          y: START,\n        ),\n        offset: Coords(\n          x: Pixels(0),\n          y: Pixels(0),\n        ),\n        size: Coords(\n          x: Percent(12),\n          y: Pixels(20),\n        ),\n      )),\n      stacking: Foreground,\n      monitor_number: Some(Literal(DynVal(\"12\", Span(595, 597, 0)))),\n      widget: Basic(BasicWidgetUse(\n        name: \"bar\",\n        attrs: Attributes(\n          span: Span(784, 795, 0),\n          attrs: {\n            AttrName(\"arg\"): AttrEntry(\n              key_span: Span(785, 789, 0),\n              value: SimplExpr(Span(790, 795, 0), Literal(DynVal(\"bla\", Span(790, 795, 0)))),\n            ),\n          },\n        ),\n        children: [],\n        span: Span(780, 796, 0),\n        name_span: Span(781, 784, 0),\n      )),\n      resizable: true,\n      backend_options: BackendWindowOptions(\n        wm_ignore: false,\n        sticky: true,\n        window_type: Dock,\n        struts: StrutDefinition(\n          side: Left,\n          dist: Pixels(30),\n        ),\n      ),\n    ),\n  },\n  var_definitions: {\n    VarName(\"some_var\"): VarDefinition(\n      name: VarName(\"some_var\"),\n      initial_value: DynVal(\"bla\", Span(89, 94, 0)),\n      span: Span(72, 95, 0),\n    ),\n  },\n  script_vars: {\n    VarName(\"stuff\"): Listen(ListenScriptVar(\n      name: VarName(\"stuff\"),\n      command: \"tail -f stuff\",\n      initial_value: DynVal(\"\", Span(18446744073709551615, 18446744073709551615, 18446744073709551615)),\n      command_span: Span(168, 183, 0),\n      name_span: Span(162, 167, 0),\n    )),\n  },\n)\n"
  },
  {
    "path": "crates/yuck/src/config/toplevel.rs",
    "content": "use std::{\n    collections::HashMap,\n    path::{Path, PathBuf},\n};\n\nuse itertools::Itertools;\n\nuse super::{\n    file_provider::{FilesError, YuckFileProvider},\n    script_var_definition::ScriptVarDefinition,\n    var_definition::VarDefinition,\n    widget_definition::WidgetDefinition,\n    window_definition::WindowDefinition,\n};\nuse crate::{\n    config::script_var_definition::{ListenScriptVar, PollScriptVar},\n    error::{DiagError, DiagResult},\n    gen_diagnostic,\n    parser::{\n        ast::Ast,\n        ast_iterator::AstIterator,\n        from_ast::{FromAst, FromAstElementContent},\n    },\n};\nuse eww_shared_util::{Span, Spanned, VarName};\n\nstatic TOP_LEVEL_DEFINITION_NAMES: &[&str] = &[\n    WidgetDefinition::ELEMENT_NAME,\n    WindowDefinition::ELEMENT_NAME,\n    VarDefinition::ELEMENT_NAME,\n    ListenScriptVar::ELEMENT_NAME,\n    PollScriptVar::ELEMENT_NAME,\n    Include::ELEMENT_NAME,\n];\n\n#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]\npub struct Include {\n    pub path: String,\n    pub path_span: Span,\n}\n\nimpl FromAstElementContent for Include {\n    const ELEMENT_NAME: &'static str = \"include\";\n\n    fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {\n        let (path_span, path) = iter.expect_literal()?;\n        iter.expect_done()?;\n        Ok(Include { path: path.to_string(), path_span })\n    }\n}\n\npub enum TopLevel {\n    Include(Include),\n    VarDefinition(VarDefinition),\n    ScriptVarDefinition(ScriptVarDefinition),\n    WidgetDefinition(WidgetDefinition),\n    WindowDefinition(WindowDefinition),\n}\n\nimpl FromAst for TopLevel {\n    fn from_ast(e: Ast) -> DiagResult<Self> {\n        let span = e.span();\n        let mut iter = e.try_ast_iter()?;\n        let (sym_span, element_name) = iter.expect_symbol()?;\n        Ok(match element_name.as_str() {\n            x if x == Include::ELEMENT_NAME => Self::Include(Include::from_tail(span, iter)?),\n            x if x == WidgetDefinition::ELEMENT_NAME => Self::WidgetDefinition(WidgetDefinition::from_tail(span, iter)?),\n            x if x == VarDefinition::ELEMENT_NAME => Self::VarDefinition(VarDefinition::from_tail(span, iter)?),\n            x if x == PollScriptVar::ELEMENT_NAME => {\n                Self::ScriptVarDefinition(ScriptVarDefinition::Poll(PollScriptVar::from_tail(span, iter)?))\n            }\n            x if x == ListenScriptVar::ELEMENT_NAME => {\n                Self::ScriptVarDefinition(ScriptVarDefinition::Listen(ListenScriptVar::from_tail(span, iter)?))\n            }\n            x if x == WindowDefinition::ELEMENT_NAME => Self::WindowDefinition(WindowDefinition::from_tail(span, iter)?),\n            x => {\n                return Err(DiagError(gen_diagnostic! {\n                    msg = format!(\"Unknown toplevel declaration `{x}`\"),\n                    label = sym_span,\n                    note = format!(\"Must be one of: {}\", TOP_LEVEL_DEFINITION_NAMES.iter().join(\", \")),\n                }))\n            }\n        })\n    }\n}\n\n#[derive(Debug, PartialEq, Clone, serde::Serialize)]\npub struct Config {\n    pub widget_definitions: HashMap<String, WidgetDefinition>,\n    pub window_definitions: HashMap<String, WindowDefinition>,\n    pub var_definitions: HashMap<VarName, VarDefinition>,\n    pub script_vars: HashMap<VarName, ScriptVarDefinition>,\n}\n\nimpl Config {\n    fn append_toplevel(&mut self, files: &mut impl YuckFileProvider, toplevel: TopLevel) -> DiagResult<()> {\n        match toplevel {\n            TopLevel::VarDefinition(x) => {\n                if self.var_definitions.contains_key(&x.name) || self.script_vars.contains_key(&x.name) {\n                    return Err(DiagError(gen_diagnostic! {\n                        msg = format!(\"Variable {} defined twice\", x.name),\n                        label = x.span => \"defined again here\",\n                    }));\n                } else {\n                    self.var_definitions.insert(x.name.clone(), x);\n                }\n            }\n            TopLevel::ScriptVarDefinition(x) => {\n                if self.var_definitions.contains_key(x.name()) || self.script_vars.contains_key(x.name()) {\n                    return Err(DiagError(gen_diagnostic! {\n                        msg = format!(\"Variable {} defined twice\", x.name()),\n                        label = x.name_span() => \"defined again here\",\n                    }));\n                } else {\n                    self.script_vars.insert(x.name().clone(), x);\n                }\n            }\n            TopLevel::WidgetDefinition(x) => {\n                self.widget_definitions.insert(x.name.clone(), x);\n            }\n            TopLevel::WindowDefinition(x) => {\n                self.window_definitions.insert(x.name.clone(), x);\n            }\n            TopLevel::Include(include) => {\n                let (_, toplevels) = files.load_yuck_file(PathBuf::from(&include.path)).map_err(|err| match err {\n                    FilesError::IoError(_) => DiagError(gen_diagnostic! {\n                        msg = format!(\"Included file `{}` not found\", include.path),\n                        label = include.path_span => \"Included here\",\n                    }),\n                    FilesError::DiagError(x) => x,\n                })?;\n                for element in toplevels {\n                    self.append_toplevel(files, TopLevel::from_ast(element)?)?;\n                }\n            }\n        }\n        Ok(())\n    }\n\n    pub fn generate(files: &mut impl YuckFileProvider, elements: Vec<Ast>) -> DiagResult<Self> {\n        let mut config = Self {\n            widget_definitions: HashMap::new(),\n            window_definitions: HashMap::new(),\n            var_definitions: HashMap::new(),\n            script_vars: HashMap::new(),\n        };\n        for element in elements {\n            config.append_toplevel(files, TopLevel::from_ast(element)?)?;\n        }\n        Ok(config)\n    }\n\n    pub fn generate_from_main_file(files: &mut impl YuckFileProvider, path: impl AsRef<Path>) -> DiagResult<Self> {\n        let (_span, top_levels) = files.load_yuck_file(path.as_ref().to_path_buf()).map_err(|err| match err {\n            FilesError::IoError(err) => DiagError(gen_diagnostic!(err)),\n            FilesError::DiagError(x) => x,\n        })?;\n        Self::generate(files, top_levels)\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/config/validate.rs",
    "content": "use std::collections::{HashMap, HashSet};\n\nuse simplexpr::SimplExpr;\n\nuse super::{widget_definition::WidgetDefinition, widget_use::WidgetUse, Config};\nuse eww_shared_util::{AttrName, Span, Spanned, VarName};\n\n#[derive(Debug, thiserror::Error)]\npub enum ValidationError {\n    #[error(\"There is already a builtin widget called `{1}`\")]\n    AccidentalBuiltinOverride(Span, String),\n\n    #[error(\"Missing attribute `{arg_name}` in use of widget `{widget_name}`\")]\n    MissingAttr { widget_name: String, arg_name: AttrName, arg_list_span: Option<Span>, use_span: Span },\n\n    #[error(\"No variable named `{name}` in scope\")]\n    UnknownVariable {\n        span: Span,\n        name: VarName,\n        /// True if the error occurred inside a widget definition, false if it occurred in a window definition\n        in_definition: bool,\n    },\n}\n\nimpl Spanned for ValidationError {\n    fn span(&self) -> Span {\n        match self {\n            ValidationError::MissingAttr { use_span, .. } => *use_span,\n            ValidationError::UnknownVariable { span, .. } => *span,\n            ValidationError::AccidentalBuiltinOverride(span, ..) => *span,\n        }\n    }\n}\n\npub fn validate(config: &Config, additional_globals: Vec<VarName>) -> Result<(), ValidationError> {\n    let var_names: HashSet<VarName> = std::iter::empty()\n        .chain(additional_globals.iter().cloned())\n        .chain(config.script_vars.keys().cloned())\n        .chain(config.var_definitions.keys().cloned())\n        .collect();\n    for window in config.window_definitions.values() {\n        let local_var_names: HashSet<VarName> = std::iter::empty()\n            .chain(var_names.iter().cloned())\n            .chain(window.expected_args.iter().map(|x| VarName::from(x.name.clone())))\n            .collect();\n        validate_variables_in_widget_use(&config.widget_definitions, &local_var_names, &window.widget, false)?;\n    }\n    for def in config.widget_definitions.values() {\n        validate_widget_definition(&config.widget_definitions, &var_names, def)?;\n    }\n    Ok(())\n}\n\npub fn validate_widget_definition(\n    other_defs: &HashMap<String, WidgetDefinition>,\n    globals: &HashSet<VarName>,\n    def: &WidgetDefinition,\n) -> Result<(), ValidationError> {\n    let mut variables_in_scope = globals.clone();\n    for arg in def.expected_args.iter() {\n        variables_in_scope.insert(VarName(arg.name.to_string()));\n    }\n\n    validate_variables_in_widget_use(other_defs, &variables_in_scope, &def.widget, true)\n}\n\npub fn validate_variables_in_widget_use(\n    defs: &HashMap<String, WidgetDefinition>,\n    variables: &HashSet<VarName>,\n    widget: &WidgetUse,\n    is_in_definition: bool,\n) -> Result<(), ValidationError> {\n    if let WidgetUse::Basic(widget) = widget {\n        let matching_definition = defs.get(&widget.name);\n        if let Some(matching_def) = matching_definition {\n            let missing_arg = matching_def\n                .expected_args\n                .iter()\n                .find(|expected| !expected.optional && !widget.attrs.attrs.contains_key(&expected.name));\n            if let Some(missing_arg) = missing_arg {\n                return Err(ValidationError::MissingAttr {\n                    widget_name: widget.name.clone(),\n                    arg_name: missing_arg.name.clone(),\n                    arg_list_span: Some(matching_def.args_span),\n                    use_span: widget.attrs.span,\n                });\n            }\n        }\n        let values = widget.attrs.attrs.values();\n        let unknown_var = values.filter_map(|value| value.value.as_simplexpr().ok()).find_map(|expr: SimplExpr| {\n            expr.var_refs_with_span()\n                .iter()\n                .cloned()\n                .map(|(span, var_ref)| (span, var_ref.clone()))\n                .find(|(_, var_ref)| !variables.contains(var_ref))\n        });\n        if let Some((span, var)) = unknown_var {\n            return Err(ValidationError::UnknownVariable { span, name: var, in_definition: is_in_definition });\n        }\n\n        for child in widget.children.iter() {\n            validate_variables_in_widget_use(defs, variables, child, is_in_definition)?;\n        }\n    } else if let WidgetUse::Loop(widget) = widget {\n        let unknown_var = widget\n            .elements_expr\n            .var_refs_with_span()\n            .iter()\n            .cloned()\n            .map(|(span, var_ref)| (span, var_ref.clone()))\n            .find(|(_, var_ref)| var_ref != &widget.element_name && !variables.contains(var_ref));\n        if let Some((span, var)) = unknown_var {\n            return Err(ValidationError::UnknownVariable { span, name: var, in_definition: is_in_definition });\n        }\n        let mut variables = variables.clone();\n        variables.insert(widget.element_name.clone());\n        validate_variables_in_widget_use(defs, &variables, &widget.body, is_in_definition)?;\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/yuck/src/config/var_definition.rs",
    "content": "use simplexpr::dynval::DynVal;\n\nuse crate::{\n    error::{DiagResult, DiagResultExt},\n    parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent},\n};\nuse eww_shared_util::{Span, VarName};\n\n#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]\npub struct VarDefinition {\n    pub name: VarName,\n    pub initial_value: DynVal,\n    pub span: Span,\n}\n\nimpl FromAstElementContent for VarDefinition {\n    const ELEMENT_NAME: &'static str = \"defvar\";\n\n    fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {\n        let result = (move || {\n            let (_, name) = iter.expect_symbol()?;\n            let (_, initial_value) = iter.expect_literal()?;\n            iter.expect_done()?;\n            Ok(Self { name: VarName(name), initial_value, span })\n        })();\n        result.note(r#\"Expected format: `(defvar name \"initial-value\")`\"#)\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/config/widget_definition.rs",
    "content": "use crate::{\n    error::{DiagError, DiagResult, DiagResultExt},\n    format_diagnostic::ToDiagnostic,\n    gen_diagnostic,\n    parser::{\n        ast::Ast,\n        ast_iterator::AstIterator,\n        from_ast::{FromAst, FromAstElementContent},\n    },\n};\nuse eww_shared_util::{Span, Spanned};\n\nuse super::{attributes::AttrSpec, widget_use::WidgetUse};\n\n#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]\npub struct WidgetDefinition {\n    pub name: String,\n    pub expected_args: Vec<AttrSpec>,\n    pub widget: WidgetUse,\n    pub span: Span,\n    pub args_span: Span,\n}\n\nimpl FromAstElementContent for WidgetDefinition {\n    const ELEMENT_NAME: &'static str = \"defwidget\";\n\n    fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {\n        let (name_span, name) = iter.expect_symbol().map_err(DiagError::from).note(EXPECTED_WIDGET_DEF_FORMAT)?;\n        let (args_span, expected_args) = iter\n            .expect_array()\n            .map_err(|e| {\n                DiagError(match e {\n                    crate::ast_error::AstError::WrongExprType(..) => gen_diagnostic! {\n                        msg = \"Widget definition missing argument list\",\n                        label = name_span.point_span_at_end() => \"Insert the argument list (e.g.: `[]`) here\",\n                        note = \"This list needs to declare all the non-global variables / attributes used in this widget.\"\n                    },\n                    other => other.to_diagnostic(),\n                })\n            })\n            .note(EXPECTED_WIDGET_DEF_FORMAT)?;\n        let expected_args = expected_args.into_iter().map(AttrSpec::from_ast).collect::<DiagResult<_>>()?;\n        let widget = iter.expect_any().map_err(DiagError::from).note(EXPECTED_WIDGET_DEF_FORMAT).and_then(WidgetUse::from_ast)?;\n        iter.expect_done().map_err(|e| {\n            DiagError(gen_diagnostic! {\n                msg = \"Widget definition has more than one child widget\",\n                label = e.span() => \"Found more than one child element here.\",\n                note = \"A widget-definition may only contain one child element.\\n\\\n                        To include multiple elements, wrap these elements in a single container widget such as `box`.\\n\\\n                        This is necessary as eww can't know how you want these elements to be layed out otherwise.\"\n            })\n        })?;\n\n        Ok(Self { name, expected_args, widget, span, args_span })\n    }\n}\n\nstatic EXPECTED_WIDGET_DEF_FORMAT: &str = r#\"Expected format: `(defwidget name [] (contained-widgets))`\"#;\n"
  },
  {
    "path": "crates/yuck/src/config/widget_use.rs",
    "content": "use simplexpr::SimplExpr;\n\nuse crate::{\n    config::attributes::AttrEntry,\n    error::{DiagError, DiagResult, DiagResultExt},\n    gen_diagnostic,\n    parser::{\n        ast::Ast,\n        ast_iterator::AstIterator,\n        from_ast::{FromAst, FromAstElementContent},\n    },\n};\nuse eww_shared_util::{AttrName, Span, Spanned, VarName};\n\nuse super::attributes::Attributes;\n\n#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]\npub enum WidgetUse {\n    Basic(BasicWidgetUse),\n    Loop(LoopWidgetUse),\n    Children(ChildrenWidgetUse),\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]\npub struct LoopWidgetUse {\n    pub element_name: VarName,\n    pub elements_expr: SimplExpr,\n    pub elements_expr_span: Span,\n    pub body: Box<WidgetUse>,\n    pub span: Span,\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]\npub struct ChildrenWidgetUse {\n    pub span: Span,\n    pub nth_expr: Option<SimplExpr>,\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]\npub struct BasicWidgetUse {\n    pub name: String,\n    pub attrs: Attributes,\n    pub children: Vec<WidgetUse>,\n    pub span: Span,\n    pub name_span: Span,\n}\n\nimpl BasicWidgetUse {\n    pub fn children_span(&self) -> Span {\n        if self.children.is_empty() {\n            self.span.point_span_at_end().shifted(-1)\n        } else {\n            self.children.first().unwrap().span().to(self.children.last().unwrap().span())\n        }\n    }\n\n    fn from_iter<I: Iterator<Item = Ast>>(\n        span: Span,\n        name: String,\n        name_span: Span,\n        mut iter: AstIterator<I>,\n    ) -> DiagResult<Self> {\n        let attrs = iter.expect_key_values()?;\n        let children = iter.map(WidgetUse::from_ast).collect::<DiagResult<Vec<_>>>()?;\n        Ok(Self { name, attrs, children, span, name_span })\n    }\n}\n\nimpl FromAstElementContent for LoopWidgetUse {\n    const ELEMENT_NAME: &'static str = \"for\";\n\n    fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {\n        let (_element_name_span, element_name) = iter.expect_symbol()?;\n        let (in_string_span, in_string) = iter.expect_symbol()?;\n        if in_string != \"in\" {\n            return Err(DiagError(gen_diagnostic! {\n                msg = \"Expected 'in' in this position, but got '{in_string}'\",\n                label = in_string_span\n            }));\n        }\n        let (elements_span, elements_expr) = iter.expect_simplexpr()?;\n        let body = iter.expect_any().map_err(DiagError::from).note(\"Expected a loop body\").and_then(WidgetUse::from_ast)?;\n        iter.expect_done()?;\n        Ok(Self {\n            element_name: VarName(element_name),\n            elements_expr,\n            body: Box::new(body),\n            span,\n            elements_expr_span: elements_span,\n        })\n    }\n}\n\nimpl FromAstElementContent for ChildrenWidgetUse {\n    const ELEMENT_NAME: &'static str = \"children\";\n\n    fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {\n        let mut attrs = iter.expect_key_values()?;\n        let nth_expr = attrs.ast_optional(\"nth\")?;\n        iter.expect_done()?;\n        Ok(Self { span, nth_expr })\n    }\n}\n\nimpl FromAst for WidgetUse {\n    fn from_ast(e: Ast) -> DiagResult<Self> {\n        let span = e.span();\n        if let Ok(value) = e.clone().as_simplexpr() {\n            Ok(WidgetUse::Basic(label_from_simplexpr(value, span)))\n        } else {\n            let mut iter = e.try_ast_iter()?;\n            let (name_span, name) = iter.expect_symbol()?;\n            match name.as_ref() {\n                LoopWidgetUse::ELEMENT_NAME => Ok(WidgetUse::Loop(LoopWidgetUse::from_tail(span, iter)?)),\n                ChildrenWidgetUse::ELEMENT_NAME => Ok(WidgetUse::Children(ChildrenWidgetUse::from_tail(span, iter)?)),\n                _ => Ok(WidgetUse::Basic(BasicWidgetUse::from_iter(span, name, name_span, iter)?)),\n            }\n        }\n    }\n}\n\nfn label_from_simplexpr(value: SimplExpr, span: Span) -> BasicWidgetUse {\n    BasicWidgetUse {\n        name: \"label\".to_string(),\n        name_span: span.point_span(),\n        attrs: Attributes::new(\n            span,\n            maplit::hashmap! {\n                AttrName(\"text\".to_string()) => AttrEntry::new(\n                    span,\n                    Ast::SimplExpr(span, value)\n                )\n            },\n        ),\n        children: Vec::new(),\n        span,\n    }\n}\n\nmacro_rules! impl_spanned {\n    ($($super:ident => $name:ident),*) => {\n        $(impl Spanned for $name { fn span(&self) -> Span { self.span } })*\n        impl Spanned for WidgetUse {\n            fn span(&self) -> Span {\n                match self { $(WidgetUse::$super(widget) => widget.span),* }\n            }\n        }\n    }\n}\nimpl_spanned!(Basic => BasicWidgetUse, Loop => LoopWidgetUse, Children => ChildrenWidgetUse);\n"
  },
  {
    "path": "crates/yuck/src/config/window_definition.rs",
    "content": "use std::{collections::HashMap, fmt::Display};\n\nuse crate::{\n    config::monitor::MonitorIdentifier,\n    error::{DiagError, DiagResult},\n    parser::{\n        ast::Ast,\n        ast_iterator::AstIterator,\n        from_ast::{FromAst, FromAstElementContent},\n    },\n};\nuse eww_shared_util::{Span, VarName};\nuse simplexpr::{\n    dynval::{DynVal, FromDynVal},\n    eval::EvalError,\n    SimplExpr,\n};\n\nuse super::{\n    attributes::AttrSpec, backend_window_options::BackendWindowOptionsDef, widget_use::WidgetUse,\n    window_geometry::WindowGeometryDef,\n};\n\n#[derive(Debug, thiserror::Error)]\npub enum WindowStackingConversionError {\n    #[error(transparent)]\n    EvalError(#[from] EvalError),\n    #[error(transparent)]\n    EnumParseError(#[from] EnumParseError),\n}\n\n#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]\npub struct WindowDefinition {\n    pub name: String,\n    pub expected_args: Vec<AttrSpec>,\n    pub args_span: Span,\n    pub geometry: Option<WindowGeometryDef>,\n    pub stacking: Option<SimplExpr>,\n    pub monitor: Option<SimplExpr>,\n    pub widget: WidgetUse,\n    pub resizable: Option<SimplExpr>,\n    pub backend_options: BackendWindowOptionsDef,\n}\n\nimpl WindowDefinition {\n    /// Evaluate the `monitor` field of the window definition\n    pub fn eval_monitor(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<Option<MonitorIdentifier>, EvalError> {\n        Ok(match &self.monitor {\n            Some(monitor_expr) => Some(MonitorIdentifier::from_dynval(&monitor_expr.eval(local_variables)?)?),\n            None => None,\n        })\n    }\n\n    /// Evaluate the `resizable` field of the window definition\n    pub fn eval_resizable(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<bool, EvalError> {\n        Ok(match &self.resizable {\n            Some(expr) => expr.eval(local_variables)?.as_bool()?,\n            None => true,\n        })\n    }\n\n    /// Evaluate the `stacking` field of the window definition\n    pub fn eval_stacking(\n        &self,\n        local_variables: &HashMap<VarName, DynVal>,\n    ) -> Result<WindowStacking, WindowStackingConversionError> {\n        match &self.stacking {\n            Some(stacking_expr) => match stacking_expr.eval(local_variables) {\n                Ok(val) => Ok(WindowStacking::from_dynval(&val)?),\n                Err(err) => Err(WindowStackingConversionError::EvalError(err)),\n            },\n            None => Ok(WindowStacking::Foreground),\n        }\n    }\n}\n\nimpl FromAstElementContent for WindowDefinition {\n    const ELEMENT_NAME: &'static str = \"defwindow\";\n\n    fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {\n        let (_, name) = iter.expect_symbol()?;\n        let (args_span, expected_args) = iter.expect_array().unwrap_or((Span::DUMMY, Vec::new()));\n        let expected_args = expected_args.into_iter().map(AttrSpec::from_ast).collect::<DiagResult<_>>()?;\n        let mut attrs = iter.expect_key_values()?;\n        let monitor = attrs.ast_optional(\"monitor\")?;\n        let resizable = attrs.ast_optional(\"resizable\")?;\n        let stacking = attrs.ast_optional(\"stacking\")?;\n        let geometry = attrs.ast_optional(\"geometry\")?;\n        let backend_options = BackendWindowOptionsDef::from_attrs(&mut attrs)?;\n        let widget = iter.expect_any().map_err(DiagError::from).and_then(WidgetUse::from_ast)?;\n        iter.expect_done()?;\n        Ok(Self { name, expected_args, args_span, monitor, resizable, widget, stacking, geometry, backend_options })\n    }\n}\n\n#[derive(Debug, thiserror::Error)]\npub struct EnumParseError {\n    pub input: String,\n    pub expected: Vec<&'static str>,\n}\nimpl Display for EnumParseError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"Failed to parse `{}`, must be one of {}\", self.input, self.expected.join(\", \"))\n    }\n}\n\n/// Parse a string with a concrete set of options into some data-structure,\n/// and return an [EnumParseError]\n/// ```rs\n/// let input = \"up\";\n/// enum_parse { \"direction\", input,\n///   \"up\" => Direction::Up,\n///   \"down\" => Direction::Down,\n/// }\n/// ```\n#[macro_export]\nmacro_rules! enum_parse {\n    ($name:literal, $input:expr, $($($s:literal)|* => $val:expr),* $(,)?) => {\n        let input = $input.to_lowercase();\n        match input.as_str() {\n            $( $( $s )|* => Ok($val) ),*,\n            _ => Err(EnumParseError {\n                input,\n                expected: vec![$($($s),*),*],\n            })\n        }\n    };\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display, smart_default::SmartDefault, serde::Serialize)]\npub enum WindowStacking {\n    #[default]\n    Foreground,\n    Background,\n    Bottom,\n    Overlay,\n}\n\nimpl std::str::FromStr for WindowStacking {\n    type Err = EnumParseError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        enum_parse! { \"WindowStacking\", s,\n            \"foreground\" | \"fg\" => WindowStacking::Foreground,\n            \"background\" | \"bg\" => WindowStacking::Background,\n            \"bottom\" | \"bt\" => WindowStacking::Bottom,\n            \"overlay\" | \"ov\" => WindowStacking::Overlay,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/config/window_geometry.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::{\n    enum_parse,\n    error::DiagResult,\n    format_diagnostic::ToDiagnostic,\n    parser::{ast::Ast, ast_iterator::AstIterator, from_ast::FromAstElementContent},\n    value::{coords, Coords, NumWithUnit},\n};\n\nuse super::window_definition::EnumParseError;\nuse eww_shared_util::{Span, VarName};\nuse serde::{Deserialize, Serialize};\nuse simplexpr::{\n    dynval::{DynVal, FromDynVal},\n    eval::EvalError,\n    SimplExpr,\n};\n\n#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, strum::Display)]\npub enum AnchorAlignment {\n    #[strum(serialize = \"start\")]\n    #[default]\n    START,\n    #[strum(serialize = \"center\")]\n    CENTER,\n    #[strum(serialize = \"end\")]\n    END,\n}\n\nimpl AnchorAlignment {\n    pub fn from_x_alignment(s: &str) -> Result<AnchorAlignment, EnumParseError> {\n        enum_parse! { \"x-alignment\", s,\n            \"l\" | \"left\" => AnchorAlignment::START,\n            \"c\" | \"center\" => AnchorAlignment::CENTER,\n            \"r\" | \"right\" => AnchorAlignment::END,\n        }\n    }\n\n    pub fn from_y_alignment(s: &str) -> Result<AnchorAlignment, EnumParseError> {\n        enum_parse! { \"y-alignment\", s,\n            \"t\" | \"top\" => AnchorAlignment::START,\n            \"c\" | \"center\" => AnchorAlignment::CENTER,\n            \"b\" | \"bottom\" => AnchorAlignment::END,\n        }\n    }\n\n    pub fn alignment_to_coordinate(&self, size_inner: i32, size_container: i32) -> i32 {\n        match self {\n            AnchorAlignment::START => 0,\n            AnchorAlignment::CENTER => (size_container / 2) - (size_inner / 2),\n            AnchorAlignment::END => size_container - size_inner,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)]\npub struct AnchorPoint {\n    pub x: AnchorAlignment,\n    pub y: AnchorAlignment,\n}\n\nimpl std::fmt::Display for AnchorPoint {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        use AnchorAlignment::*;\n        match (self.x, self.y) {\n            (CENTER, CENTER) => write!(f, \"center\"),\n            (x, y) => write!(\n                f,\n                \"{} {}\",\n                match x {\n                    START => \"left\",\n                    CENTER => \"center\",\n                    END => \"right\",\n                },\n                match y {\n                    START => \"top\",\n                    CENTER => \"center\",\n                    END => \"bottom\",\n                }\n            ),\n        }\n    }\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum AnchorPointParseError {\n    #[error(\"Could not parse anchor: Must either be \\\"center\\\" or be formatted like \\\"top left\\\"\")]\n    WrongFormat(String),\n    #[error(transparent)]\n    EnumParseError(#[from] EnumParseError),\n}\n\nimpl std::str::FromStr for AnchorPoint {\n    type Err = AnchorPointParseError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        if s == \"center\" {\n            Ok(AnchorPoint { x: AnchorAlignment::CENTER, y: AnchorAlignment::CENTER })\n        } else {\n            let (first, second) = s.split_once(' ').ok_or_else(|| AnchorPointParseError::WrongFormat(s.to_string()))?;\n            let x_y_result: Result<_, EnumParseError> = (move || {\n                Ok(AnchorPoint { x: AnchorAlignment::from_x_alignment(first)?, y: AnchorAlignment::from_y_alignment(second)? })\n            })();\n            x_y_result.or_else(|_| {\n                Ok(AnchorPoint { x: AnchorAlignment::from_x_alignment(second)?, y: AnchorAlignment::from_y_alignment(first)? })\n            })\n        }\n    }\n}\n\n/// Unevaluated variant of [`Coords`]\n#[derive(Clone, Debug, Eq, PartialEq, Serialize)]\npub struct CoordsDef {\n    pub x: Option<SimplExpr>,\n    pub y: Option<SimplExpr>,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n    #[error(transparent)]\n    AnchorPointParseError(#[from] AnchorPointParseError),\n    #[error(transparent)]\n    CoordsError(#[from] coords::Error),\n    #[error(transparent)]\n    EvalError(#[from] EvalError),\n}\n\nimpl CoordsDef {\n    pub fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<Coords, Error> {\n        Ok(Coords {\n            x: convert_to_num_with_unit(&self.x, local_variables)?,\n            y: convert_to_num_with_unit(&self.y, local_variables)?,\n        })\n    }\n}\n\nfn convert_to_num_with_unit(\n    opt_expr: &Option<SimplExpr>,\n    local_variables: &HashMap<VarName, DynVal>,\n) -> Result<NumWithUnit, Error> {\n    Ok(match opt_expr {\n        Some(expr) => NumWithUnit::from_dynval(&expr.eval(local_variables)?)?,\n        None => NumWithUnit::default(),\n    })\n}\n\n/// Unevaluated variant of [`WindowGeometry`]\n#[derive(Clone, Debug, Eq, PartialEq, Serialize)]\npub struct WindowGeometryDef {\n    pub anchor_point: Option<SimplExpr>,\n    pub offset: CoordsDef,\n    pub size: CoordsDef,\n}\n\nimpl FromAstElementContent for WindowGeometryDef {\n    const ELEMENT_NAME: &'static str = \"geometry\";\n\n    fn from_tail<I: Iterator<Item = Ast>>(_span: Span, mut iter: AstIterator<I>) -> DiagResult<Self> {\n        let mut attrs = iter.expect_key_values()?;\n        iter.expect_done()\n            .map_err(|e| e.to_diagnostic().with_notes(vec![\"Check if you are missing a colon in front of a key\".to_string()]))?;\n\n        Ok(WindowGeometryDef {\n            anchor_point: attrs.ast_optional(\"anchor\")?,\n            size: CoordsDef { x: attrs.ast_optional(\"width\")?, y: attrs.ast_optional(\"height\")? },\n            offset: CoordsDef { x: attrs.ast_optional(\"x\")?, y: attrs.ast_optional(\"y\")? },\n        })\n    }\n}\n\nimpl WindowGeometryDef {\n    pub fn eval(&self, local_variables: &HashMap<VarName, DynVal>) -> Result<WindowGeometry, Error> {\n        Ok(WindowGeometry {\n            anchor_point: match &self.anchor_point {\n                Some(expr) => AnchorPoint::from_dynval(&expr.eval(local_variables)?)?,\n                None => AnchorPoint::default(),\n            },\n            size: self.size.eval(local_variables)?,\n            offset: self.offset.eval(local_variables)?,\n        })\n    }\n}\n\n#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize)]\npub struct WindowGeometry {\n    pub anchor_point: AnchorPoint,\n    pub offset: Coords,\n    pub size: Coords,\n}\n\nimpl WindowGeometry {\n    pub fn override_if_given(&self, anchor_point: Option<AnchorPoint>, offset: Option<Coords>, size: Option<Coords>) -> Self {\n        WindowGeometry {\n            anchor_point: anchor_point.unwrap_or(self.anchor_point),\n            offset: offset.unwrap_or(self.offset),\n            size: size.unwrap_or(self.size),\n        }\n    }\n}\n\nimpl std::fmt::Display for WindowGeometry {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}-{} ({})\", self.offset, self.size, self.anchor_point)\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/error.rs",
    "content": "use crate::{\n    format_diagnostic::{lalrpop_error_to_diagnostic, DiagnosticExt, ToDiagnostic},\n    parser::{lexer, parse_error},\n};\nuse codespan_reporting::diagnostic;\nuse eww_shared_util::{Span, Spanned};\nuse simplexpr::dynval;\nuse thiserror::Error;\n\npub type DiagResult<T> = Result<T, DiagError>;\n\n#[derive(Debug, Error)]\n#[error(\"{}\", .0.to_message())]\npub struct DiagError(pub diagnostic::Diagnostic<usize>);\n\nstatic_assertions::assert_impl_all!(DiagError: Send, Sync);\nstatic_assertions::assert_impl_all!(dynval::ConversionError: Send, Sync);\nstatic_assertions::assert_impl_all!(lalrpop_util::ParseError < usize, lexer::Token, parse_error::ParseError>: Send, Sync);\n\nimpl<T: ToDiagnostic> From<T> for DiagError {\n    fn from(x: T) -> Self {\n        Self(x.to_diagnostic())\n    }\n}\n\nimpl DiagError {\n    pub fn note(self, note: &str) -> Self {\n        DiagError(self.0.with_note(note.to_string()))\n    }\n\n    pub fn from_parse_error(\n        file_id: usize,\n        err: lalrpop_util::ParseError<usize, lexer::Token, parse_error::ParseError>,\n    ) -> DiagError {\n        DiagError(lalrpop_error_to_diagnostic(&err, file_id))\n    }\n}\n\npub fn get_parse_error_span<T, E: Spanned>(file_id: usize, err: &lalrpop_util::ParseError<usize, T, E>) -> Span {\n    use lalrpop_util::ParseError::*;\n    match err {\n        InvalidToken { location } => Span(*location, *location, file_id),\n        UnrecognizedEof { location, .. } => Span(*location, *location, file_id),\n        UnrecognizedToken { token, .. } => Span(token.0, token.2, file_id),\n        ExtraToken { token } => Span(token.0, token.2, file_id),\n        User { error } => error.span(),\n    }\n}\n\npub trait DiagResultExt<T> {\n    fn note(self, note: &str) -> DiagResult<T>;\n}\n\nimpl<T> DiagResultExt<T> for DiagResult<T> {\n    fn note(self, note: &str) -> DiagResult<T> {\n        self.map_err(|e| e.note(note))\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/format_diagnostic.rs",
    "content": "use codespan_reporting::diagnostic;\nuse itertools::Itertools;\nuse simplexpr::dynval;\n\nuse diagnostic::*;\n\nuse crate::config::{attributes::AttrError, validate::ValidationError};\n\nuse super::parser::parse_error;\nuse eww_shared_util::{Span, Spanned};\n\npub fn span_to_primary_label(span: Span) -> Label<usize> {\n    Label::primary(span.2, span.0..span.1)\n}\npub fn span_to_secondary_label(span: Span) -> Label<usize> {\n    Label::secondary(span.2, span.0..span.1)\n}\n\n/// Generate a nicely formatted diagnostic\n/// ```rs\n/// gen_diagnostic! {\n///     kind = Severity::Error,\n///     msg = format!(\"Expected value, but got `{}`\", actual),\n///     label = span => \"Expected some value here\",\n///     note = format!(\"Got: {}\", actual),\n/// }\n/// ```\n#[macro_export]\nmacro_rules! gen_diagnostic {\n    ( $(kind = $kind:expr,)?\n      $(msg = $msg:expr)?\n      $(, label = $span:expr $(=> $label:expr)?)?\n      $(, note = $note:expr)? $(,)?\n    ) => {\n        ::codespan_reporting::diagnostic::Diagnostic::new(gen_diagnostic! {\n            @macro_fallback $({$kind})? {::codespan_reporting::diagnostic::Severity::Error}\n        })\n            $(.with_message($msg.to_string()))?\n            $(.with_labels(vec![\n                ::codespan_reporting::diagnostic::Label::primary($span.2, $span.0..$span.1)\n                    $(.with_message($label))?\n            ]))?\n            $(.with_notes(vec![$note.to_string()]))?\n    };\n    ($msg:expr $(, $span:expr $(,)?)?) => {{\n        ::codespan_reporting::diagnostic::Diagnostic::error()\n            .with_message($msg.to_string())\n            $(.with_labels(vec![::codespan_reporting::diagnostic::Label::primary($span.2, $span.0..$span.1)]))?\n    }};\n\n\n    (@macro_fallback { $value:expr } { $fallback:expr }) => {\n        $value\n    };\n    (@macro_fallback { $fallback:expr }) => {\n        $fallback\n    };\n}\n\npub trait DiagnosticExt: Sized {\n    fn with_label(self, label: Label<usize>) -> Self;\n    fn with_note(self, note: String) -> Self;\n}\n\nimpl DiagnosticExt for Diagnostic<usize> {\n    fn with_label(self, label: Label<usize>) -> Self {\n        self.with_labels(vec![label])\n    }\n\n    fn with_note(self, note: String) -> Self {\n        self.with_notes(vec![note])\n    }\n}\n\npub trait ToDiagnostic: std::fmt::Debug {\n    fn to_diagnostic(&self) -> Diagnostic<usize>;\n    fn to_message(&self) -> String {\n        self.to_diagnostic().message\n    }\n}\n\nimpl ToDiagnostic for Diagnostic<usize> {\n    fn to_diagnostic(&self) -> Diagnostic<usize> {\n        self.clone()\n    }\n}\n\nimpl ToDiagnostic for parse_error::ParseError {\n    fn to_diagnostic(&self) -> Diagnostic<usize> {\n        match self {\n            parse_error::ParseError::SimplExpr(source) => lalrpop_error_to_diagnostic(&source.source, source.file_id),\n            parse_error::ParseError::LexicalError(span) => generate_lexical_error_diagnostic(*span),\n        }\n    }\n}\n\nimpl ToDiagnostic for AttrError {\n    fn to_diagnostic(&self) -> Diagnostic<usize> {\n        match self {\n            AttrError::MissingRequiredAttr(span, attr_name) => {\n                gen_diagnostic!(format!(\"Missing attribute `{}`\", attr_name), span)\n            }\n            AttrError::EvaluationError(_span, source) => source.to_diagnostic(),\n            AttrError::Other(span, source) => gen_diagnostic!(source, span),\n        }\n    }\n}\n\nimpl ToDiagnostic for ValidationError {\n    fn to_diagnostic(&self) -> Diagnostic<usize> {\n        match self {\n            ValidationError::MissingAttr { widget_name, arg_name, arg_list_span, use_span } => {\n                let mut diag = Diagnostic::error()\n                    .with_message(self.to_string())\n                    .with_label(span_to_secondary_label(*use_span).with_message(\"Argument missing here\"))\n                    .with_notes(vec![format!(\n                        \"Hint: pass the attribute like so: `({} :{} your-value ...`\",\n                        widget_name, arg_name\n                    )]);\n                if let Some(arg_list_span) = arg_list_span {\n                    diag = diag.with_label(span_to_secondary_label(*arg_list_span).with_message(\"But is required here\"));\n                }\n                diag\n            }\n            ValidationError::UnknownVariable { span, name, in_definition } => {\n                let diag = gen_diagnostic! {\n                    msg = self,\n                    label = span => \"Used here\",\n                    note = if *in_definition {\n                        \"Hint: Either define it as a global variable, or add it to the argument-list of your `defwidget` and pass it as an argument\"\n                    } else {\n                        \"Hint: Define it as a global variable\"\n                    }\n                };\n\n                let mut extra_notes =\n                    vec![format!(\"Hint: If you meant to use the literal value \\\"{}\\\", surround the value in quotes\", name)];\n\n                if let Some(deprecation_note) = variable_deprecation_note(name.to_string()) {\n                    extra_notes.push(deprecation_note)\n                };\n\n                diag.with_notes(extra_notes)\n            }\n            ValidationError::AccidentalBuiltinOverride(span, _widget_name) => gen_diagnostic! {\n                msg = self,\n                label = span => \"Defined here\",\n                note = \"Hint: Give your widget a different name. You could call it \\\"John\\\" for example. That's a cool name.\"\n            },\n        }\n    }\n}\n\nfn variable_deprecation_note(var_name: String) -> Option<String> {\n    (var_name == \"EWW_CPU_USAGE\")\n        .then(|| \"Note: EWW_CPU_USAGE has recently been removed, and has now been renamed to EWW_CPU\".to_string())\n}\n\npub fn lalrpop_error_to_diagnostic<T: std::fmt::Display, E: Spanned + ToDiagnostic>(\n    error: &lalrpop_util::ParseError<usize, T, E>,\n    file_id: usize,\n) -> Diagnostic<usize> {\n    use lalrpop_util::ParseError::*;\n    match error {\n        InvalidToken { location } => gen_diagnostic!(\"Invalid token\", Span::point(*location, file_id)),\n        UnrecognizedEof { location, expected: _ } => gen_diagnostic! {\n            msg = \"Input ended unexpectedly. Check if you have any unclosed delimiters\",\n            label = Span::point(*location, file_id),\n        },\n        UnrecognizedToken { token, expected: _ } => gen_diagnostic! {\n            msg = format!(\"Unexpected token `{}` encountered\", token.1),\n            label = Span(token.0, token.2, file_id) => \"Token unexpected\",\n        },\n        ExtraToken { token } => gen_diagnostic!(format!(\"Extra token encountered: `{}`\", token.1)),\n        User { error } => error.to_diagnostic(),\n    }\n}\n\nimpl ToDiagnostic for simplexpr::parser::lexer::LexicalError {\n    fn to_diagnostic(&self) -> Diagnostic<usize> {\n        generate_lexical_error_diagnostic(self.span())\n    }\n}\n\nimpl ToDiagnostic for simplexpr::eval::EvalError {\n    fn to_diagnostic(&self) -> Diagnostic<usize> {\n        use simplexpr::eval::EvalError;\n        match self {\n            EvalError::NoVariablesAllowed(_name) => gen_diagnostic!(self),\n            EvalError::UnknownVariable(name, similar) => {\n                let mut notes = Vec::new();\n                if similar.len() == 1 {\n                    notes.push(format!(\"Did you mean `{}`?\", similar.first().unwrap()))\n                } else if similar.len() > 1 {\n                    notes.push(format!(\"Did you mean one of: {}?\", similar.iter().map(|x| format!(\"`{}`\", x)).join(\", \")))\n                }\n                // TODO the note here is confusing when it's an unknown variable being used _within_ a string literal / simplexpr\n                // it only really makes sense on top-level symbols\n                notes.push(format!(\"Hint: If you meant to use the literal value \\\"{}\\\", surround the value in quotes\", name));\n                gen_diagnostic!(self).with_notes(notes)\n            }\n            EvalError::Spanned(span, err) => {\n                if let EvalError::JaqParseError(err) = err.as_ref() {\n                    if let Some(ref err) = err.as_ref().0 {\n                        let span = span.new_relative(err.span().start, err.span().end).shifted(1);\n                        let mut diag = gen_diagnostic!(self, span);\n\n                        if let Some(label) = err.label() {\n                            diag = diag.with_label(span_to_secondary_label(span).with_message(label));\n                        }\n\n                        let expected: Vec<_> = err.expected().filter_map(|x| x.clone()).sorted().collect();\n                        if !expected.is_empty() {\n                            let label = format!(\"Expected one of {} here\", expected.join(\", \"));\n                            diag = diag.with_label(span_to_primary_label(span).with_message(label));\n                        }\n                        return diag;\n                    }\n                }\n                return err.as_ref().to_diagnostic().with_label(span_to_primary_label(*span));\n            }\n            _ => gen_diagnostic!(self, self.span()),\n        }\n    }\n}\n\nimpl ToDiagnostic for dynval::ConversionError {\n    fn to_diagnostic(&self) -> Diagnostic<usize> {\n        let diag = gen_diagnostic! {\n            msg = self,\n            label = self.value.span() => format!(\"`{}` is not of type `{}`\", self.value, self.target_type),\n        };\n        diag.with_notes(self.source.as_ref().map(|x| vec![x.to_string()]).unwrap_or_default())\n    }\n}\n\nfn generate_lexical_error_diagnostic(span: Span) -> Diagnostic<usize> {\n    gen_diagnostic! {\n        msg = \"Invalid token\",\n        label = span => \"Invalid token\"\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/lib.rs",
    "content": "#![allow(clippy::comparison_chain)]\n\npub mod ast_error;\npub mod config;\npub mod error;\npub mod format_diagnostic;\npub mod parser;\npub mod value;\n"
  },
  {
    "path": "crates/yuck/src/parser/ast.rs",
    "content": "use itertools::Itertools;\nuse simplexpr::ast::SimplExpr;\n\nuse eww_shared_util::{Span, Spanned, VarName};\nuse std::fmt::Display;\n\nuse super::ast_iterator::AstIterator;\nuse crate::ast_error::AstError;\n\n#[derive(Debug, PartialEq, Eq, Copy, Clone)]\npub enum AstType {\n    List,\n    Array,\n    Keyword,\n    Symbol,\n    // TODO this does no longer correspond to an actual literal ast type as that's replaced with SimplExpr\n    Literal,\n    SimplExpr,\n    Comment,\n    /// A value that could be used as a [SimplExpr]\n    IntoPrimitive,\n}\n\nimpl Display for AstType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            AstType::IntoPrimitive => write!(f, \"primitive\"),\n            _ => write!(f, \"{:?}\", self),\n        }\n    }\n}\n\n#[derive(PartialEq, Eq, Clone, serde::Serialize)]\npub enum Ast {\n    /// I.e.: `(foo bar baz)`\n    List(Span, Vec<Ast>),\n    /// I.e.: `[foo bar baz]`\n    Array(Span, Vec<Ast>),\n    /// I.e.: `:foo`\n    Keyword(Span, String),\n    /// I.e.: `foo`\n    Symbol(Span, String),\n    /// I.e.: `{1 + 2}`\n    SimplExpr(Span, SimplExpr),\n    /// I.e.: `// foo`\n    Comment(Span),\n}\n\nmacro_rules! as_func {\n    ($exprtype:expr, $name:ident $nameref:ident < $t:ty > = $p:pat => $value:expr) => {\n        pub fn $name(self) -> Result<$t, AstError> {\n            match self {\n                $p => Ok($value),\n                x => Err(AstError::WrongExprType(x.span(), $exprtype, x.expr_type())),\n            }\n        }\n\n        pub fn $nameref(&self) -> Result<&$t, AstError> {\n            match self {\n                $p => Ok($value),\n                x => Err(AstError::WrongExprType(x.span(), $exprtype, x.expr_type())),\n            }\n        }\n    };\n}\n\nimpl Ast {\n    as_func!(AstType::Symbol, as_symbol as_symbol_ref<String> = Ast::Symbol(_, x) => x);\n\n    as_func!(AstType::Keyword, as_keyword as_keyword_ref<String> = Ast::Keyword(_, x) => x);\n\n    as_func!(AstType::List, as_list as_list_ref<Vec<Ast>> = Ast::List(_, x) => x);\n\n    as_func!(AstType::Array, as_array as_array_ref<Vec<Ast>> = Ast::Array(_, x) => x);\n\n    pub fn expr_type(&self) -> AstType {\n        match self {\n            Ast::List(..) => AstType::List,\n            Ast::Array(..) => AstType::Array,\n            Ast::Keyword(..) => AstType::Keyword,\n            Ast::Symbol(..) => AstType::Symbol,\n            Ast::SimplExpr(..) => AstType::SimplExpr,\n            Ast::Comment(_) => AstType::Comment,\n        }\n    }\n\n    pub fn as_simplexpr(&self) -> Result<SimplExpr, AstError> {\n        match self {\n            // TODO do I do this?\n            // Ast::Array(span, elements) => todo!()\n            Ast::Symbol(span, x) => Ok(SimplExpr::VarRef(*span, VarName(x.clone()))),\n            Ast::SimplExpr(_span, x) => Ok(x.clone()),\n            _ => Err(AstError::WrongExprType(self.span(), AstType::IntoPrimitive, self.expr_type())),\n        }\n    }\n\n    pub fn try_ast_iter(self) -> Result<AstIterator<impl Iterator<Item = Ast>>, AstError> {\n        let span = self.span();\n        let list = self.as_list()?;\n        Ok(AstIterator::new(span, list.into_iter()))\n    }\n}\n\nimpl std::fmt::Display for Ast {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        use Ast::*;\n        match self {\n            List(_, x) => write!(f, \"({})\", x.iter().map(|e| format!(\"{}\", e)).join(\" \")),\n            Array(_, x) => write!(f, \"({})\", x.iter().map(|e| format!(\"{}\", e)).join(\" \")),\n            Keyword(_, x) => write!(f, \":{}\", x),\n            Symbol(_, x) => write!(f, \"{}\", x),\n            SimplExpr(_, simplexpr::SimplExpr::Literal(value)) => write!(f, \"\\\"{}\\\"\", value),\n            SimplExpr(_, x) => write!(f, \"{{{}}}\", x),\n            Comment(_) => write!(f, \"\"),\n        }\n    }\n}\nimpl std::fmt::Debug for Ast {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self)\n    }\n}\n\nimpl Spanned for Ast {\n    fn span(&self) -> Span {\n        match self {\n            Ast::List(span, _) => *span,\n            Ast::Array(span, _) => *span,\n            Ast::Keyword(span, _) => *span,\n            Ast::Symbol(span, _) => *span,\n            Ast::SimplExpr(span, _) => *span,\n            Ast::Comment(span) => *span,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/parser/ast_iterator.rs",
    "content": "use simplexpr::{ast::SimplExpr, dynval::DynVal};\nuse std::collections::HashMap;\n\nuse super::ast::{Ast, AstType};\nuse crate::{\n    ast_error::AstError,\n    config::attributes::{AttrEntry, Attributes},\n};\nuse eww_shared_util::{AttrName, Span, Spanned, VarName};\n\n/// Iterator over [`crate::parser::ast::Ast`] nodes which allows to explicitly expect specific types of items\npub struct AstIterator<I: Iterator<Item = Ast>> {\n    remaining_span: Span,\n    iter: itertools::PutBack<I>,\n}\n\nmacro_rules! return_or_put_back {\n    ($(fn $name:ident -> $expr_type:expr, $t:ty = $p:pat => $ret:expr)*) => {\n        $(\n            pub fn $name(&mut self) -> Result<$t, AstError> {\n                let expr_type = $expr_type;\n                use eww_shared_util::Spanned;\n                match self.expect_any()? {\n                    $p => Ok($ret),\n                    other => {\n                        let span = other.span();\n                        let actual_type = other.expr_type();\n                        self.put_back(other);\n                        Err(AstError::WrongExprType(span, expr_type, actual_type))\n                    }\n                }\n            }\n        )*\n    };\n}\n\nimpl<I: Iterator<Item = Ast>> AstIterator<I> {\n    return_or_put_back! {\n        fn expect_symbol    -> AstType::Symbol,    (Span, String)    = Ast::Symbol(span, x)    => (span, x)\n        fn expect_list      -> AstType::List,      (Span, Vec<Ast>)  = Ast::List(span, x)      => (span, x)\n        fn expect_array     -> AstType::Array,     (Span, Vec<Ast>)  = Ast::Array(span, x)     => (span, x)\n    }\n\n    pub fn expect_literal(&mut self) -> Result<(Span, DynVal), AstError> {\n        // TODO add some others\n        match self.expect_any()? {\n            // Ast::Array(_, _) => todo!(),\n            Ast::SimplExpr(span, expr) => Ok((span, expr.eval_no_vars()?)),\n            other => {\n                let span = other.span();\n                let actual_type = other.expr_type();\n                self.put_back(other);\n                Err(AstError::WrongExprType(span, AstType::Literal, actual_type))\n            }\n        }\n    }\n\n    pub fn new(span: Span, iter: I) -> Self {\n        AstIterator { remaining_span: span, iter: itertools::put_back(iter) }\n    }\n\n    pub fn expect_any(&mut self) -> Result<Ast, AstError> {\n        self.next().ok_or_else(|| AstError::TooFewElements(self.remaining_span.point_span()))\n    }\n\n    pub fn expect_simplexpr(&mut self) -> Result<(Span, SimplExpr), AstError> {\n        let expr_type = AstType::SimplExpr;\n        match self.expect_any()? {\n            Ast::SimplExpr(span, expr) => Ok((span, expr)),\n            Ast::Symbol(span, var) => Ok((span, SimplExpr::VarRef(span, VarName(var)))),\n            other => {\n                let span = other.span();\n                let actual_type = other.expr_type();\n                self.put_back(other);\n                Err(AstError::WrongExprType(span, expr_type, actual_type))\n            }\n        }\n    }\n\n    pub fn expect_done(&mut self) -> Result<(), AstError> {\n        if let Some(next) = self.next() {\n            self.put_back(next);\n            Err(AstError::NoMoreElementsExpected(self.remaining_span))\n        } else {\n            Ok(())\n        }\n    }\n\n    pub fn expect_key_values(&mut self) -> Result<Attributes, AstError> {\n        parse_key_values(self, true)\n    }\n\n    pub fn put_back(&mut self, ast: Ast) -> Option<Ast> {\n        self.remaining_span.0 = ast.span().0;\n        self.iter.put_back(ast)\n    }\n}\n\nimpl<I: Iterator<Item = Ast>> Iterator for AstIterator<I> {\n    type Item = Ast;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        self.iter.next().inspect(|x| {\n            self.remaining_span.0 = x.span().1;\n        })\n    }\n}\n\n/// Parse consecutive `:keyword value` pairs from an expression iterator into an [Attributes].\nfn parse_key_values(\n    iter: &mut AstIterator<impl Iterator<Item = Ast>>,\n    fail_on_dangling_kw: bool,\n) -> Result<Attributes, AstError> {\n    let mut data = HashMap::new();\n    let mut attrs_span = iter.remaining_span.point_span();\n    loop {\n        match iter.next() {\n            Some(Ast::Keyword(key_span, kw)) => match iter.next() {\n                Some(value) => {\n                    attrs_span.1 = iter.remaining_span.0;\n                    let attr_value = AttrEntry { key_span, value };\n                    data.insert(AttrName(kw), attr_value);\n                }\n                None => {\n                    if fail_on_dangling_kw {\n                        return Err(AstError::DanglingKeyword(key_span, kw.into()));\n                    } else {\n                        iter.iter.put_back(Ast::Keyword(key_span, kw));\n                        attrs_span.1 = iter.remaining_span.0;\n                        return Ok(Attributes::new(attrs_span, data));\n                    }\n                }\n            },\n            next => {\n                if let Some(expr) = next {\n                    iter.iter.put_back(expr);\n                }\n                attrs_span.1 = iter.remaining_span.0;\n                return Ok(Attributes::new(attrs_span, data));\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/parser/from_ast.rs",
    "content": "use super::{ast::Ast, ast_iterator::AstIterator};\nuse crate::{error::*, format_diagnostic::ToDiagnostic, gen_diagnostic};\nuse eww_shared_util::{Span, Spanned};\n\nuse simplexpr::ast::SimplExpr;\n\npub trait FromAst: Sized {\n    fn from_ast(e: Ast) -> DiagResult<Self>;\n}\n\nimpl FromAst for Ast {\n    fn from_ast(e: Ast) -> DiagResult<Self> {\n        Ok(e)\n    }\n}\n\nimpl FromAst for String {\n    fn from_ast(e: Ast) -> DiagResult<Self> {\n        Ok(e.as_simplexpr()?.eval_no_vars().map_err(|e| DiagError(e.to_diagnostic()))?.to_string())\n    }\n}\n\n/// A trait that allows creating a type from the tail of a list-node.\n/// I.e. to parse (foo [a b] (c d)), [`FromAstElementContent::from_tail`] would just get [a b] (c d).\npub trait FromAstElementContent: Sized {\n    const ELEMENT_NAME: &'static str;\n    fn from_tail<I: Iterator<Item = Ast>>(span: Span, iter: AstIterator<I>) -> DiagResult<Self>;\n}\n\nimpl<T: FromAstElementContent> FromAst for T {\n    fn from_ast(e: Ast) -> DiagResult<Self> {\n        let span = e.span();\n        let mut iter = e.try_ast_iter()?;\n        let (element_name_span, element_name) = iter.expect_symbol()?;\n        if Self::ELEMENT_NAME != element_name {\n            return Err(DiagError(gen_diagnostic! {\n                msg = format!(\"Expected element `{}`, but found `{element_name}`\", Self::ELEMENT_NAME),\n                label = element_name_span => format!(\"Expected `{}` here\", Self::ELEMENT_NAME),\n                note = format!(\"Expected: {}\\n     Got: {element_name}\", Self::ELEMENT_NAME),\n            }));\n        }\n        Self::from_tail(span, iter)\n    }\n}\n\nimpl FromAst for SimplExpr {\n    fn from_ast(e: Ast) -> DiagResult<Self> {\n        match e {\n            Ast::Symbol(span, x) => Ok(SimplExpr::var_ref(span, x)),\n            Ast::SimplExpr(_span, x) => Ok(x),\n            _ => Err(DiagError(gen_diagnostic! {\n                msg = format!(\"Expected value, but got `{}`\", e.expr_type()),\n                label = e.span() => \"Expected some value here\",\n                note = format!(\"Got: {}\", e.expr_type()),\n            })),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/parser/lexer.rs",
    "content": "use once_cell::sync::Lazy;\nuse regex::{Regex, RegexSet};\n\nuse super::parse_error;\nuse eww_shared_util::{Span, Spanned};\n\n#[derive(Debug, PartialEq, Eq, Clone)]\npub enum Token {\n    LPren,\n    RPren,\n    LBrack,\n    RBrack,\n    True,\n    False,\n    NumLit(String),\n    Symbol(String),\n    Keyword(String),\n    SimplExpr(Vec<(usize, simplexpr::parser::lexer::Token, usize)>),\n    Comment,\n    Skip,\n}\n\nimpl std::fmt::Display for Token {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Token::LPren => write!(f, \"'('\"),\n            Token::RPren => write!(f, \"')'\"),\n            Token::LBrack => write!(f, \"'['\"),\n            Token::RBrack => write!(f, \"']'\"),\n            Token::True => write!(f, \"true\"),\n            Token::False => write!(f, \"false\"),\n            Token::NumLit(x) => write!(f, \"{}\", x),\n            Token::Symbol(x) => write!(f, \"{}\", x),\n            Token::Keyword(x) => write!(f, \"{}\", x),\n            Token::SimplExpr(x) => write!(f, \"{{{:?}}}\", x.iter().map(|x| &x.1)),\n            Token::Comment => write!(f, \"\"),\n            Token::Skip => write!(f, \"\"),\n        }\n    }\n}\n\nmacro_rules! regex_rules {\n    ($( $regex:literal => $token:expr),*) => {\n        static LEXER_REGEX_SET: Lazy<RegexSet> = Lazy::new(|| { RegexSet::new(&[\n            $(concat!(\"^\", $regex)),*\n        ]).unwrap()});\n        static LEXER_REGEXES: Lazy<Vec<Regex>> = Lazy::new(|| { vec![\n            $(Regex::new(concat!(\"^\", $regex)).unwrap()),*\n        ]});\n        static LEXER_FNS: Lazy<Vec<Box<dyn Fn(String) -> Token + Sync + Send>>> = Lazy::new(|| { vec![\n            $(Box::new($token)),*\n        ]});\n    }\n}\n\nregex_rules! {\n    r\"\\(\" => |_| Token::LPren,\n    r\"\\)\" => |_| Token::RPren,\n    r\"\\[\" => |_| Token::LBrack,\n    r\"\\]\" => |_| Token::RBrack,\n    r\"true\"  => |_| Token::True,\n    r\"false\" => |_| Token::False,\n    r#\"[+-]?(?:[0-9]+[.])?[0-9]+\"# => Token::NumLit,\n    r#\":[^\\s\\)\\]}]+\"# => Token::Keyword,\n    r#\"[a-zA-Z_!\\?<>/\\.\\*-\\+\\-][^\\s{}\\(\\)\\[\\](){}]*\"# => Token::Symbol,\n    r#\";.*\"# => |_| Token::Comment,\n    r\"[ \\t\\n\\f]+\" => |_| Token::Skip\n}\n\npub struct Lexer {\n    source: String,\n    file_id: usize,\n    failed: bool,\n    pos: usize,\n}\n\nimpl Lexer {\n    pub fn new(file_id: usize, source: String) -> Self {\n        Lexer { source, file_id, failed: false, pos: 0 }\n    }\n\n    fn string_lit(&mut self) -> Option<Result<(usize, Token, usize), parse_error::ParseError>> {\n        let mut simplexpr_lexer = simplexpr::parser::lexer::Lexer::new(self.file_id, self.pos, &self.source[self.pos..]);\n        match simplexpr_lexer.string_lit() {\n            Some(Ok((lo, segments, hi))) => {\n                self.pos = hi;\n                self.advance_until_char_boundary();\n                Some(Ok((lo, Token::SimplExpr(vec![(lo, simplexpr::parser::lexer::Token::StringLit(segments), hi)]), hi)))\n            }\n            Some(Err(e)) => Some(Err(parse_error::ParseError::LexicalError(e.0))),\n            None => None,\n        }\n    }\n\n    fn simplexpr(&mut self) -> Option<Result<(usize, Token, usize), parse_error::ParseError>> {\n        use simplexpr::parser::lexer as simplexpr_lexer;\n        self.pos += 1;\n        let mut simplexpr_lexer = simplexpr_lexer::Lexer::new(self.file_id, self.pos, &self.source[self.pos..]);\n        let mut toks: Vec<(usize, _, usize)> = Vec::new();\n        let mut end;\n        let mut curly_nesting = 0;\n        loop {\n            match simplexpr_lexer.next_token()? {\n                Ok((lo, tok, hi)) => {\n                    end = hi;\n                    if tok == simplexpr_lexer::Token::LCurl {\n                        curly_nesting += 1;\n                    } else if tok == simplexpr_lexer::Token::RCurl {\n                        curly_nesting -= 1;\n                        if curly_nesting < 0 {\n                            let start = toks.first().map(|(start, ..)| *start).unwrap_or(end);\n                            self.pos = end;\n                            self.advance_until_char_boundary();\n                            return Some(Ok((start, Token::SimplExpr(toks), end)));\n                        }\n                    }\n                    toks.push((lo, tok, hi));\n                }\n                Err(err) => {\n                    return Some(Err(parse_error::ParseError::LexicalError(err.span())));\n                }\n            }\n        }\n    }\n\n    fn advance_until_char_boundary(&mut self) {\n        while self.pos < self.source.len() && !self.source.is_char_boundary(self.pos) {\n            self.pos += 1;\n        }\n    }\n}\n\nimpl Iterator for Lexer {\n    type Item = Result<(usize, Token, usize), parse_error::ParseError>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        loop {\n            if self.failed || self.pos >= self.source.len() {\n                return None;\n            }\n            let remaining = &self.source[self.pos..];\n            if remaining.starts_with(&['\"', '\\'', '`'][..]) {\n                return self.string_lit();\n            } else if remaining.starts_with('{') {\n                return self.simplexpr();\n            } else {\n                let match_set = LEXER_REGEX_SET.matches(remaining);\n                let matched_token = match_set\n                    .into_iter()\n                    .map(|i: usize| {\n                        let m = LEXER_REGEXES[i].find(remaining).unwrap();\n                        (m.end(), i)\n                    })\n                    .min_by_key(|(_, x)| *x);\n\n                let (len, i) = match matched_token {\n                    Some(x) => x,\n                    None => {\n                        self.failed = true;\n                        return Some(Err(parse_error::ParseError::LexicalError(Span(self.pos, self.pos, self.file_id))));\n                    }\n                };\n\n                let tok_str = &self.source[self.pos..self.pos + len];\n                let old_pos = self.pos;\n                self.pos += len;\n                match LEXER_FNS[i](tok_str.to_string()) {\n                    Token::Skip | Token::Comment => {}\n                    token => {\n                        return Some(Ok((old_pos, token, self.pos)));\n                    }\n                }\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod test {\n\n    use super::*;\n    use eww_shared_util::snapshot_string;\n    use itertools::Itertools;\n\n    macro_rules! v {\n        ($x:literal) => {\n            Lexer::new(0, $x.to_string())\n                .map(|x| match x {\n                    Ok((l, x, r)) => format!(\"({}, {:?}, {})\", l, x, r),\n                    Err(err) => format!(\"{}\", err),\n                })\n                .join(\"\\n\")\n        };\n    }\n\n    snapshot_string! {\n        basic => v!(r#\"(foo + - \"text\" )\"#),\n        basic_simplexpr => v!(r#\"({2})\"#),\n        escaped_strings => v!(r#\"{ bla \"} \\\" }\" \" \\\" \"}\"#),\n        escaped_quote => v!(r#\"\"< \\\" >\"\"#),\n        char_boundary => v!(r#\"{ \"   \" + music}\"#),\n        quotes_in_quotes => v!(r#\"{ \" } ' }\" }\"#),\n        end_with_string_interpolation => v!(r#\"(box \"foo ${1 + 2}\")\"#),\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/parser/mod.rs",
    "content": "use eww_shared_util::{Span, Spanned};\nuse lalrpop_util::lalrpop_mod;\n\nuse crate::gen_diagnostic;\n\nuse super::error::{DiagError, DiagResult};\nuse ast::Ast;\n\npub mod ast;\npub mod ast_iterator;\npub mod from_ast;\npub(crate) mod lexer;\npub(crate) mod parse_error;\n\nlalrpop_mod!(\n    #[allow(clippy::all)]\n    pub parser,\n    \"/parser/parser.rs\"\n);\n\npub fn parse_string(file_id: usize, s: &str) -> DiagResult<Ast> {\n    let lexer = lexer::Lexer::new(file_id, s.to_string());\n    let parser = parser::AstParser::new();\n    parser.parse(file_id, lexer).map_err(|e| DiagError::from_parse_error(file_id, e))\n}\n\n/// Parse multiple toplevel nodes into a list of [Ast]\npub fn parse_toplevel(file_id: usize, s: String) -> DiagResult<(Span, Vec<Ast>)> {\n    let lexer = lexer::Lexer::new(file_id, s);\n    let parser = parser::ToplevelParser::new();\n    parser.parse(file_id, lexer).map_err(|e| DiagError::from_parse_error(file_id, e))\n}\n\n/// get a single ast node from a list of asts, returning an Err if the length is not exactly 1.\npub fn require_single_toplevel(span: Span, mut asts: Vec<Ast>) -> DiagResult<Ast> {\n    match asts.len() {\n        1 => Ok(asts.remove(0)),\n        0 => Err(DiagError(gen_diagnostic! {\n            msg = \"Expected exactly one element, but got none\",\n            label = span\n        })),\n        _n => Err(DiagError(gen_diagnostic! {\n            msg = \"Expected exactly one element, but but got {n}\",\n            label = asts.get(1).unwrap().span().to(asts.last().unwrap().span()) => \"these elements must not be here\",\n            note = \"Consider wrapping the elements in some container element\",\n        })),\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    macro_rules! test_parser {\n    ($($text:literal),*) => {{\n        let p = parser::AstParser::new();\n        use lexer::Lexer;\n\n        ::insta::with_settings!({sort_maps => true}, {\n            $(\n                ::insta::assert_debug_snapshot!(p.parse(0, Lexer::new(0, $text.to_string())));\n            )*\n        });\n    }}\n}\n\n    #[test]\n    fn test() {\n        test_parser!(\n            \"1\",\n            \"(12)\",\n            \"1.2\",\n            \"-1.2\",\n            \"(1 2)\",\n            \"(1 :foo 1)\",\n            \"(:foo 1)\",\n            \"(:foo->: 1)\",\n            \"(foo 1)\",\n            \"(lol😄 1)\",\n            r#\"(test \"hi\")\"#,\n            r#\"(test \"h\\\"i\")\"#,\n            r#\"(test \" hi \")\"#,\n            \"(+ (1 2 (* 2 5)))\",\n            r#\"foo ; test\"#,\n            r#\"(f arg ; test\n        arg2)\"#,\n            \"\\\"h\\\\\\\"i\\\"\"\n        );\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/parser/parse_error.rs",
    "content": "use eww_shared_util::{Span, Spanned};\n\n#[derive(Debug, thiserror::Error)]\npub enum ParseError {\n    #[error(\"{0}\")]\n    SimplExpr(simplexpr::error::ParseError),\n    #[error(\"Unknown token\")]\n    LexicalError(Span),\n}\n\nimpl Spanned for ParseError {\n    fn span(&self) -> Span {\n        match self {\n            ParseError::SimplExpr(err) => err.span(),\n            ParseError::LexicalError(span) => *span,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/parser/parser.lalrpop",
    "content": "use crate::parser::{lexer::Token, ast::Ast, parse_error};\nuse eww_shared_util::Span;\nuse simplexpr::ast::SimplExpr;\nuse simplexpr;\nuse lalrpop_util::ParseError;\n\ngrammar(file_id: usize);\n\nextern {\n    type Location = usize;\n    type Error = parse_error::ParseError;\n\n    enum Token {\n        \"(\" => Token::LPren,\n        \")\" => Token::RPren,\n        \"[\" => Token::LBrack,\n        \"]\" => Token::RBrack,\n        \"true\" => Token::True,\n        \"false\" => Token::False,\n        \"number\" => Token::NumLit(<String>),\n        \"symbol\" => Token::Symbol(<String>),\n        \"keyword\" => Token::Keyword(<String>),\n        \"simplexpr\" => Token::SimplExpr(<Vec<(usize, simplexpr::parser::lexer::Token, usize)>>),\n        \"comment\" => Token::Comment,\n    }\n}\n\npub Toplevel: (Span, Vec<Ast>) = {\n    <l:@L> <elems:(<Ast>)*> <r:@R> => (Span(l, r, file_id), elems)\n}\n\npub Ast: Ast = {\n    <l:@L> \"(\" <elems:(<Ast>)*> \")\" <r:@R> => Ast::List(Span(l, r, file_id), elems),\n    <l:@L> \"[\" <elems:(<Ast>)*> \"]\" <r:@R> => Ast::Array(Span(l, r, file_id), elems),\n    <l:@L> <expr:SimplExpr> <r:@R> => Ast::SimplExpr(Span(l, r, file_id), expr),\n    <x:Keyword> => x,\n    <x:Symbol> => x,\n    <l:@L> <x:Literal> <r:@R> => Ast::SimplExpr(Span(l, r, file_id), SimplExpr::literal(Span(l, r, file_id), x.into())),\n    <l:@L> \"comment\" <r:@R> => Ast::Comment(Span(l, r, file_id)),\n};\n\nKeyword: Ast = <l:@L> <x:\"keyword\"> <r:@R> => Ast::Keyword(Span(l, r, file_id), x[1..].to_string());\nSymbol: Ast = <l:@L> <x:\"symbol\"> <r:@R> => Ast::Symbol(Span(l, r, file_id), x.to_string());\n\nLiteral: String = {\n    <Num> => <>,\n    <Bool> => <>,\n};\n\nSimplExpr: SimplExpr = {\n   <l:@L> <x:\"simplexpr\"> =>? {\n        let parser = simplexpr::simplexpr_parser::ExprParser::new();\n        parser.parse(file_id, x.into_iter().map(Ok))\n            .map_err(|e| ParseError::User {\n                error: parse_error::ParseError::SimplExpr(simplexpr::error::ParseError::from_parse_error(file_id, e))\n            })\n   }\n}\n\n\nNum: String = <\"number\"> => <>.to_string();\nBool: String = {\n    \"true\" => \"true\".to_string(),\n    \"false\" => \"false\".to_string(),\n}\n\n\n// vim:shiftwidth=4\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__element__test__test.snap",
    "content": "---\nsource: src/parser/element.rs\nexpression: \"Element::<Ast, Ast>::from_ast(parser.parse(0, lexer).unwrap()).unwrap()\"\n\n---\nElement {\n    name: \"box\",\n    attrs: {\n        \"baz\": \"hi\",\n        \"bar\": \"12\",\n    },\n    children: [\n        foo,\n        (bar),\n    ],\n    span: 0..33,\n}\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-10.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(lol😄 1)\\\".to_string()))\"\n\n---\nOk(\n    (lol😄 \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-11.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\"hi\\\")\\\"#.to_string()))\"\n\n---\nOk(\n    (test \"hi\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-12.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\"h\\\\\\\"i\\\")\\\"#.to_string()))\"\n\n---\nOk(\n    (test \"h\\\"i\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-13.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\" hi \\\")\\\"#.to_string()))\"\n\n---\nOk(\n    (test \" hi \"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-14.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(+ (1 2 (* 2 5)))\\\".to_string()))\"\n\n---\nOk(\n    (+ (\"1\" \"2\" (* \"2\" \"5\"))),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-15.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"foo ; test\\\"#.to_string()))\"\n\n---\nOk(\n    foo,\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-16.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(f arg ; test\\n        arg2)\\\"#.to_string()))\"\n\n---\nOk(\n    (f arg arg2),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-17.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"\\\\\\\"h\\\\\\\\\\\\\\\"i\\\\\\\"\\\".to_string()))\"\n\n---\nOk(\n    \"h\\\"i\",\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-2.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(12)\\\".to_string()))\"\n\n---\nOk(\n    (\"12\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-3.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"1.2\\\".to_string()))\"\n\n---\nOk(\n    \"1.2\",\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-4.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"-1.2\\\".to_string()))\"\n\n---\nOk(\n    \"-1.2\",\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-5.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(1 2)\\\".to_string()))\"\n\n---\nOk(\n    (\"1\" \"2\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-6.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(1 :foo 1)\\\".to_string()))\"\n\n---\nOk(\n    (\"1\" :foo \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-7.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(:foo 1)\\\".to_string()))\"\n\n---\nOk(\n    (:foo \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-8.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(:foo->: 1)\\\".to_string()))\"\n\n---\nOk(\n    (:foo->: \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test-9.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(foo 1)\\\".to_string()))\"\n\n---\nOk(\n    (foo \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/eww_config__parser__test.snap",
    "content": "---\nsource: src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"1\\\".to_string()))\"\n\n---\nOk(\n    \"1\",\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__basic.snap",
    "content": "---\nsource: crates/yuck/src/parser/lexer.rs\nexpression: \"v!(r#\\\"(foo + - \\\"text\\\" )\\\"#)\"\n\n---\n(0, LPren, 1)\n(1, Symbol(\"foo\"), 4)\n(5, Symbol(\"+\"), 6)\n(7, Symbol(\"-\"), 8)\n(9, SimplExpr([(9, StringLit([(9, Literal(\"text\"), 15)]), 15)]), 15)\n(16, RPren, 17)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__basic_simplexpr.snap",
    "content": "---\nsource: crates/yuck/src/parser/lexer.rs\nexpression: \"v!(r#\\\"({2})\\\"#)\"\n\n---\n(0, LPren, 1)\n(2, SimplExpr([(2, NumLit(\"2\"), 3)]), 4)\n(4, RPren, 5)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__char_boundary.snap",
    "content": "---\nsource: crates/yuck/src/parser/lexer.rs\nexpression: \"v!(r#\\\"{ \\\"   \\\" + music}\\\"#)\"\n\n---\n(2, SimplExpr([(2, StringLit([(2, Literal(\"\\u{f001}   \"), 10)]), 10), (11, Plus, 12), (13, Ident(\"music\"), 18)]), 19)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__end_with_string_interpolation.snap",
    "content": "---\nsource: crates/yuck/src/parser/lexer.rs\nexpression: \"v!(r#\\\"(box \\\"foo ${1 + 2}\\\")\\\"#)\"\n\n---\n(0, LPren, 1)\n(1, Symbol(\"box\"), 4)\n(5, SimplExpr([(5, StringLit([(5, Literal(\"foo \"), 12), (12, Interp([(12, NumLit(\"1\"), 13), (14, Plus, 15), (16, NumLit(\"2\"), 17)]), 17), (17, Literal(\"\"), 19)]), 19)]), 19)\n(19, RPren, 20)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__escaped_quote.snap",
    "content": "---\nsource: crates/yuck/src/parser/lexer.rs\nexpression: \"v!(r#\\\"\\\"< \\\\\\\" >\\\"\\\"#)\"\n\n---\n(0, SimplExpr([(0, StringLit([(0, Literal(\"< \\\" >\"), 8)]), 8)]), 8)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__escaped_strings.snap",
    "content": "---\nsource: crates/yuck/src/parser/lexer.rs\nexpression: \"v!(r#\\\"{ bla \\\"} \\\\\\\" }\\\" \\\" \\\\\\\" \\\"}\\\"#)\"\n\n---\n(2, SimplExpr([(2, Ident(\"bla\"), 5), (6, StringLit([(6, Literal(\"} \\\" }\"), 14)]), 14), (15, StringLit([(15, Literal(\" \\\" \"), 21)]), 21)]), 22)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__lexer__test__quotes_in_quotes.snap",
    "content": "---\nsource: crates/yuck/src/parser/lexer.rs\nexpression: \"v!(r#\\\"{ \\\" } ' }\\\" }\\\"#)\"\n\n---\n(2, SimplExpr([(2, StringLit([(2, Literal(\" } ' }\"), 10)]), 10)]), 12)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__lexer__yuck_lexer-2.snap",
    "content": "---\nsource: crates/yuck/src/parser/lexer.rs\nexpression: \"Lexer::new(0, r#\\\"{ bla \\\"} \\\\\\\" }\\\" \\\" \\\\\\\" \\\"}\\\"#.to_string()).collect_vec()\"\n\n---\n[\n    Ok(\n        (\n            2,\n            SimplExpr(\n                [\n                    (\n                        2,\n                        Ident(\n                            \"bla\",\n                        ),\n                        5,\n                    ),\n                    (\n                        6,\n                        StringLit(\n                            [\n                                (\n                                    6,\n                                    Literal(\n                                        \"} \\\" }\",\n                                    ),\n                                    14,\n                                ),\n                            ],\n                        ),\n                        14,\n                    ),\n                    (\n                        15,\n                        StringLit(\n                            [\n                                (\n                                    15,\n                                    Literal(\n                                        \" \\\" \",\n                                    ),\n                                    21,\n                                ),\n                            ],\n                        ),\n                        21,\n                    ),\n                ],\n            ),\n            21,\n        ),\n    ),\n]\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__lexer__yuck_lexer-3.snap",
    "content": "---\nsource: crates/yuck/src/parser/lexer.rs\nexpression: \"Lexer::new(0, r#\\\"\\\"< \\\\\\\" >\\\"\\\"#.to_string()).collect_vec()\"\n\n---\n[\n    Ok(\n        (\n            0,\n            SimplExpr(\n                [\n                    (\n                        0,\n                        StringLit(\n                            [\n                                (\n                                    0,\n                                    Literal(\n                                        \"< \\\" >\",\n                                    ),\n                                    8,\n                                ),\n                            ],\n                        ),\n                        8,\n                    ),\n                ],\n            ),\n            8,\n        ),\n    ),\n]\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__lexer__yuck_lexer-4.snap",
    "content": "---\nsource: crates/yuck/src/parser/lexer.rs\nexpression: \"Lexer::new(0, r#\\\"{ \\\"   \\\" + music}\\\"#.to_string()).collect_vec()\"\n\n---\n[\n    Ok(\n        (\n            2,\n            SimplExpr(\n                [\n                    (\n                        2,\n                        StringLit(\n                            [\n                                (\n                                    2,\n                                    Literal(\n                                        \"\\u{f001}   \",\n                                    ),\n                                    10,\n                                ),\n                            ],\n                        ),\n                        10,\n                    ),\n                    (\n                        11,\n                        Plus,\n                        12,\n                    ),\n                    (\n                        13,\n                        Ident(\n                            \"music\",\n                        ),\n                        18,\n                    ),\n                ],\n            ),\n            18,\n        ),\n    ),\n]\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__lexer__yuck_lexer.snap",
    "content": "---\nsource: crates/yuck/src/parser/lexer.rs\nexpression: \"Lexer::new(0, r#\\\"(foo + - \\\"text\\\" )\\\"#.to_string()).collect_vec()\"\n\n---\n[\n    Ok(\n        (\n            0,\n            LPren,\n            1,\n        ),\n    ),\n    Ok(\n        (\n            1,\n            Symbol(\n                \"foo\",\n            ),\n            4,\n        ),\n    ),\n    Ok(\n        (\n            5,\n            Symbol(\n                \"+\",\n            ),\n            6,\n        ),\n    ),\n    Ok(\n        (\n            7,\n            Symbol(\n                \"-\",\n            ),\n            8,\n        ),\n    ),\n    Ok(\n        (\n            9,\n            SimplExpr(\n                [\n                    (\n                        9,\n                        StringLit(\n                            [\n                                (\n                                    9,\n                                    Literal(\n                                        \"text\",\n                                    ),\n                                    15,\n                                ),\n                            ],\n                        ),\n                        15,\n                    ),\n                ],\n            ),\n            15,\n        ),\n    ),\n    Ok(\n        (\n            16,\n            RPren,\n            17,\n        ),\n    ),\n]\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-10.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(lol😄 1)\\\".to_string()))\"\n\n---\nOk(\n    (lol😄 \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-11.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\"hi\\\")\\\"#.to_string()))\"\n\n---\nOk(\n    (test \"hi\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-12.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\"h\\\\\\\"i\\\")\\\"#.to_string()))\"\n\n---\nOk(\n    (test \"h\"i\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-13.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\" hi \\\")\\\"#.to_string()))\"\n\n---\nOk(\n    (test \" hi \"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-14.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(+ (1 2 (* 2 5)))\\\".to_string()))\"\n\n---\nOk(\n    (+ (\"1\" \"2\" (* \"2\" \"5\"))),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-15.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"foo ; test\\\"#.to_string()))\"\n\n---\nOk(\n    foo,\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-16.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(f arg ; test\\n        arg2)\\\"#.to_string()))\"\n\n---\nOk(\n    (f arg arg2),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-17.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"\\\\\\\"h\\\\\\\\\\\\\\\"i\\\\\\\"\\\".to_string()))\"\n\n---\nOk(\n    \"h\"i\",\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-2.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(12)\\\".to_string()))\"\n\n---\nOk(\n    (\"12\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-3.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"1.2\\\".to_string()))\"\n\n---\nOk(\n    \"1.2\",\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-4.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"-1.2\\\".to_string()))\"\n\n---\nOk(\n    \"-1.2\",\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-5.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(1 2)\\\".to_string()))\"\n\n---\nOk(\n    (\"1\" \"2\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-6.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(1 :foo 1)\\\".to_string()))\"\n\n---\nOk(\n    (\"1\" :foo \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-7.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(:foo 1)\\\".to_string()))\"\n\n---\nOk(\n    (:foo \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-8.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(:foo->: 1)\\\".to_string()))\"\n\n---\nOk(\n    (:foo->: \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test-9.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"(foo 1)\\\".to_string()))\"\n\n---\nOk(\n    (foo \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nexpression: \"p.parse(0, Lexer::new(0, \\\"1\\\".to_string()))\"\n\n---\nOk(\n    \"1\",\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-10.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"(lol😄 1)\\\".to_string()))\"\n---\nOk(\n    (lol😄 \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-11.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\"hi\\\")\\\"#.to_string()))\"\n---\nOk(\n    (test \"hi\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-12.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\"h\\\\\\\"i\\\")\\\"#.to_string()))\"\n---\nOk(\n    (test \"h\"i\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-13.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(test \\\" hi \\\")\\\"#.to_string()))\"\n---\nOk(\n    (test \" hi \"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-14.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"(+ (1 2 (* 2 5)))\\\".to_string()))\"\n---\nOk(\n    (+ (\"1\" \"2\" (* \"2\" \"5\"))),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-15.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"foo ; test\\\"#.to_string()))\"\n---\nOk(\n    foo,\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-16.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, r#\\\"(f arg ; test\\n        arg2)\\\"#.to_string()))\"\n---\nOk(\n    (f arg arg2),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-17.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"\\\\\\\"h\\\\\\\\\\\\\\\"i\\\\\\\"\\\".to_string()))\"\n---\nOk(\n    \"h\"i\",\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-2.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"(12)\\\".to_string()))\"\n---\nOk(\n    (\"12\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-3.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"1.2\\\".to_string()))\"\n---\nOk(\n    \"1.2\",\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-4.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"-1.2\\\".to_string()))\"\n---\nOk(\n    \"-1.2\",\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-5.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"(1 2)\\\".to_string()))\"\n---\nOk(\n    (\"1\" \"2\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-6.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"(1 :foo 1)\\\".to_string()))\"\n---\nOk(\n    (\"1\" :foo \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-7.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"(:foo 1)\\\".to_string()))\"\n---\nOk(\n    (:foo \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-8.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"(:foo->: 1)\\\".to_string()))\"\n---\nOk(\n    (:foo->: \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test-9.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"(foo 1)\\\".to_string()))\"\n---\nOk(\n    (foo \"1\"),\n)\n"
  },
  {
    "path": "crates/yuck/src/parser/snapshots/yuck__parser__test__test.snap",
    "content": "---\nsource: crates/yuck/src/parser/mod.rs\nassertion_line: 68\nexpression: \"p.parse(0, Lexer::new(0, \\\"1\\\".to_string()))\"\n---\nOk(\n    \"1\",\n)\n"
  },
  {
    "path": "crates/yuck/src/value/coords.rs",
    "content": "use derive_more::{Debug, *};\nuse once_cell::sync::Lazy;\nuse serde::{Deserialize, Serialize};\nuse smart_default::SmartDefault;\nuse std::{fmt, str::FromStr};\n\n#[derive(Debug, thiserror::Error)]\npub enum Error {\n    #[error(\"Failed to parse \\\"{0}\\\" as a length value\")]\n    NumParseFailed(String),\n    #[error(\"Invalid unit \\\"{0}\\\", must be either % or px\")]\n    InvalidUnit(String),\n    #[error(\"Invalid format. Coordinates must be formated like 200x100\")]\n    MalformedCoords,\n}\n\n#[derive(Clone, Copy, PartialEq, Deserialize, Serialize, Display, Debug, SmartDefault)]\npub enum NumWithUnit {\n    #[display(\"{}%\", _0)]\n    #[debug(\"{}%\", _0)]\n    Percent(f32),\n    #[display(\"{}px\", _0)]\n    #[debug(\"{}px\", _0)]\n    #[default]\n    Pixels(i32),\n}\n\nimpl NumWithUnit {\n    pub fn pixels_relative_to(&self, max: i32) -> i32 {\n        match *self {\n            NumWithUnit::Percent(n) => ((max as f64 / 100.0) * n as f64) as i32,\n            NumWithUnit::Pixels(n) => n,\n        }\n    }\n\n    pub fn perc_relative_to(&self, max: i32) -> f32 {\n        match *self {\n            NumWithUnit::Percent(n) => n,\n            NumWithUnit::Pixels(n) => ((n as f64 / max as f64) * 100.0) as f32,\n        }\n    }\n}\n\nimpl FromStr for NumWithUnit {\n    type Err = Error;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        static PATTERN: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(\"^(-?\\\\d+(?:.\\\\d+)?)(.*)$\").unwrap());\n\n        let captures = PATTERN.captures(s).ok_or_else(|| Error::NumParseFailed(s.to_string()))?;\n        let value = captures.get(1).unwrap().as_str().parse::<f32>().map_err(|_| Error::NumParseFailed(s.to_string()))?;\n        match captures.get(2).unwrap().as_str() {\n            \"px\" | \"\" => Ok(NumWithUnit::Pixels(value.floor() as i32)),\n            \"%\" => Ok(NumWithUnit::Percent(value)),\n            unit => Err(Error::InvalidUnit(unit.to_string())),\n        }\n    }\n}\n\n#[derive(Clone, Copy, PartialEq, Deserialize, Serialize, Display, Default)]\n#[display(\"{}*{}\", x, y)]\npub struct Coords {\n    pub x: NumWithUnit,\n    pub y: NumWithUnit,\n}\n\nimpl FromStr for Coords {\n    type Err = Error;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        let (x, y) = s\n            .split_once(|x: char| x.to_ascii_lowercase() == 'x' || x.to_ascii_lowercase() == '*')\n            .ok_or(Error::MalformedCoords)?;\n        Coords::from_strs(x, y)\n    }\n}\n\nimpl fmt::Debug for Coords {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"CoordsWithUnits({}, {})\", self.x, self.y)\n    }\n}\n\nimpl Coords {\n    pub fn from_pixels((x, y): (i32, i32)) -> Self {\n        Coords { x: NumWithUnit::Pixels(x), y: NumWithUnit::Pixels(y) }\n    }\n\n    /// parse a string for x and a string for y into a [`Coords`] object.\n    pub fn from_strs(x: &str, y: &str) -> Result<Coords, Error> {\n        Ok(Coords { x: x.parse()?, y: y.parse()? })\n    }\n\n    /// resolve the possibly relative coordinates relative to a given containers size\n    pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) {\n        (self.x.pixels_relative_to(width), self.y.pixels_relative_to(height))\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n    use pretty_assertions::assert_eq;\n\n    #[test]\n    fn test_parse_num_with_unit() {\n        assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str(\"55\").unwrap());\n        assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str(\"55px\").unwrap());\n        assert_eq!(NumWithUnit::Percent(55.0), NumWithUnit::from_str(\"55%\").unwrap());\n        assert_eq!(NumWithUnit::Percent(55.5), NumWithUnit::from_str(\"55.5%\").unwrap());\n        assert!(NumWithUnit::from_str(\"55pp\").is_err());\n    }\n\n    #[test]\n    fn test_parse_coords() {\n        assert_eq!(Coords { x: NumWithUnit::Pixels(50), y: NumWithUnit::Pixels(60) }, Coords::from_str(\"50x60\").unwrap());\n        assert!(Coords::from_str(\"5060\").is_err());\n    }\n}\n"
  },
  {
    "path": "crates/yuck/src/value/mod.rs",
    "content": "pub mod coords;\npub use coords::*;\n"
  },
  {
    "path": "default.nix",
    "content": "(import (\n  let\n    lock = builtins.fromJSON (builtins.readFile ./flake.lock);\n  in\n  fetchTarball {\n    url =\n      lock.nodes.flake-compat.locked.url\n        or \"https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz\";\n    sha256 = lock.nodes.flake-compat.locked.narHash;\n  }\n) { src = ./.; }).defaultNix\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "book\n"
  },
  {
    "path": "docs/book.toml",
    "content": "[book]\nauthors = [\"elkowar\"]\nlanguage = \"en\"\nmultilingual = false\nsrc = \"src\"\ntitle = \"eww documentation\"\n\n\n[output.html]\ndefault-theme = \"ayu\"\nno-section-label = true\ngit-repository-url = \"https://github.com/elkowar/eww/tree/master/docs\"\n"
  },
  {
    "path": "docs/src/SUMMARY.md",
    "content": "# Summary\n\n- [Eww - Widgets for everyone!](./eww.md)\n- [Configuration](./configuration.md)\n- [Eww expressions](./expression_language.md)\n- [Theming with GTK](./working_with_gtk.md)\n- [Magic Variables](./magic-vars.md)\n- [Widgets](./widgets.md)\n- [Troubleshooting](./troubleshooting.md)\n- [Examples](./examples.md)\n"
  },
  {
    "path": "docs/src/configuration.md",
    "content": "# Writing your eww configuration\n\n(For a list of all built-in widgets (i.e. `box`, `label`, `button`),  see [Widget Documentation](widgets.md).)\\\nEww is configured using its own language called `yuck`.\nUsing yuck, you declare the structure and content of your widgets, the geometry, position, and behavior of any windows,\nas well as any state and data that will be used in your widgets.\nYuck is based around S-expressions, which you may know from lisp-like languages.\nIf you're using vim, you can make use of [yuck.vim](https://github.com/elkowar/yuck.vim) for editor support.\nIf you're using VSCode, you can get syntax highlighting and formatting from [yuck-vscode](https://marketplace.visualstudio.com/items?itemName=eww-yuck.yuck).\nIt is also recommended to use [parinfer](https://shaunlebron.github.io/parinfer/),\nwhich makes working with S-expressions delightfully easy!\n\nAdditionally, any styles are defined in CSS or SCSS (which is mostly just slightly improved CSS syntax).\nWhile eww supports a significant portion of the CSS you know from the web,\nnot everything is supported, as eww relies on GTK's own CSS engine.\nNotably, some animation features are unsupported,\nas well as most layout-related CSS properties such as flexbox, `float`, absolute position or `width`/`height`.\n\nTo get started, you'll need to create two files: `eww.yuck` and `eww.scss` (or `eww.css`, if you prefer).\nThese files must be placed under `$XDG_CONFIG_HOME/eww` (this is most likely `~/.config/eww`).\n\nNow that those files are created, you can start writing your first widget!\n\n## Creating your first window\n\nFirstly, you will need to create a top-level window. Here, you configure things such as the name, position, geometry, and content of your window.\n\nLet's look at an example window definition:\n\n```lisp\n(defwindow example\n           :monitor 0\n           :geometry (geometry :x \"0%\"\n                               :y \"20px\"\n                               :width \"90%\"\n                               :height \"30px\"\n                               :anchor \"top center\")\n           :stacking \"fg\"\n           :reserve (struts :distance \"40px\" :side \"top\")\n           :windowtype \"dock\"\n           :wm-ignore false\n  \"example content\")\n```\n\nHere, we are defining a window named `example`, which we then define a set of properties for. Additionally, we set the content of the window to be the text `\"example content\"`.\n\nYou can now open your first window by running `eww open example`! Glorious!\n\n### `defwindow`-properties\n\n|   Property | Description                                                  |\n| ---------: | ------------------------------------------------------------ |\n|  `monitor` | Which monitor this window should be displayed on. See below for details.|\n| `geometry` | Geometry of the window.  |\n\n\n**`monitor`-property**\n\nThis field can be:\n\n- the string `<primary>`, in which case eww tries to identify the primary display (which may fail, especially on wayland)\n- an integer, declaring the monitor index\n- the name of the monitor\n- a string containing a JSON-array of monitor matchers, such as: `'[\"<primary>\", \"HDMI-A-1\", \"PHL 345B1C\", 0]'`. Eww will try to find a match in order, allowing you to specify fallbacks.\n\n\n**`geometry`-properties**\n\n| Property          | Description |\n| -----------------:| ------------------------------------------------------------ |\n|          `x`, `y` | Position of the window. Values may be provided in `px` or `%`. Will be relative to `anchor`. |\n| `width`, `height` | Width and height of the window. Values may be provided in `px` or `%`. |\n|          `anchor` | Anchor-point of the window. Either `center` or combinations of `top`, `center`, `bottom` and `left`, `center`, `right`. |\n\n<br/>\nDepending on if you are using X11 or Wayland, some additional properties exist:\n\n#### X11\n\n|     Property | Description                                                  |\n| -----------: | ------------------------------------------------------------ |\n|   `stacking` | Where the window should appear in the stack. Possible values: `fg`, `bg`. |\n|  `wm-ignore` | Whether the window manager should ignore this window. This is useful for dashboard-style widgets that don't need to interact with other windows at all. Note that this makes some of the other properties not have any effect. Either `true` or `false`. |\n|    `reserve` | Specify how the window manager should make space for your window. This is useful for bars, which should not overlap any other windows. |\n| `windowtype` | Specify what type of window this is. This will be used by your window manager to determine how it should handle your window. Possible values: `normal`, `dock`, `toolbar`, `dialog`, `desktop`. Default: `dock` if `reserve` is specified, `normal` otherwise. |\n\n#### Wayland\n\n|    Property | Description                                                                                                                                                            |\n| ----------: |------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n|  `stacking` | Where the window should appear in the stack. Possible values: `fg`, `bg`, `overlay`, `bottom`.                                                                         |\n| `exclusive` | Whether the compositor should reserve space for the window automatically. Either `true` or `false`. If `true` `:anchor` has to include `center`.                       |\n| `focusable` | Whether the window should be able to be focused. This is necessary for any widgets that use the keyboard to work. Possible values: `none`, `exclusive` and `ondemand`. |\n| `namespace` | Set the wayland layersurface namespace eww uses. Accepts a `string` value.                                                                                             |\n\n\n\n## Your first widget\n\nWhile our bar is already looking great, it's a bit boring. Thus, let's add some actual content!\n\n```lisp\n(defwidget greeter [?text name]\n  (box :orientation \"horizontal\"\n       :halign \"center\"\n    text\n    (button :onclick \"notify-send 'Hello' 'Hello, ${name}'\"\n      \"Greet\")))\n```\n\nTo show this, let's replace the text in our window definition with a call to this new widget:\n\n```lisp\n(defwindow example\n           ; ... values omitted\n  (greeter :text \"Say hello!\"\n           :name \"Tim\"))\n```\n\nThere is a lot going on here, so let's step through this.\n\nWe are creating a widget named `greeter`. This widget takes two attributes, called `text` and `name`.\nThe declaration `?text` specifies that the `text`-attribute is optional, and can thus be left out. In that case,\nits value will be the empty string `\"\"`.\nThe `name` attribute _must_ be provided.\n\nNow we declare the body of our widget. We make use of a `box`, which we set a couple attributes of.\n\nWe need this `box`, as a widget definition can only ever contain a single widget - otherwise,\neww would not know if it should align them vertically or horizontally, how it should space them, and so on.\nThus, we wrap multiple children in a `box`.\nThis box then contains a reference to the provided attribute `text`, as well as a button.\nIn that button's `onclick` attribute, we refer to the provided `name` using string-interpolation syntax: `\"${name}\"`.\nThis allows us to easily refer to any variables within strings.\nIn fact, there is a lot more you can do within `${...}` - more on that in the chapter about the [expression language](expression_language.md).\n\nTo then use our widget, we call it just like we would use any other built-in widget and provide the required attributes.\n\nAs you may have noticed, we are using a couple predefined widgets here. These are all listed and explained in the [widgets chapter](widgets.md).\n\n\n### Rendering children in your widgets\nAs your configuration grows, you might want to improve the structure of your config by factoring out functionality into basic reusable widgets.\nEww allows you to create custom wrapper widgets that can themselves take children, just like some of the built-in widgets like `box` or `button` can.\nFor this, use the `children` placeholder:\n```lisp\n(defwidget labeled-container [name]\n  (box :class \"container\"\n    name\n    (children)))\n```\nNow you can use this widget as expected:\n```lisp\n(labeled-container :name \"foo\"\n  (button :onclick \"notify-send hey ho\"\n    \"click me\"))\n```\n\nYou can also create more complex structure by referring to specific children with the `nth`-attribute:\n```lisp\n(defwidget two-boxes []\n  (box\n    (box :class \"first\" (children :nth 0))\n    (box :class \"second\" (children :nth 1))))\n```\n\n## Adding dynamic content\n\nNow that you feel sufficiently greeted by your bar, you may realize that showing data like the time and date might be even more useful than having a button that greets you.\n\nTo implement dynamic content in your widgets, you make use of _variables_.\n\nThese user-defined variables are globally available from all of your widgets. Whenever the variable changes, the value in the widget will update!\n\nThere are four different types of variables: basic, polling, listening, and a set of builtin \"magic\" variables.\n\n**Basic variables (`defvar`)**\n\n```lisp\n(defvar foo \"initial value\")\n```\n\nThis is the simplest type of variable.\nBasic variables don't ever change automatically.\nInstead, you explicitly update them by calling eww like so: `eww update foo=\"new value\"`.\n\nThis is useful if you have values that change very rarely, or may change as a result of some external script you wrote.\nThey may also be useful to have buttons within eww change what is shown within your widget, by setting attributes like `onclick` to run `eww update`.\n\n**Polling variables (`defpoll`)**\n\n```lisp\n(defvar time-visible false)   ; for :run-while property of below variable\n                              ; when this turns true, the polling starts and\n                              ; var gets updated with given interval\n\n(defpoll time :interval \"1s\"\n              :initial \"initial-value\"  ; optional, defaults to poll at startup\n              :run-while time-visible   ; optional, defaults to 'true'\n  `date +%H:%M:%S`)\n```\n\nA polling variable is a variable which runs a provided shell-script repeatedly, in a given interval.\n\nThis may be the most commonly used type of variable.\nThey are useful to access any quickly retrieved value repeatedly,\nand thus are the perfect choice for showing your time, date, as well as other bits of information such as pending package updates, weather, and battery level.\n\nYou can also specify an initial-value. This should prevent eww from waiting for the result of a given command during startup, thus\nmaking the startup time faster.\n\nTo externally update a polling variable, `eww update` can be used like with basic variables to assign a value.\nYou can also call `eww poll` to poll the variable outside of its usual interval, or even while it isn't running at all.\n\n**Listening variables (`deflisten`)**\n\n```lisp\n(deflisten foo :initial \"whatever\"\n  `tail -F /tmp/some_file`)\n```\n\nListening variables might be the most confusing of the bunch.\nA listening variable runs a script once, and reads its output continously.\nWhenever the script outputs a new line, the value will be updated to that new line.\nIn the example given above, the value of `foo` will start out as `\"whatever\"`, and will change whenever a new line is appended to `/tmp/some_file`.\n\nThese are particularly useful when you want to apply changes instantaneously when an operation happens if you have a script\nthat can monitor some value on its own. Volume, brightness, workspaces that get added/removed at runtime,\nmonitoring currently focused desktop/tag, etc. are the most common usecases of this type of variable.\nThese are particularly efficient and should be preferred if possible.\n\nFor example, the command `xprop -spy -root _NET_CURRENT_DESKTOP` writes the currently focused desktop whenever it changes.\nAnother example usecase is monitoring the currently playing song with playerctl: `playerctl --follow metadata --format {{title}}`.\n\n**Built-in \"magic\" variables**\n\nIn addition to defining your own variables, eww provides some values for you to use out of the box.\nThese include values such as your CPU and RAM usage.\nThese mostly contain their data as JSON, which you can then get using the [json access syntax](expression_language.md).\nAll available magic variables are listed [here](magic-vars.md).\n\n## Dynamically generated widgets with `literal`\n\nIn some cases, you want to not only change the text,\nvalue, or color of a widget dynamically, but instead want to generate an entire widget structure dynamically.\nThis is necessary if you want to display lists of things (for example notifications)\nwhere the amount is not necessarily known,\nor if you want to change the widget structure in some other, more complex way.\n\nFor this, you can make use of one of eww's most powerful features: the `literal` widget.\n\n```lisp\n(defvar variable_containing_yuck\n  \"(box (button 'foo') (button 'bar'))\")\n\n; Then, inside your widget, use:\n(literal :content variable_containing_yuck)\n```\n\nHere, you specify the content of your literal by providing it a string (most likely stored in a variable) which contains a single yuck widget tree.\nEww then reads the provided value and renders the resulting widget. Whenever it changes, the widget will be rerendered.\n\nNote that this is not all that efficient. Make sure to only use `literal` when necessary!\n\n## Using window arguments and IDs\n\nIn some cases you may want to use the same window configuration for multiple widgets, e.g. for multiple windows. This is where arguments and ids come in.\n\n### Window ID\n\nFirstly let us start off with ids. An id can be specified in the `open` command\nwith `--id`, by default the id will be set to the name of the window\nconfiguration. These ids allow you to spawn multiple of the same windows. So\nfor example you can do:\n\n```bash\neww open my_bar --screen 0 --id primary\neww open my_bar --screen 1 --id secondary\n```\n\nWhen using `open-many` you can follow the structure below. Again if no id is\ngiven, the id will default to the name of the window configuration.\n\n```bash\neww open-many my_config:primary my_config:secondary\n```\n\nYou may notice with this we didn't set `screen`, this is set through the\n`--arg` system, please see below for more information.\n\n### Window Arguments\n\nHowever this may not be enough and you want to have slight changes for each of\nthese bars, e.g. having a different class for 1080p displays vs 4k or having\nspawning the window in a different size or location. This is where the\narguments come in.\n\nPlease note these arguments are **CONSTANT** and so cannot be update after the\nwindow has been opened.\n\nDefining arguments in a window is the exact same as in a widget so you can\nhave:\n\n```lisp\n(defwindow my_bar [arg1 ?arg2]\n          :geometry (geometry\n                       :x      \"0%\"\n                       :y      \"6px\"\n                       :width  \"100%\"\n                       :height { arg1 == \"small\" ? \"30px\" : \"40px\" }\n                       :anchor \"top center\")\n          :stacking   \"bg\"\n          :windowtype \"dock\"\n          :reserve    (struts :distance \"50px\" :side \"top\")\n    (my_widget :arg2 arg2))\n```\n\nHere we have two arguments, `arg1` and `arg2` (an optional parameter).\n\nOnce we have these parameters, when opening a new window, we must specify them\n(unless they are optional, like `arg2`), but how? Well, we use the `--arg`\noption when running the `open` command:\n\n```bash\neww open my_bar --id primary --arg arg1=some_value --arg arg2=another_value\n```\n\nWith the `open-many` it looks like this:\n\n```bash\n# Please note that `--arg` option must be given after all the windows names\neww open-many my_bar:primary --arg primary:arg1=some_value --arg primary:arg2=another_value\n```\n\nUsing this method you can define `screen`, `anchor`, `pos`, `size` inside the\nargs for each window and it will act like giving `--screen`, `--anchor` etc. in\nthe `open` command.\n\nSo, now you know the basics, I shall introduce you to some of these \"special\"\nparameters, which are set slightly differently. However these can all be\noverridden by the `--arg` option.\n\n- `id` - If `id` is included in the argument list, it will be set to the id\n  specified by `--id` or will be set to the name of the config. This can be\n  used when closing the current window through eww commands.\n- `screen` - If `screen` is specified it will be set to the value given by\n  `--screen`, so you can use this in other widgets to access screen specific\n  information.\n\n### Further insight into args in `open-many`\n\nNow due to the system behind processing the `open-many` `--arg` option you\ndon't have to specify an id for each argument. If you do not, that argument\nwill be applied across all windows e.g.\n\n```bash\neww open-many my_bar:primary my_bar:secondary --arg gui_size=\"small\"\n```\n\nThis will mean the config is the same throughout the bars.\n\nFurthermore if you didn't specify an id for the window, you can still set args\nspecifically for that window - following the idea that the id will be set to\nthe window configuration if not given - by just using the name of the window\nconfiguration e.g.\n\n```bash\neww open-many my_primary_bar --arg my_primary_bar:screen=0\n```\n\n## Generating a list of widgets from JSON using `for`\n\nIf you want to display a list of values, you can use the `for`-Element to fill a container with a list of elements generated from a JSON-array.\n```lisp\n(defvar my-json \"[1, 2, 3]\")\n\n; Then, inside your widget, you can use\n(box\n  (for entry in my-json\n    (button :onclick \"notify-send 'click' 'button ${entry}'\"\n      entry)))\n```\n\nThis can be useful in many situations, for example when generating a workspace list from a JSON representation of your workspaces.\nIn many cases, this can be used instead of `literal`, and should most likely be preferred in those cases.\n\nTo see how to declare and use more advanced data structures, check out the [data structures example](/examples/data-structures/eww.yuck).\n\n## Splitting up your configuration\n\nAs time passes, your configuration might grow larger and larger. Luckily, you can easily split up your configuration into multiple files!\n\nThere are two options to achieve this:\n\n### Using `include`\n\n```lisp\n(include \"./path/to/your/file.yuck\")\n```\n\nA single yuck file may import the contents of any other yuck file. For this, make use of the `include` directive.\n\n### Using a separate eww configuration directory\n\nIf you want to separate different widgets even further, you can create a new eww config folder anywhere else.\nThen, you can tell eww to use that configuration directory by passing _every_ command the `--config /path/to/your/config/dir` flag.\nMake sure to actually include this in all your `eww` calls, including `eww kill`, `eww logs`, etc.\nThis launches a separate instance of the eww daemon that has separate logs and state from your main eww configuration.\n"
  },
  {
    "path": "docs/src/eww.md",
    "content": "# Eww - Widgets for everyone!\n\nEww (ElKowar's Wacky Widgets, pronounced with sufficient amounts of disgust)\nis a widget system made in [Rust](https://www.rust-lang.org/),\nwhich lets you create your own widgets similarly to how you can in AwesomeWM.\nThe key difference: It is independent of your window manager!\n\nConfigured in yuck and themed using CSS, it is easy to customize and provides all the flexibility you need!\n\n\n## How to install Eww\n\n### Prerequisites\n\n* rustc\n* cargo\n\nRather than with your system package manager,\nI **strongly** recommend installing it using  [rustup](https://rustup.rs/).\n\nAdditionally, eww requires some dynamic libraries to be available on your system.\nThe exact names of the packages that provide these may differ depending on your distribution.\nThe following list of package names should work for arch linux:\n\n<details>\n<summary>Packages</summary>\n\n- gtk3 (libgdk-3, libgtk-3)\n- gtk-layer-shell (only on Wayland)\n- pango (libpango)\n- gdk-pixbuf2 (libgdk_pixbuf-2)\n- libdbusmenu-gtk3\n- cairo (libcairo, libcairo-gobject)\n- glib2 (libgio, libglib-2, libgobject-2)\n- gcc-libs (libgcc)\n- glibc\n\n</details>\n\n(Note that you will most likely need the -devel variants of your distro's packages to be able to compile eww.)\n\n### Building\n\nOnce you have the prerequisites ready, you're ready to install and build eww.\n\nFirst clone the repo:\n```bash\ngit clone https://github.com/elkowar/eww\n```\n\n```bash\ncd eww\n```\nThen build:\n```bash\ncargo build --release --no-default-features --features x11\n```\n**NOTE:**\nWhen you're on Wayland, build with:\n```bash\ncargo build --release --no-default-features --features=wayland\n```\n\n### Running eww\nOnce you've built it you can now run it by entering:\n```bash\ncd target/release\n```\nThen make the Eww binary executable:\n```bash\nchmod +x ./eww\n```\nThen to run it, enter:\n```\n./eww daemon\n./eww open <window_name>\n```\n"
  },
  {
    "path": "docs/src/examples.md",
    "content": "## Example Configurations\n\nThese configurations of eww are available in the `examples/` directory of the [repo](https://github.com/elkowar/eww).\n\nAn eww bar configuration:\n![Example bar](https://github.com/elkowar/eww/raw/master/examples/eww-bar/eww-bar.png)\n\nA demo on how to declare and use data structures:\n\n![Data structure example](https://github.com/elkowar/eww/raw/master/examples/data-structures/data-structures-preview.png)\n"
  },
  {
    "path": "docs/src/expression_language.md",
    "content": "# Simple expression language\n\nYuck includes a small expression language that can be used to run several operations on your data.\nThis can be used to show different values depending on certain conditions,\ndo mathematic operations, and even access values within JSON-structures.\n\nThese expressions can be placed anywhere within your configuration inside `{ ... }`,\nas well as within strings, inside string-interpolation blocks (`\"foo ${ ... } bar\"`).\n\n## Example\n\n```lisp\n(box\n  \"Some math: ${12 + foo * 10}\"\n  (button :class {button_active ? \"active\" : \"inactive\"}\n          :onclick \"toggle_thing\"\n    {button_active ? \"disable\" : \"enable\"}))\n```\n\n## Features\n\nSupported currently are the following features:\n- simple mathematical operations (`+`, `-`, `*`, `/`, `%`)\n- comparisons (`==`, `!=`, `>`, `<`, `<=`, `>=`)\n- boolean operations (`||`, `&&`, `!`)\n- regex match operator (`=~`)\n    - Rust regex style, left hand is regex, right hand is string\n    - ex: workspace.name =~ '^special:.+$'\n- elvis operator (`?:`)\n    - if the left side is `\"\"` or a JSON `null`, then returns the right side,\n      otherwise evaluates to the left side.\n- Safe Access operator (`?.`) or (`?.[index]`)\n    - if the left side is an empty string or a JSON `null`, then return `null`. Otherwise,\n      attempt to index. Note that indexing an empty JSON string (`'\"\"'`) is an error.\n    - This can still cause an error to occur if the left hand side exists but is\n      not an object or an array.\n      (`Number` or `String`).\n- conditionals (`condition ? 'value' : 'other value'`)\n- numbers, strings, booleans and variable references (`12`, `'hi'`, `true`, `some_variable`)\n- json access (`object.field`, `array[12]`, `object[\"field\"]`)\n    - for this, the object/array value needs to refer to a variable that contains a valid json string.\n- some function calls:\n    - `round(number, decimal_digits)`: Round a number to the given amount of decimals\n    - `floor(number)`: Round a number down to the nearest integer\n    - `ceil(number)`: Round a number up to the nearest integer\n    - `sin(number)`, `cos(number)`, `tan(number)`, `cot(number)`: Calculate the trigonometric value of a given number in **radians**\n    - `min(a, b)`, `max(a, b)`: Get the smaller or bigger number out of two given numbers\n    - `powi(num, n)`, `powf(num, n)`: Raise number `num` to power `n`. `powi` expects `n` to be of type `i32`\n    - `log(num, n)`: Calculate the base `n` logarithm of `num`. `num`, `n` and return type are `f64`\n    - `degtorad(number)`: Converts a number from degrees to radians\n    - `radtodeg(number)`: Converts a number from radians to degrees\n    - `replace(string, regex, replacement)`: Replace matches of a given regex in a string\n  - `search(string, regex)`: Search for a given regex in a string (returns array)\n  - `matches(string, regex)`: check if a given string matches a given regex (returns bool)\n  - `captures(string, regex)`: Get the captures of a given regex in a string (returns array)\n  - `strlength(value)`: Gets the length of the string\n    - `substring(string, start, length)`: Return a substring of given length starting at the given index\n  - `arraylength(value)`: Gets the length of the array\n  - `objectlength(value)`: Gets the amount of entries in the object\n  - `jq(value, jq_filter_string)`: run a [jq](https://jqlang.github.io/jq/manual/) style command on a json value. (Uses [jaq](https://crates.io/crates/jaq) internally).\n  - `jq(value, jq_filter_string, args)`: Emulate command line flags for jq, see [the docs](https://jqlang.github.io/jq/manual/#invoking-jq) on invoking jq for details. Invalid flags are silently ignored.\n    Currently supported flags:\n    - `\"r\"`: If the result is a string, it won't be formatted as a JSON string. The equivalent jq flag is `--raw-output`.\n  - `get_env(string)`: Gets the specified enviroment variable\n  - `formattime(unix_timestamp, format_str, timezone)`: Gets the time in a given format from UNIX timestamp.\n     Check [chrono's documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) for more\n     information about format string and [chrono-tz's documentation](https://docs.rs/chrono-tz/latest/chrono_tz/enum.Tz.html)\n     for available time zones.\n  - `formattime(unix_timestamp, format_str)`: Gets the time in a given format from UNIX timestamp.\n     Same as other `formattime`, but does not accept timezone. Instead, it uses system's local timezone.\n     Check [chrono's documentation](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) for more\n     information about format string.\n  - `formatbytes(bytes, short, format_mode)`: Display bytes in a human-readable format.\n    Arguments:\n    - `bytes`: `i64` of bytes, supports negative sizes.\n    - `short`: set true for a compact version (default: false)\n    - `format_mode`: set to either to \"iec\" (eg. `1.0 GiB`) or \"si\" (eg. `1.2 GB`) (default: \"iec\")\n"
  },
  {
    "path": "docs/src/magic-vars.md",
    "content": "# Magic variables\n\nThese are variables that are always there, without you having to import them.\n\nThe delay between all the updating variables except `EWW_TIME` is 2s, for `EWW_TIME` it is 1s.\n\n"
  },
  {
    "path": "docs/src/troubleshooting.md",
    "content": "# Troubleshooting\n\nHere you will find help if something doesn't work. If the issue isn't listed here, please [open an issue on the GitHub repo.](https://github.com/elkowar/eww/issues)\n\n## Eww does not compile\n\n1. Make sure that you are compiling eww using a recent version of rust (run `rustup update` to be sure you have the latest version available)\n2. Make sure you have all the necessary dependencies. If there are compile-errors, the compiler will tell you what you're missing.\n\n## Eww does not work on Wayland\n\n1. Make sure you compiled eww with the `--no-default-features --features=wayland` flags.\n2. Make sure that you're not trying to use X11-specific features (these are (hopefully) explicitly specified as such in the documentation).\n\n## My configuration is not loaded correctly\n\n1. Make sure the `eww.yuck` and `eww.(s)css` files are in the correct places.\n2. Sometimes, eww might fail to load your configuration as a result of a configuration error. Make sure your configuration is valid.\n\n## Something isn't styled correctly!\n\nCheck the [GTK-Debugger](working_with_gtk.md#gtk-debugger) to get more insight into what styles GTK is applying to which elements.\n\n## General issues\n\nYou should try the following things before opening an issue or doing more specialized troubleshooting:\n\n-   Kill the eww daemon by running `eww kill` and re-open your window with the `--debug`-flag to get additional log output.\n-   Now you can take a look at the logs by running `eww logs`.\n-   Use `eww state` to see the state of all variables.\n-   Use `eww debug` to see the structure of your widget and other information.\n-   Update to the latest eww version.\n-   Sometimes hot reloading doesn't work. In that case, you can make use of `eww reload` manually.\n\nRemember, if your issue isn't listed here, [open an issue on the GitHub repo](https://github.com/elkowar/eww/issues).\n"
  },
  {
    "path": "docs/src/widgets.md",
    "content": "# Widgets\n\n"
  },
  {
    "path": "docs/src/working_with_gtk.md",
    "content": "# GTK\n\n## Gtk-theming\n\nEww is styled in GTK CSS.\nYou can use `Vanilla CSS` or `SCSS` to make theming even easier. The latter is compiled into CSS for you.\nIf you don't know any way to style something check out the [GTK CSS Overview wiki](https://docs.gtk.org/gtk3/css-overview.html),\nthe [GTK CSS Properties Overview wiki ](https://docs.gtk.org/gtk3/css-properties.html),\nor use the [GTK-Debugger](#gtk-debugger).\n\nIf you have **NO** clue about how to do CSS, check out some online guides or tutorials.\n\nSCSS is _very_ close to CSS, so if you know CSS you'll have no problem learning SCSS.\n\n## GTK-Debugger\n\nThe debugger can be used for **a lot** of things, especially if something doesn't work or isn't styled right.\n\nTo open the GTK debugger, simply run\n\n```bash\neww inspector\n```\n\nIf a style or something similar doesn't work, you can click on the icon in the top left to select the thing that isn't being styled correctly.\n\nThen you can click on the drop down menu in the top right corner and select CSS Nodes. Here you will see everything about styling it, CSS Properties, and how it's structured.\n"
  },
  {
    "path": "docs/theme/book.js",
    "content": "\"use strict\";\n\n// Fix back button cache problem\nwindow.onunload = function () { };\n\n// Global variable, shared between modules\nfunction playground_text(playground) {\n    let code_block = playground.querySelector(\"code\");\n\n    if (window.ace && code_block.classList.contains(\"editable\")) {\n        let editor = window.ace.edit(code_block);\n        return editor.getValue();\n    } else {\n        return code_block.textContent;\n    }\n}\n\n(function codeSnippets() {\n    function fetch_with_timeout(url, options, timeout = 6000) {\n        return Promise.race([\n            fetch(url, options),\n            new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))\n        ]);\n    }\n\n    var playgrounds = Array.from(document.querySelectorAll(\".playground\"));\n    if (playgrounds.length > 0) {\n        fetch_with_timeout(\"https://play.rust-lang.org/meta/crates\", {\n            headers: {\n                'Content-Type': \"application/json\",\n            },\n            method: 'POST',\n            mode: 'cors',\n        })\n        .then(response => response.json())\n        .then(response => {\n            // get list of crates available in the rust playground\n            let playground_crates = response.crates.map(item => item[\"id\"]);\n            playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));\n        });\n    }\n\n    function handle_crate_list_update(playground_block, playground_crates) {\n        // update the play buttons after receiving the response\n        update_play_button(playground_block, playground_crates);\n\n        // and install on change listener to dynamically update ACE editors\n        if (window.ace) {\n            let code_block = playground_block.querySelector(\"code\");\n            if (code_block.classList.contains(\"editable\")) {\n                let editor = window.ace.edit(code_block);\n                editor.addEventListener(\"change\", function (e) {\n                    update_play_button(playground_block, playground_crates);\n                });\n                // add Ctrl-Enter command to execute rust code\n                editor.commands.addCommand({\n                    name: \"run\",\n                    bindKey: {\n                        win: \"Ctrl-Enter\",\n                        mac: \"Ctrl-Enter\"\n                    },\n                    exec: _editor => run_rust_code(playground_block)\n                });\n            }\n        }\n    }\n\n    // updates the visibility of play button based on `no_run` class and\n    // used crates vs ones available on http://play.rust-lang.org\n    function update_play_button(pre_block, playground_crates) {\n        var play_button = pre_block.querySelector(\".play-button\");\n\n        // skip if code is `no_run`\n        if (pre_block.querySelector('code').classList.contains(\"no_run\")) {\n            play_button.classList.add(\"hidden\");\n            return;\n        }\n\n        // get list of `extern crate`'s from snippet\n        var txt = playground_text(pre_block);\n        var re = /extern\\s+crate\\s+([a-zA-Z_0-9]+)\\s*;/g;\n        var snippet_crates = [];\n        var item;\n        while (item = re.exec(txt)) {\n            snippet_crates.push(item[1]);\n        }\n\n        // check if all used crates are available on play.rust-lang.org\n        var all_available = snippet_crates.every(function (elem) {\n            return playground_crates.indexOf(elem) > -1;\n        });\n\n        if (all_available) {\n            play_button.classList.remove(\"hidden\");\n        } else {\n            play_button.classList.add(\"hidden\");\n        }\n    }\n\n    function run_rust_code(code_block) {\n        var result_block = code_block.querySelector(\".result\");\n        if (!result_block) {\n            result_block = document.createElement('code');\n            result_block.className = 'result hljs language-bash';\n\n            code_block.append(result_block);\n        }\n\n        let text = playground_text(code_block);\n        let classes = code_block.querySelector('code').classList;\n        let has_2018 = classes.contains(\"edition2018\");\n        let edition = has_2018 ? \"2018\" : \"2015\";\n\n        var params = {\n            version: \"stable\",\n            optimize: \"0\",\n            code: text,\n            edition: edition\n        };\n\n        if (text.indexOf(\"#![feature\") !== -1) {\n            params.version = \"nightly\";\n        }\n\n        result_block.innerText = \"Running...\";\n\n        fetch_with_timeout(\"https://play.rust-lang.org/evaluate.json\", {\n            headers: {\n                'Content-Type': \"application/json\",\n            },\n            method: 'POST',\n            mode: 'cors',\n            body: JSON.stringify(params)\n        })\n        .then(response => response.json())\n        .then(response => result_block.innerText = response.result)\n        .catch(error => result_block.innerText = \"Playground Communication: \" + error.message);\n    }\n\n    // Syntax highlighting Configuration\n    hljs.configure({\n        tabReplace: '    ', // 4 spaces\n        languages: [],      // Languages used for auto-detection\n    });\n\n    let code_nodes = Array\n        .from(document.querySelectorAll('code'))\n        // Don't highlight `inline code` blocks in headers.\n        .filter(function (node) {return !node.parentElement.classList.contains(\"header\"); });\n\n    if (window.ace) {\n        // language-rust class needs to be removed for editable\n        // blocks or highlightjs will capture events\n        Array\n            .from(document.querySelectorAll('code.editable'))\n            .forEach(function (block) { block.classList.remove('language-rust'); });\n\n        Array\n            .from(document.querySelectorAll('code:not(.editable)'))\n            .forEach(function (block) { hljs.highlightBlock(block); });\n    } else {\n        code_nodes.forEach(function (block) { hljs.highlightBlock(block); });\n    }\n\n    // Adding the hljs class gives code blocks the color css\n    // even if highlighting doesn't apply\n    code_nodes.forEach(function (block) { block.classList.add('hljs'); });\n\n    Array.from(document.querySelectorAll(\"code.language-rust\")).forEach(function (block) {\n\n        var lines = Array.from(block.querySelectorAll('.boring'));\n        // If no lines were hidden, return\n        if (!lines.length) { return; }\n        block.classList.add(\"hide-boring\");\n\n        var buttons = document.createElement('div');\n        buttons.className = 'buttons';\n        buttons.innerHTML = \"<button class=\\\"fa fa-eye\\\" title=\\\"Show hidden lines\\\" aria-label=\\\"Show hidden lines\\\"></button>\";\n\n        // add expand button\n        var pre_block = block.parentNode;\n        pre_block.insertBefore(buttons, pre_block.firstChild);\n\n        pre_block.querySelector('.buttons').addEventListener('click', function (e) {\n            if (e.target.classList.contains('fa-eye')) {\n                e.target.classList.remove('fa-eye');\n                e.target.classList.add('fa-eye-slash');\n                e.target.title = 'Hide lines';\n                e.target.setAttribute('aria-label', e.target.title);\n\n                block.classList.remove('hide-boring');\n            } else if (e.target.classList.contains('fa-eye-slash')) {\n                e.target.classList.remove('fa-eye-slash');\n                e.target.classList.add('fa-eye');\n                e.target.title = 'Show hidden lines';\n                e.target.setAttribute('aria-label', e.target.title);\n\n                block.classList.add('hide-boring');\n            }\n        });\n    });\n\n    if (window.playground_copyable) {\n        Array.from(document.querySelectorAll('pre code')).forEach(function (block) {\n            var pre_block = block.parentNode;\n            if (!pre_block.classList.contains('playground')) {\n                var buttons = pre_block.querySelector(\".buttons\");\n                if (!buttons) {\n                    buttons = document.createElement('div');\n                    buttons.className = 'buttons';\n                    pre_block.insertBefore(buttons, pre_block.firstChild);\n                }\n\n                var clipButton = document.createElement('button');\n                clipButton.className = 'fa fa-copy clip-button';\n                clipButton.title = 'Copy to clipboard';\n                clipButton.setAttribute('aria-label', clipButton.title);\n                clipButton.innerHTML = '<i class=\\\"tooltiptext\\\"></i>';\n\n                buttons.insertBefore(clipButton, buttons.firstChild);\n            }\n        });\n    }\n\n    // Process playground code blocks\n    Array.from(document.querySelectorAll(\".playground\")).forEach(function (pre_block) {\n        // Add play button\n        var buttons = pre_block.querySelector(\".buttons\");\n        if (!buttons) {\n            buttons = document.createElement('div');\n            buttons.className = 'buttons';\n            pre_block.insertBefore(buttons, pre_block.firstChild);\n        }\n\n        var runCodeButton = document.createElement('button');\n        runCodeButton.className = 'fa fa-play play-button';\n        runCodeButton.hidden = true;\n        runCodeButton.title = 'Run this code';\n        runCodeButton.setAttribute('aria-label', runCodeButton.title);\n\n        buttons.insertBefore(runCodeButton, buttons.firstChild);\n        runCodeButton.addEventListener('click', function (e) {\n            run_rust_code(pre_block);\n        });\n\n        if (window.playground_copyable) {\n            var copyCodeClipboardButton = document.createElement('button');\n            copyCodeClipboardButton.className = 'fa fa-copy clip-button';\n            copyCodeClipboardButton.innerHTML = '<i class=\"tooltiptext\"></i>';\n            copyCodeClipboardButton.title = 'Copy to clipboard';\n            copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);\n\n            buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);\n        }\n\n        let code_block = pre_block.querySelector(\"code\");\n        if (window.ace && code_block.classList.contains(\"editable\")) {\n            var undoChangesButton = document.createElement('button');\n            undoChangesButton.className = 'fa fa-history reset-button';\n            undoChangesButton.title = 'Undo changes';\n            undoChangesButton.setAttribute('aria-label', undoChangesButton.title);\n\n            buttons.insertBefore(undoChangesButton, buttons.firstChild);\n\n            undoChangesButton.addEventListener('click', function () {\n                let editor = window.ace.edit(code_block);\n                editor.setValue(editor.originalCode);\n                editor.clearSelection();\n            });\n        }\n    });\n})();\n\n(function themes() {\n    var html = document.querySelector('html');\n    var themeToggleButton = document.getElementById('theme-toggle');\n    var themePopup = document.getElementById('theme-list');\n    var themeColorMetaTag = document.querySelector('meta[name=\"theme-color\"]');\n    var stylesheets = {\n        ayuHighlight: document.querySelector(\"[href$='ayu-highlight.css']\"),\n        tomorrowNight: document.querySelector(\"[href$='tomorrow-night.css']\"),\n        highlight: document.querySelector(\"[href$='highlight.css']\"),\n    };\n\n    function showThemes() {\n        themePopup.style.display = 'block';\n        themeToggleButton.setAttribute('aria-expanded', true);\n        themePopup.querySelector(\"button#\" + get_theme()).focus();\n    }\n\n    function hideThemes() {\n        themePopup.style.display = 'none';\n        themeToggleButton.setAttribute('aria-expanded', false);\n        themeToggleButton.focus();\n    }\n\n    function get_theme() {\n        var theme;\n        try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }\n        if (theme === null || theme === undefined) {\n            return default_theme;\n        } else {\n            return theme;\n        }\n    }\n\n    function set_theme(theme, store = true) {\n        let ace_theme;\n\n        if (theme == 'coal' || theme == 'navy') {\n            stylesheets.ayuHighlight.disabled = true;\n            stylesheets.tomorrowNight.disabled = false;\n            stylesheets.highlight.disabled = true;\n\n            ace_theme = \"ace/theme/tomorrow_night\";\n        } else if (theme == 'ayu') {\n            stylesheets.ayuHighlight.disabled = false;\n            stylesheets.tomorrowNight.disabled = true;\n            stylesheets.highlight.disabled = true;\n            ace_theme = \"ace/theme/tomorrow_night\";\n        } else {\n            stylesheets.ayuHighlight.disabled = true;\n            stylesheets.tomorrowNight.disabled = true;\n            stylesheets.highlight.disabled = false;\n            ace_theme = \"ace/theme/dawn\";\n        }\n\n        setTimeout(function () {\n            themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;\n        }, 1);\n\n        if (window.ace && window.editors) {\n            window.editors.forEach(function (editor) {\n                editor.setTheme(ace_theme);\n            });\n        }\n\n        var previousTheme = get_theme();\n\n        if (store) {\n            try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }\n        }\n\n        html.classList.remove(previousTheme);\n        html.classList.add(theme);\n    }\n\n    // Set theme\n    var theme = get_theme();\n\n    set_theme(theme, false);\n\n    themeToggleButton.addEventListener('click', function () {\n        if (themePopup.style.display === 'block') {\n            hideThemes();\n        } else {\n            showThemes();\n        }\n    });\n\n    themePopup.addEventListener('click', function (e) {\n        var theme = e.target.id || e.target.parentElement.id;\n        set_theme(theme);\n    });\n\n    themePopup.addEventListener('focusout', function(e) {\n        // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)\n        if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {\n            hideThemes();\n        }\n    });\n\n    // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628\n    document.addEventListener('click', function(e) {\n        if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {\n            hideThemes();\n        }\n    });\n\n    document.addEventListener('keydown', function (e) {\n        if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }\n        if (!themePopup.contains(e.target)) { return; }\n\n        switch (e.key) {\n            case 'Escape':\n                e.preventDefault();\n                hideThemes();\n                break;\n            case 'ArrowUp':\n                e.preventDefault();\n                var li = document.activeElement.parentElement;\n                if (li && li.previousElementSibling) {\n                    li.previousElementSibling.querySelector('button').focus();\n                }\n                break;\n            case 'ArrowDown':\n                e.preventDefault();\n                var li = document.activeElement.parentElement;\n                if (li && li.nextElementSibling) {\n                    li.nextElementSibling.querySelector('button').focus();\n                }\n                break;\n            case 'Home':\n                e.preventDefault();\n                themePopup.querySelector('li:first-child button').focus();\n                break;\n            case 'End':\n                e.preventDefault();\n                themePopup.querySelector('li:last-child button').focus();\n                break;\n        }\n    });\n})();\n\n(function sidebar() {\n    var html = document.querySelector(\"html\");\n    var sidebar = document.getElementById(\"sidebar\");\n    var sidebarLinks = document.querySelectorAll('#sidebar a');\n    var sidebarToggleButton = document.getElementById(\"sidebar-toggle\");\n    var sidebarResizeHandle = document.getElementById(\"sidebar-resize-handle\");\n    var firstContact = null;\n\n    function showSidebar() {\n        html.classList.remove('sidebar-hidden')\n        html.classList.add('sidebar-visible');\n        Array.from(sidebarLinks).forEach(function (link) {\n            link.setAttribute('tabIndex', 0);\n        });\n        sidebarToggleButton.setAttribute('aria-expanded', true);\n        sidebar.setAttribute('aria-hidden', false);\n        try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }\n    }\n\n\n    var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');\n\n    function toggleSection(ev) {\n        ev.currentTarget.parentElement.classList.toggle('expanded');\n    }\n\n    Array.from(sidebarAnchorToggles).forEach(function (el) {\n        el.addEventListener('click', toggleSection);\n    });\n\n    function hideSidebar() {\n        html.classList.remove('sidebar-visible')\n        html.classList.add('sidebar-hidden');\n        Array.from(sidebarLinks).forEach(function (link) {\n            link.setAttribute('tabIndex', -1);\n        });\n        sidebarToggleButton.setAttribute('aria-expanded', false);\n        sidebar.setAttribute('aria-hidden', true);\n        try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }\n    }\n\n    // Toggle sidebar\n    sidebarToggleButton.addEventListener('click', function sidebarToggle() {\n        if (html.classList.contains(\"sidebar-hidden\")) {\n            var current_width = parseInt(\n                document.documentElement.style.getPropertyValue('--sidebar-width'), 10);\n            if (current_width < 150) {\n                document.documentElement.style.setProperty('--sidebar-width', '150px');\n            }\n            showSidebar();\n        } else if (html.classList.contains(\"sidebar-visible\")) {\n            hideSidebar();\n        } else {\n            if (getComputedStyle(sidebar)['transform'] === 'none') {\n                hideSidebar();\n            } else {\n                showSidebar();\n            }\n        }\n    });\n\n    sidebarResizeHandle.addEventListener('mousedown', initResize, false);\n\n    function initResize(e) {\n        window.addEventListener('mousemove', resize, false);\n        window.addEventListener('mouseup', stopResize, false);\n        html.classList.add('sidebar-resizing');\n    }\n    function resize(e) {\n        var pos = (e.clientX - sidebar.offsetLeft);\n        if (pos < 20) {\n            hideSidebar();\n        } else {\n            if (html.classList.contains(\"sidebar-hidden\")) {\n                showSidebar();\n            }\n            pos = Math.min(pos, window.innerWidth - 100);\n            document.documentElement.style.setProperty('--sidebar-width', pos + 'px');\n        }\n    }\n    //on mouseup remove windows functions mousemove & mouseup\n    function stopResize(e) {\n        html.classList.remove('sidebar-resizing');\n        window.removeEventListener('mousemove', resize, false);\n        window.removeEventListener('mouseup', stopResize, false);\n    }\n\n    document.addEventListener('touchstart', function (e) {\n        firstContact = {\n            x: e.touches[0].clientX,\n            time: Date.now()\n        };\n    }, { passive: true });\n\n    document.addEventListener('touchmove', function (e) {\n        if (!firstContact)\n            return;\n\n        var curX = e.touches[0].clientX;\n        var xDiff = curX - firstContact.x,\n            tDiff = Date.now() - firstContact.time;\n\n        if (tDiff < 250 && Math.abs(xDiff) >= 150) {\n            if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))\n                showSidebar();\n            else if (xDiff < 0 && curX < 300)\n                hideSidebar();\n\n            firstContact = null;\n        }\n    }, { passive: true });\n\n    // Scroll sidebar to current active section\n    var activeSection = document.getElementById(\"sidebar\").querySelector(\".active\");\n    if (activeSection) {\n        // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView\n        activeSection.scrollIntoView({ block: 'center' });\n    }\n})();\n\n(function chapterNavigation() {\n    document.addEventListener('keydown', function (e) {\n        if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }\n        if (window.search && window.search.hasFocus()) { return; }\n\n        switch (e.key) {\n            case 'ArrowRight':\n                e.preventDefault();\n                var nextButton = document.querySelector('.nav-chapters.next');\n                if (nextButton) {\n                    window.location.href = nextButton.href;\n                }\n                break;\n            case 'ArrowLeft':\n                e.preventDefault();\n                var previousButton = document.querySelector('.nav-chapters.previous');\n                if (previousButton) {\n                    window.location.href = previousButton.href;\n                }\n                break;\n        }\n    });\n})();\n\n(function clipboard() {\n    var clipButtons = document.querySelectorAll('.clip-button');\n\n    function hideTooltip(elem) {\n        elem.firstChild.innerText = \"\";\n        elem.className = 'fa fa-copy clip-button';\n    }\n\n    function showTooltip(elem, msg) {\n        elem.firstChild.innerText = msg;\n        elem.className = 'fa fa-copy tooltipped';\n    }\n\n    var clipboardSnippets = new ClipboardJS('.clip-button', {\n        text: function (trigger) {\n            hideTooltip(trigger);\n            let playground = trigger.closest(\"pre\");\n            return playground_text(playground);\n        }\n    });\n\n    Array.from(clipButtons).forEach(function (clipButton) {\n        clipButton.addEventListener('mouseout', function (e) {\n            hideTooltip(e.currentTarget);\n        });\n    });\n\n    clipboardSnippets.on('success', function (e) {\n        e.clearSelection();\n        showTooltip(e.trigger, \"Copied!\");\n    });\n\n    clipboardSnippets.on('error', function (e) {\n        showTooltip(e.trigger, \"Clipboard error!\");\n    });\n})();\n\n(function scrollToTop () {\n    var menuTitle = document.querySelector('.menu-title');\n\n    menuTitle.addEventListener('click', function () {\n        document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });\n    });\n})();\n\n(function controllMenu() {\n    var menu = document.getElementById('menu-bar');\n\n    (function controllPosition() {\n        var scrollTop = document.scrollingElement.scrollTop;\n        var prevScrollTop = scrollTop;\n        var minMenuY = -menu.clientHeight - 50;\n        // When the script loads, the page can be at any scroll (e.g. if you reforesh it).\n        menu.style.top = scrollTop + 'px';\n        // Same as parseInt(menu.style.top.slice(0, -2), but faster\n        var topCache = menu.style.top.slice(0, -2);\n        menu.classList.remove('sticky');\n        var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster\n        document.addEventListener('scroll', function () {\n            scrollTop = Math.max(document.scrollingElement.scrollTop, 0);\n            // `null` means that it doesn't need to be updated\n            var nextSticky = null;\n            var nextTop = null;\n            var scrollDown = scrollTop > prevScrollTop;\n            var menuPosAbsoluteY = topCache - scrollTop;\n            if (scrollDown) {\n                nextSticky = false;\n                if (menuPosAbsoluteY > 0) {\n                    nextTop = prevScrollTop;\n                }\n            } else {\n                if (menuPosAbsoluteY > 0) {\n                    nextSticky = true;\n                } else if (menuPosAbsoluteY < minMenuY) {\n                    nextTop = prevScrollTop + minMenuY;\n                }\n            }\n            if (nextSticky === true && stickyCache === false) {\n                menu.classList.add('sticky');\n                stickyCache = true;\n            } else if (nextSticky === false && stickyCache === true) {\n                menu.classList.remove('sticky');\n                stickyCache = false;\n            }\n            if (nextTop !== null) {\n                menu.style.top = nextTop + 'px';\n                topCache = nextTop;\n            }\n            prevScrollTop = scrollTop;\n        }, { passive: true });\n    })();\n    (function controllBorder() {\n        menu.classList.remove('bordered');\n        document.addEventListener('scroll', function () {\n            if (menu.offsetTop === 0) {\n                menu.classList.remove('bordered');\n            } else {\n                menu.classList.add('bordered');\n            }\n        }, { passive: true });\n    })();\n})();\n"
  },
  {
    "path": "docs/theme/css/chrome.css",
    "content": "/* CSS for UI elements (a.k.a. chrome) */\n\n@import 'variables.css';\n\n::-webkit-scrollbar {\n    background: var(--bg);\n}\n::-webkit-scrollbar-thumb {\n    background: var(--scrollbar);\n}\nhtml {\n    scrollbar-color: var(--scrollbar) var(--bg);\n}\n#searchresults a,\n.content a:link,\na:visited,\na > .hljs {\n    color: var(--links);\n}\n\n/* Menu Bar */\n\n#menu-bar,\n#menu-bar-hover-placeholder {\n    z-index: 101;\n    margin: auto calc(0px - var(--page-padding));\n}\n#menu-bar {\n    position: relative;\n    display: flex;\n    flex-wrap: wrap;\n    background-color: var(--bg);\n    border-bottom-color: var(--bg);\n    border-bottom-width: 1px;\n    border-bottom-style: solid;\n}\n#menu-bar.sticky,\n.js #menu-bar-hover-placeholder:hover + #menu-bar,\n.js #menu-bar:hover,\n.js.sidebar-visible #menu-bar {\n    position: -webkit-sticky;\n    position: sticky;\n    top: 0 !important;\n}\n#menu-bar-hover-placeholder {\n    position: sticky;\n    position: -webkit-sticky;\n    top: 0;\n    height: var(--menu-bar-height);\n}\n#menu-bar.bordered {\n    border-bottom-color: var(--table-border-color);\n}\n#menu-bar i, #menu-bar .icon-button {\n    position: relative;\n    padding: 0 8px;\n    z-index: 10;\n    line-height: var(--menu-bar-height);\n    cursor: pointer;\n    transition: color 0.5s;\n}\n@media only screen and (max-width: 420px) {\n    #menu-bar i, #menu-bar .icon-button {\n        padding: 0 5px;\n    }\n}\n\n.icon-button {\n    border: none;\n    background: none;\n    padding: 0;\n    color: inherit;\n}\n.icon-button i {\n    margin: 0;\n}\n\n.right-buttons {\n    margin: 0 15px;\n}\n.right-buttons a {\n    text-decoration: none;\n}\n\n.left-buttons {\n    display: flex;\n    margin: 0 5px;\n}\n.no-js .left-buttons {\n    display: none;\n}\n\n.menu-title {\n    display: inline-block;\n    font-weight: 200;\n    font-size: 2.4rem;\n    line-height: var(--menu-bar-height);\n    text-align: center;\n    margin: 0;\n    flex: 1;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n}\n.js .menu-title {\n    cursor: pointer;\n}\n\n.menu-bar,\n.menu-bar:visited,\n.nav-chapters,\n.nav-chapters:visited,\n.mobile-nav-chapters,\n.mobile-nav-chapters:visited,\n.menu-bar .icon-button,\n.menu-bar a i {\n    color: var(--icons);\n}\n\n.menu-bar i:hover,\n.menu-bar .icon-button:hover,\n.nav-chapters:hover,\n.mobile-nav-chapters i:hover {\n    color: var(--icons-hover);\n}\n\n/* Nav Icons */\n\n.nav-chapters {\n    font-size: 2.5em;\n    text-align: center;\n    text-decoration: none;\n\n    position: fixed;\n    top: 0;\n    bottom: 0;\n    margin: 0;\n    max-width: 150px;\n    min-width: 90px;\n\n    display: flex;\n    justify-content: center;\n    align-content: center;\n    flex-direction: column;\n\n    transition: color 0.5s, background-color 0.5s;\n}\n\n.nav-chapters:hover {\n    text-decoration: none;\n    background-color: var(--theme-hover);\n    transition: background-color 0.15s, color 0.15s;\n}\n\n.nav-wrapper {\n    margin-top: 50px;\n    display: none;\n}\n\n.mobile-nav-chapters {\n    font-size: 2.5em;\n    text-align: center;\n    text-decoration: none;\n    width: 90px;\n    border-radius: 5px;\n    background-color: var(--sidebar-bg);\n}\n\n.previous {\n    float: left;\n}\n\n.next {\n    float: right;\n    right: var(--page-padding);\n}\n\n@media only screen and (max-width: 1080px) {\n    .nav-wide-wrapper { display: none; }\n    .nav-wrapper { display: block; }\n}\n\n@media only screen and (max-width: 1380px) {\n    .sidebar-visible .nav-wide-wrapper { display: none; }\n    .sidebar-visible .nav-wrapper { display: block; }\n}\n\n/* Inline code */\n\n:not(pre) > .hljs {\n    display: inline;\n    padding: 0.1em 0.3em;\n    border-radius: 3px;\n}\n\n:not(pre):not(a) > .hljs {\n    color: var(--inline-code-color);\n    overflow-x: initial;\n}\n\na:hover > .hljs {\n    text-decoration: underline;\n}\n\npre {\n    position: relative;\n}\npre > .buttons {\n    position: absolute;\n    z-index: 100;\n    right: 5px;\n    top: 5px;\n\n    color: var(--sidebar-fg);\n    cursor: pointer;\n}\npre > .buttons :hover {\n    color: var(--sidebar-active);\n}\npre > .buttons i {\n    margin-left: 8px;\n}\npre > .buttons button {\n    color: inherit;\n    background: transparent;\n    border: none;\n    cursor: inherit;\n}\npre > .result {\n    margin-top: 10px;\n}\n\n/* Search */\n\n#searchresults a {\n    text-decoration: none;\n}\n\nmark {\n    border-radius: 2px;\n    padding: 0 3px 1px 3px;\n    margin: 0 -3px -1px -3px;\n    background-color: var(--search-mark-bg);\n    transition: background-color 300ms linear;\n    cursor: pointer;\n}\n\nmark.fade-out {\n    background-color: rgba(0,0,0,0) !important;\n    cursor: auto;\n}\n\n.searchbar-outer {\n    margin-left: auto;\n    margin-right: auto;\n    max-width: var(--content-max-width);\n}\n\n#searchbar {\n    width: 100%;\n    margin: 5px auto 0px auto;\n    padding: 10px 16px;\n    transition: box-shadow 300ms ease-in-out;\n    border: 1px solid var(--searchbar-border-color);\n    border-radius: 3px;\n    background-color: var(--searchbar-bg);\n    color: var(--searchbar-fg);\n}\n#searchbar:focus,\n#searchbar.active {\n    box-shadow: 0 0 3px var(--searchbar-shadow-color);\n}\n\n.searchresults-header {\n    font-weight: bold;\n    font-size: 1em;\n    padding: 18px 0 0 5px;\n    color: var(--searchresults-header-fg);\n}\n\n.searchresults-outer {\n    margin-left: auto;\n    margin-right: auto;\n    max-width: var(--content-max-width);\n    border-bottom: 1px dashed var(--searchresults-border-color);\n}\n\nul#searchresults {\n    list-style: none;\n    padding-left: 20px;\n}\nul#searchresults li {\n    margin: 10px 0px;\n    padding: 2px;\n    border-radius: 2px;\n}\nul#searchresults li.focus {\n    background-color: var(--searchresults-li-bg);\n}\nul#searchresults span.teaser {\n    display: block;\n    clear: both;\n    margin: 5px 0 0 20px;\n    font-size: 0.8em;\n}\nul#searchresults span.teaser em {\n    font-weight: bold;\n    font-style: normal;\n}\n\n/* Sidebar */\n\n.sidebar {\n    position: fixed;\n    left: 0;\n    top: 0;\n    bottom: 0;\n    width: var(--sidebar-width);\n    font-size: 0.875em;\n    box-sizing: border-box;\n    -webkit-overflow-scrolling: touch;\n    overscroll-behavior-y: contain;\n    background-color: var(--sidebar-bg);\n    color: var(--sidebar-fg);\n}\n.sidebar-resizing {\n    -moz-user-select: none;\n    -webkit-user-select: none;\n    -ms-user-select: none;\n    user-select: none;\n}\n.js:not(.sidebar-resizing) .sidebar {\n    transition: transform 0.3s; /* Animation: slide away */\n}\n.sidebar code {\n    line-height: 2em;\n}\n.sidebar .sidebar-scrollbox {\n    overflow-y: auto;\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    right: 0;\n    padding: 10px 10px;\n}\n.sidebar .sidebar-resize-handle {\n    position: absolute;\n    cursor: col-resize;\n    width: 0;\n    right: 0;\n    top: 0;\n    bottom: 0;\n}\n.js .sidebar .sidebar-resize-handle {\n    cursor: col-resize;\n    width: 5px;\n}\n.sidebar-hidden .sidebar {\n    transform: translateX(calc(0px - var(--sidebar-width)));\n}\n.sidebar::-webkit-scrollbar {\n    background: var(--sidebar-bg);\n}\n.sidebar::-webkit-scrollbar-thumb {\n    background: var(--scrollbar);\n}\n\n.sidebar-visible .page-wrapper {\n    transform: translateX(var(--sidebar-width));\n}\n@media only screen and (min-width: 620px) {\n    .sidebar-visible .page-wrapper {\n        transform: none;\n        margin-left: var(--sidebar-width);\n    }\n}\n\n.chapter {\n    list-style: none outside none;\n    padding-left: 0;\n    line-height: 2.2em;\n}\n\n.chapter ol {\n    width: 100%;\n}\n\n.chapter li {\n    display: flex;\n    color: var(--sidebar-non-existant);\n}\n.chapter li a {\n    display: block;\n    padding: 0;\n    text-decoration: none;\n    color: var(--sidebar-fg);\n}\n\n.chapter li a:hover {\n    color: var(--sidebar-active);\n}\n\n.chapter li a.active {\n    color: var(--sidebar-active);\n}\n\n.chapter li > a.toggle {\n    cursor: pointer;\n    display: block;\n    margin-left: auto;\n    padding: 0 10px;\n    user-select: none;\n    opacity: 0.68;\n}\n\n.chapter li > a.toggle div {\n    transition: transform 0.5s;\n}\n\n/* collapse the section */\n.chapter li:not(.expanded) + li > ol {\n    display: none;\n}\n\n.chapter li.chapter-item {\n    line-height: 1.5em;\n    margin-top: 0.6em;\n}\n\n.chapter li.expanded > a.toggle div {\n    transform: rotate(90deg);\n}\n\n.spacer {\n    width: 100%;\n    height: 3px;\n    margin: 5px 0px;\n}\n.chapter .spacer {\n    background-color: var(--sidebar-spacer);\n}\n\n@media (-moz-touch-enabled: 1), (pointer: coarse) {\n    .chapter li a { padding: 5px 0; }\n    .spacer { margin: 10px 0; }\n}\n\n.section {\n    list-style: none outside none;\n    padding-left: 20px;\n    line-height: 1.9em;\n}\n\n/* Theme Menu Popup */\n\n.theme-popup {\n    position: absolute;\n    left: 10px;\n    top: var(--menu-bar-height);\n    z-index: 1000;\n    border-radius: 4px;\n    font-size: 0.7em;\n    color: var(--fg);\n    background: var(--theme-popup-bg);\n    border: 1px solid var(--theme-popup-border);\n    margin: 0;\n    padding: 0;\n    list-style: none;\n    display: none;\n}\n.theme-popup .default {\n    color: var(--icons);\n}\n.theme-popup .theme {\n    width: 100%;\n    border: 0;\n    margin: 0;\n    padding: 2px 10px;\n    line-height: 25px;\n    white-space: nowrap;\n    text-align: left;\n    cursor: pointer;\n    color: inherit;\n    background: inherit;\n    font-size: inherit;\n}\n.theme-popup .theme:hover {\n    background-color: var(--theme-hover);\n}\n.theme-popup .theme:hover:first-child,\n.theme-popup .theme:hover:last-child {\n    border-top-left-radius: inherit;\n    border-top-right-radius: inherit;\n}\n"
  },
  {
    "path": "docs/theme/css/general.css",
    "content": "/* Base styles and content styles */\n\n@import 'variables.css';\n\n:root {\n    /* Browser default font-size is 16px, this way 1 rem = 10px */\n    font-size: 62.5%;\n}\n\nhtml {\n    font-family: \"Open Sans\", sans-serif;\n    color: var(--fg);\n    background-color: var(--bg);\n    text-size-adjust: none;\n}\n\nbody {\n    margin: 0;\n    font-size: 1.6rem;\n    overflow-x: hidden;\n}\n\ncode {\n    font-family: \"Source Code Pro\", Consolas, \"Ubuntu Mono\", Menlo, \"DejaVu Sans Mono\", monospace, monospace !important;\n    font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */\n}\n\n/* Don't change font size in headers. */\nh1 code, h2 code, h3 code, h4 code, h5 code, h6 code {\n    font-size: unset;\n}\n\n.left { float: left; }\n.right { float: right; }\n.boring { opacity: 0.6; }\n.hide-boring .boring { display: none; }\n.hidden { display: none !important; }\n\nh2, h3 { margin-top: 2.5em; }\nh4, h5 { margin-top: 2em; }\n\n.header + .header h3,\n.header + .header h4,\n.header + .header h5 {\n    margin-top: 1em;\n}\n\nh1:target::before,\nh2:target::before,\nh3:target::before,\nh4:target::before,\nh5:target::before,\nh6:target::before {\n    display: inline-block;\n    content: \"»\";\n    margin-left: -30px;\n    width: 30px;\n}\n\n/* This is broken on Safari as of version 14, but is fixed\n   in Safari Technology Preview 117 which I think will be Safari 14.2.\n   https://bugs.webkit.org/show_bug.cgi?id=218076\n*/\n:target {\n    scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);\n}\n\n.page {\n    outline: 0;\n    padding: 0 var(--page-padding);\n    margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */\n}\n.page-wrapper {\n    box-sizing: border-box;\n}\n.js:not(.sidebar-resizing) .page-wrapper {\n    transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */\n}\n\n.content {\n    overflow-y: auto;\n    padding: 0 15px;\n    padding-bottom: 50px;\n}\n.content main {\n    margin-left: auto;\n    margin-right: auto;\n    max-width: var(--content-max-width);\n}\n.content p { line-height: 1.45em; }\n.content ol { line-height: 1.45em; }\n.content ul { line-height: 1.45em; }\n.content a { text-decoration: none; }\n.content a:hover { text-decoration: underline; }\n.content img, .content video { max-width: 100%; }\n.content .header:link,\n.content .header:visited {\n    color: var(--fg);\n}\n.content .header:link,\n.content .header:visited:hover {\n    text-decoration: none;\n}\n\ntable {\n    margin: 0 auto;\n    border-collapse: collapse;\n}\ntable td {\n    padding: 3px 20px;\n    border: 1px var(--table-border-color) solid;\n}\ntable thead {\n    background: var(--table-header-bg);\n}\ntable thead td {\n    font-weight: 700;\n    border: none;\n}\ntable thead th {\n    padding: 3px 20px;\n}\ntable thead tr {\n    border: 1px var(--table-header-bg) solid;\n}\n/* Alternate background colors for rows */\ntable tbody tr:nth-child(2n) {\n    background: var(--table-alternate-bg);\n}\n\n\nblockquote {\n    margin: 20px 0;\n    padding: 0 20px;\n    color: var(--fg);\n    background-color: var(--quote-bg);\n    border-top: .1em solid var(--quote-border);\n    border-bottom: .1em solid var(--quote-border);\n}\n\n\n:not(.footnote-definition) + .footnote-definition,\n.footnote-definition + :not(.footnote-definition) {\n    margin-top: 2em;\n}\n.footnote-definition {\n    font-size: 0.9em;\n    margin: 0.5em 0;\n}\n.footnote-definition p {\n    display: inline;\n}\n\n.tooltiptext {\n    position: absolute;\n    visibility: hidden;\n    color: #fff;\n    background-color: #333;\n    transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */\n    left: -8px; /* Half of the width of the icon */\n    top: -35px;\n    font-size: 0.8em;\n    text-align: center;\n    border-radius: 6px;\n    padding: 5px 8px;\n    margin: 5px;\n    z-index: 1000;\n}\n.tooltipped .tooltiptext {\n    visibility: visible;\n}\n\n.chapter li.part-title {\n    color: var(--sidebar-fg);\n    margin: 5px 0px;\n    font-weight: bold;\n}\n"
  },
  {
    "path": "docs/theme/css/print.css",
    "content": "\n#sidebar,\n#menu-bar,\n.nav-chapters,\n.mobile-nav-chapters {\n    display: none;\n}\n\n#page-wrapper.page-wrapper {\n    transform: none;\n    margin-left: 0px;\n    overflow-y: initial;\n}\n\n#content {\n    max-width: none;\n    margin: 0;\n    padding: 0;\n}\n\n.page {\n    overflow-y: initial;\n}\n\ncode {\n    background-color: #666666;\n    border-radius: 5px;\n\n    /* Force background to be printed in Chrome */\n    -webkit-print-color-adjust: exact;\n}\n\npre > .buttons {\n    z-index: 2;\n}\n\na, a:visited, a:active, a:hover {\n    color: #4183c4;\n    text-decoration: none;\n}\n\nh1, h2, h3, h4, h5, h6 {\n    page-break-inside: avoid;\n    page-break-after: avoid;\n}\n\npre, code {\n    page-break-inside: avoid;\n    white-space: pre-wrap;\n}\n\n.fa {\n    display: none !important;\n}\n"
  },
  {
    "path": "docs/theme/css/variables.css",
    "content": "\n/* Globals */\n\n:root {\n    --sidebar-width: 300px;\n    --page-padding: 15px;\n    --content-max-width: 750px;\n    --menu-bar-height: 50px;\n}\n\n/* Themes */\n\n.ayu {\n    --bg: hsl(210, 25%, 8%);\n    --fg: #c5c5c5;\n\n    --sidebar-bg: #14191f;\n    --sidebar-fg: #c8c9db;\n    --sidebar-non-existant: #5c6773;\n    --sidebar-active: #ffb454;\n    --sidebar-spacer: #2d334f;\n\n    --scrollbar: var(--sidebar-fg);\n\n    --icons: #737480;\n    --icons-hover: #b7b9cc;\n\n    --links: #0096cf;\n\n    --inline-code-color: #ffb454;\n\n    --theme-popup-bg: #14191f;\n    --theme-popup-border: #5c6773;\n    --theme-hover: #191f26;\n\n    --quote-bg: hsl(226, 15%, 17%);\n    --quote-border: hsl(226, 15%, 22%);\n\n    --table-border-color: hsl(210, 25%, 13%);\n    --table-header-bg: hsl(210, 25%, 28%);\n    --table-alternate-bg: hsl(210, 25%, 11%);\n\n    --searchbar-border-color: #848484;\n    --searchbar-bg: #424242;\n    --searchbar-fg: #fff;\n    --searchbar-shadow-color: #d4c89f;\n    --searchresults-header-fg: #666;\n    --searchresults-border-color: #888;\n    --searchresults-li-bg: #252932;\n    --search-mark-bg: #e3b171;\n}\n\n.coal {\n    --bg: hsl(200, 7%, 8%);\n    --fg: #98a3ad;\n\n    --sidebar-bg: #292c2f;\n    --sidebar-fg: #a1adb8;\n    --sidebar-non-existant: #505254;\n    --sidebar-active: #3473ad;\n    --sidebar-spacer: #393939;\n\n    --scrollbar: var(--sidebar-fg);\n\n    --icons: #43484d;\n    --icons-hover: #b3c0cc;\n\n    --links: #2b79a2;\n\n    --inline-code-color: #c5c8c6;;\n\n    --theme-popup-bg: #141617;\n    --theme-popup-border: #43484d;\n    --theme-hover: #1f2124;\n\n    --quote-bg: hsl(234, 21%, 18%);\n    --quote-border: hsl(234, 21%, 23%);\n\n    --table-border-color: hsl(200, 7%, 13%);\n    --table-header-bg: hsl(200, 7%, 28%);\n    --table-alternate-bg: hsl(200, 7%, 11%);\n\n    --searchbar-border-color: #aaa;\n    --searchbar-bg: #b7b7b7;\n    --searchbar-fg: #000;\n    --searchbar-shadow-color: #aaa;\n    --searchresults-header-fg: #666;\n    --searchresults-border-color: #98a3ad;\n    --searchresults-li-bg: #2b2b2f;\n    --search-mark-bg: #355c7d;\n}\n\n.light {\n    --bg: hsl(0, 0%, 100%);\n    --fg: hsl(0, 0%, 0%);\n\n    --sidebar-bg: #fafafa;\n    --sidebar-fg: hsl(0, 0%, 0%);\n    --sidebar-non-existant: #aaaaaa;\n    --sidebar-active: #1f1fff;\n    --sidebar-spacer: #f4f4f4;\n\n    --scrollbar: #8F8F8F;\n\n    --icons: #747474;\n    --icons-hover: #000000;\n\n    --links: #20609f;\n\n    --inline-code-color: #301900;\n\n    --theme-popup-bg: #fafafa;\n    --theme-popup-border: #cccccc;\n    --theme-hover: #e6e6e6;\n\n    --quote-bg: hsl(197, 37%, 96%);\n    --quote-border: hsl(197, 37%, 91%);\n\n    --table-border-color: hsl(0, 0%, 95%);\n    --table-header-bg: hsl(0, 0%, 80%);\n    --table-alternate-bg: hsl(0, 0%, 97%);\n\n    --searchbar-border-color: #aaa;\n    --searchbar-bg: #fafafa;\n    --searchbar-fg: #000;\n    --searchbar-shadow-color: #aaa;\n    --searchresults-header-fg: #666;\n    --searchresults-border-color: #888;\n    --searchresults-li-bg: #e4f2fe;\n    --search-mark-bg: #a2cff5;\n}\n\n.navy {\n    --bg: hsl(226, 23%, 11%);\n    --fg: #bcbdd0;\n\n    --sidebar-bg: #282d3f;\n    --sidebar-fg: #c8c9db;\n    --sidebar-non-existant: #505274;\n    --sidebar-active: #2b79a2;\n    --sidebar-spacer: #2d334f;\n\n    --scrollbar: var(--sidebar-fg);\n\n    --icons: #737480;\n    --icons-hover: #b7b9cc;\n\n    --links: #2b79a2;\n\n    --inline-code-color: #c5c8c6;;\n\n    --theme-popup-bg: #161923;\n    --theme-popup-border: #737480;\n    --theme-hover: #282e40;\n\n    --quote-bg: hsl(226, 15%, 17%);\n    --quote-border: hsl(226, 15%, 22%);\n\n    --table-border-color: hsl(226, 23%, 16%);\n    --table-header-bg: hsl(226, 23%, 31%);\n    --table-alternate-bg: hsl(226, 23%, 14%);\n\n    --searchbar-border-color: #aaa;\n    --searchbar-bg: #aeaec6;\n    --searchbar-fg: #000;\n    --searchbar-shadow-color: #aaa;\n    --searchresults-header-fg: #5f5f71;\n    --searchresults-border-color: #5c5c68;\n    --searchresults-li-bg: #242430;\n    --search-mark-bg: #a2cff5;\n}\n\n.rust {\n    --bg: hsl(60, 9%, 87%);\n    --fg: #262625;\n\n    --sidebar-bg: #3b2e2a;\n    --sidebar-fg: #c8c9db;\n    --sidebar-non-existant: #505254;\n    --sidebar-active: #e69f67;\n    --sidebar-spacer: #45373a;\n\n    --scrollbar: var(--sidebar-fg);\n\n    --icons: #737480;\n    --icons-hover: #262625;\n\n    --links: #2b79a2;\n\n    --inline-code-color: #6e6b5e;\n\n    --theme-popup-bg: #e1e1db;\n    --theme-popup-border: #b38f6b;\n    --theme-hover: #99908a;\n\n    --quote-bg: hsl(60, 5%, 75%);\n    --quote-border: hsl(60, 5%, 70%);\n\n    --table-border-color: hsl(60, 9%, 82%);\n    --table-header-bg: #b3a497;\n    --table-alternate-bg: hsl(60, 9%, 84%);\n\n    --searchbar-border-color: #aaa;\n    --searchbar-bg: #fafafa;\n    --searchbar-fg: #000;\n    --searchbar-shadow-color: #aaa;\n    --searchresults-header-fg: #666;\n    --searchresults-border-color: #888;\n    --searchresults-li-bg: #dec2a2;\n    --search-mark-bg: #e69f67;\n}\n\n@media (prefers-color-scheme: dark) {\n    .light.no-js {\n        --bg: hsl(200, 7%, 8%);\n        --fg: #98a3ad;\n\n        --sidebar-bg: #292c2f;\n        --sidebar-fg: #a1adb8;\n        --sidebar-non-existant: #505254;\n        --sidebar-active: #3473ad;\n        --sidebar-spacer: #393939;\n\n        --scrollbar: var(--sidebar-fg);\n\n        --icons: #43484d;\n        --icons-hover: #b3c0cc;\n\n        --links: #2b79a2;\n\n        --inline-code-color: #c5c8c6;;\n\n        --theme-popup-bg: #141617;\n        --theme-popup-border: #43484d;\n        --theme-hover: #1f2124;\n\n        --quote-bg: hsl(234, 21%, 18%);\n        --quote-border: hsl(234, 21%, 23%);\n\n        --table-border-color: hsl(200, 7%, 13%);\n        --table-header-bg: hsl(200, 7%, 28%);\n        --table-alternate-bg: hsl(200, 7%, 11%);\n\n        --searchbar-border-color: #aaa;\n        --searchbar-bg: #b7b7b7;\n        --searchbar-fg: #000;\n        --searchbar-shadow-color: #aaa;\n        --searchresults-header-fg: #666;\n        --searchresults-border-color: #98a3ad;\n        --searchresults-li-bg: #2b2b2f;\n        --search-mark-bg: #355c7d;\n    }\n}\n"
  },
  {
    "path": "docs/theme/highlight.css",
    "content": "/*\n * An increased contrast highlighting scheme loosely based on the\n * \"Base16 Atelier Dune Light\" theme by Bram de Haan\n * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune)\n * Original Base16 color scheme by Chris Kempson\n * (https://github.com/chriskempson/base16)\n */\n\n/* Comment */\n.hljs-comment,\n.hljs-quote {\n  color: #575757;\n}\n\n/* Red */\n.hljs-variable,\n.hljs-template-variable,\n.hljs-attribute,\n.hljs-tag,\n.hljs-name,\n.hljs-regexp,\n.hljs-link,\n.hljs-name,\n.hljs-selector-id,\n.hljs-selector-class {\n  color: #d70025;\n}\n\n/* Orange */\n.hljs-number,\n.hljs-meta,\n.hljs-built_in,\n.hljs-builtin-name,\n.hljs-literal,\n.hljs-type,\n.hljs-params {\n  color: #b21e00;\n}\n\n/* Green */\n.hljs-string,\n.hljs-symbol,\n.hljs-bullet {\n  color: #008200;\n}\n\n/* Blue */\n.hljs-title,\n.hljs-section {\n  color: #0030f2;\n}\n\n/* Purple */\n.hljs-keyword,\n.hljs-selector-tag {\n  color: #9d00ec;\n}\n\n.hljs {\n  display: block;\n  overflow-x: auto;\n  background: #f6f7f6;\n  color: #000;\n  padding: 0.5em;\n}\n\n.hljs-emphasis {\n  font-style: italic;\n}\n\n.hljs-strong {\n  font-weight: bold;\n}\n\n.hljs-addition {\n  color: #22863a;\n  background-color: #f0fff4;\n}\n\n.hljs-deletion {\n  color: #b31d28;\n  background-color: #ffeef0;\n}\n"
  },
  {
    "path": "docs/theme/highlight.js",
    "content": "/*!\n  Highlight.js v11.0.1 (git: 1cf31f015d)\n  (c) 2006-2021 Ivan Sagalaev and other contributors\n  License: BSD-3-Clause\n */\nvar hljs=function(){\"use strict\";var e={exports:{}};function t(e){\nreturn e instanceof Map?e.clear=e.delete=e.set=()=>{\nthrow Error(\"map is read-only\")}:e instanceof Set&&(e.add=e.clear=e.delete=()=>{\nthrow Error(\"set is read-only\")\n}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((n=>{var i=e[n]\n;\"object\"!=typeof i||Object.isFrozen(i)||t(i)})),e}\ne.exports=t,e.exports.default=t;var n=e.exports;class i{constructor(e){\nvoid 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}\nignoreMatch(){this.isMatchIgnored=!0}}function r(e){\nreturn e.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\").replace(/'/g,\"&#x27;\")\n}function s(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t]\n;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const o=e=>!!e.kind\n;class a{constructor(e,t){\nthis.buffer=\"\",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){\nthis.buffer+=r(e)}openNode(e){if(!o(e))return;let t=e.kind\n;t=e.sublanguage?\"language-\"+t:((e,{prefix:t})=>{if(e.includes(\".\")){\nconst n=e.split(\".\")\n;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${\"_\".repeat(t+1)}`))].join(\" \")\n}return`${t}${e}`})(t,{prefix:this.classPrefix}),this.span(t)}closeNode(e){\no(e)&&(this.buffer+=\"</span>\")}value(){return this.buffer}span(e){\nthis.buffer+=`<span class=\"${e}\">`}}class l{constructor(){this.rootNode={\nchildren:[]},this.stack=[this.rootNode]}get top(){\nreturn this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){\nthis.top.children.push(e)}openNode(e){const t={kind:e,children:[]}\n;this.add(t),this.stack.push(t)}closeNode(){\nif(this.stack.length>1)return this.stack.pop()}closeAllNodes(){\nfor(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}\nwalk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){\nreturn\"string\"==typeof t?e.addText(t):t.children&&(e.openNode(t),\nt.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){\n\"string\"!=typeof e&&e.children&&(e.children.every((e=>\"string\"==typeof e))?e.children=[e.children.join(\"\")]:e.children.forEach((e=>{\nl._collapse(e)})))}}class c extends l{constructor(e){super(),this.options=e}\naddKeyword(e,t){\"\"!==e&&(this.openNode(t),this.addText(e),this.closeNode())}\naddText(e){\"\"!==e&&this.add(e)}addSublanguage(e,t){const n=e.root\n;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){\nreturn new a(this,this.options).value()}finalize(){return!0}}function g(e){\nreturn e?\"string\"==typeof e?e:e.source:null}function d(...e){\nreturn e.map((e=>g(e))).join(\"\")}function u(...e){return\"(\"+((e=>{\nconst t=e[e.length-1]\n;return\"object\"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{}\n})(e).capture?\"\":\"?:\")+e.map((e=>g(e))).join(\"|\")+\")\"}function h(e){\nreturn RegExp(e.toString()+\"|\").exec(\"\").length-1}\nconst f=/\\[(?:[^\\\\\\]]|\\\\.)*\\]|\\(\\??|\\\\([1-9][0-9]*)|\\\\./\n;function p(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n\n;let i=g(e),r=\"\";for(;i.length>0;){const e=f.exec(i);if(!e){r+=i;break}\nr+=i.substring(0,e.index),\ni=i.substring(e.index+e[0].length),\"\\\\\"===e[0][0]&&e[1]?r+=\"\\\\\"+(Number(e[1])+t):(r+=e[0],\n\"(\"===e[0]&&n++)}return r})).map((e=>`(${e})`)).join(t)}\nconst b=\"[a-zA-Z]\\\\w*\",m=\"[a-zA-Z_]\\\\w*\",E=\"\\\\b\\\\d+(\\\\.\\\\d+)?\",x=\"(-?)(\\\\b0[xX][a-fA-F0-9]+|(\\\\b\\\\d+(\\\\.\\\\d*)?|\\\\.\\\\d+)([eE][-+]?\\\\d+)?)\",y=\"\\\\b(0b[01]+)\",w={\nbegin:\"\\\\\\\\[\\\\s\\\\S]\",relevance:0},_={scope:\"string\",begin:\"'\",end:\"'\",\nillegal:\"\\\\n\",contains:[w]},v={scope:\"string\",begin:'\"',end:'\"',illegal:\"\\\\n\",\ncontains:[w]},O=(e,t,n={})=>{const i=s({scope:\"comment\",begin:e,end:t,\ncontains:[]},n);i.contains.push({scope:\"doctag\",\nbegin:\"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)\",\nend:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0})\n;const r=u(\"I\",\"a\",\"is\",\"so\",\"us\",\"to\",\"at\",\"if\",\"in\",\"it\",\"on\",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/)\n;return i.contains.push({begin:d(/[ ]+/,\"(\",r,/[.]?[:]?([.][ ]|[ ])/,\"){3}\")}),i\n},k=O(\"//\",\"$\"),N=O(\"/\\\\*\",\"\\\\*/\"),S=O(\"#\",\"$\");var M=Object.freeze({\n__proto__:null,MATCH_NOTHING_RE:/\\b\\B/,IDENT_RE:b,UNDERSCORE_IDENT_RE:m,\nNUMBER_RE:E,C_NUMBER_RE:x,BINARY_NUMBER_RE:y,\nRE_STARTERS_RE:\"!|!=|!==|%|%=|&|&&|&=|\\\\*|\\\\*=|\\\\+|\\\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\\\?|\\\\[|\\\\{|\\\\(|\\\\^|\\\\^=|\\\\||\\\\|=|\\\\|\\\\||~\",\nSHEBANG:(e={})=>{const t=/^#![ ]*\\//\n;return e.binary&&(e.begin=d(t,/.*\\b/,e.binary,/\\b.*/)),s({scope:\"meta\",begin:t,\nend:/$/,relevance:0,\"on:begin\":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)},\nBACKSLASH_ESCAPE:w,APOS_STRING_MODE:_,QUOTE_STRING_MODE:v,PHRASAL_WORDS_MODE:{\nbegin:/\\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\\b/\n},COMMENT:O,C_LINE_COMMENT_MODE:k,C_BLOCK_COMMENT_MODE:N,HASH_COMMENT_MODE:S,\nNUMBER_MODE:{scope:\"number\",begin:E,relevance:0},C_NUMBER_MODE:{scope:\"number\",\nbegin:x,relevance:0},BINARY_NUMBER_MODE:{scope:\"number\",begin:y,relevance:0},\nREGEXP_MODE:{begin:/(?=\\/[^/\\n]*\\/)/,contains:[{scope:\"regexp\",begin:/\\//,\nend:/\\/[gimuy]*/,illegal:/\\n/,contains:[w,{begin:/\\[/,end:/\\]/,relevance:0,\ncontains:[w]}]}]},TITLE_MODE:{scope:\"title\",begin:b,relevance:0},\nUNDERSCORE_TITLE_MODE:{scope:\"title\",begin:m,relevance:0},METHOD_GUARD:{\nbegin:\"\\\\.\\\\s*[a-zA-Z_]\\\\w*\",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{\n\"on:begin\":(e,t)=>{t.data._beginMatch=e[1]},\"on:end\":(e,t)=>{\nt.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function R(e,t){\n\".\"===e.input[e.index-1]&&t.ignoreMatch()}function j(e,t){\nvoid 0!==e.className&&(e.scope=e.className,delete e.className)}function A(e,t){\nt&&e.beginKeywords&&(e.begin=\"\\\\b(\"+e.beginKeywords.split(\" \").join(\"|\")+\")(?!\\\\.)(?=\\\\b|\\\\s)\",\ne.__beforeBegin=R,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,\nvoid 0===e.relevance&&(e.relevance=0))}function I(e,t){\nArray.isArray(e.illegal)&&(e.illegal=u(...e.illegal))}function B(e,t){\nif(e.match){\nif(e.begin||e.end)throw Error(\"begin & end are not supported with match\")\n;e.begin=e.match,delete e.match}}function T(e,t){\nvoid 0===e.relevance&&(e.relevance=1)}const L=(e,t)=>{if(!e.beforeMatch)return\n;if(e.starts)throw Error(\"beforeMatch cannot be used with starts\")\n;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t]\n})),e.keywords=n.keywords,\ne.begin=d(n.beforeMatch,d(\"(?=\",n.begin,\")\")),e.starts={relevance:0,\ncontains:[Object.assign(n,{endsParent:!0})]},e.relevance=0,delete n.beforeMatch\n},D=[\"of\",\"and\",\"for\",\"in\",\"not\",\"or\",\"if\",\"then\",\"parent\",\"list\",\"value\"]\n;function P(e,t,n=\"keyword\"){const i=Object.create(null)\n;return\"string\"==typeof e?r(n,e.split(\" \")):Array.isArray(e)?r(n,e):Object.keys(e).forEach((n=>{\nObject.assign(i,P(e[n],t,n))})),i;function r(e,n){\nt&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split(\"|\")\n;i[n[0]]=[e,C(n[0],n[1])]}))}}function C(e,t){\nreturn t?Number(t):(e=>D.includes(e.toLowerCase()))(e)?0:1}const H={},$=e=>{\nconsole.error(e)},U=(e,...t)=>{console.log(\"WARN: \"+e,...t)},z=(e,t)=>{\nH[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),H[`${e}/${t}`]=!0)\n},K=Error();function W(e,t,{key:n}){let i=0;const r=e[n],s={},o={}\n;for(let e=1;e<=t.length;e++)o[e+i]=r[e],s[e+i]=!0,i+=h(t[e-1])\n;e[n]=o,e[n]._emit=s,e[n]._multi=!0}function X(e){(e=>{\ne.scope&&\"object\"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope,\ndelete e.scope)})(e),\"string\"==typeof e.beginScope&&(e.beginScope={\n_wrap:e.beginScope}),\"string\"==typeof e.endScope&&(e.endScope={_wrap:e.endScope\n}),(e=>{if(Array.isArray(e.begin)){\nif(e.skip||e.excludeBegin||e.returnBegin)throw $(\"skip, excludeBegin, returnBegin not compatible with beginScope: {}\"),\nK\n;if(\"object\"!=typeof e.beginScope||null===e.beginScope)throw $(\"beginScope must be object\"),\nK;W(e,e.begin,{key:\"beginScope\"}),e.begin=p(e.begin,{joinWith:\"\"})}})(e),(e=>{\nif(Array.isArray(e.end)){\nif(e.skip||e.excludeEnd||e.returnEnd)throw $(\"skip, excludeEnd, returnEnd not compatible with endScope: {}\"),\nK\n;if(\"object\"!=typeof e.endScope||null===e.endScope)throw $(\"endScope must be object\"),\nK;W(e,e.end,{key:\"endScope\"}),e.end=p(e.end,{joinWith:\"\"})}})(e)}function G(e){\nfunction t(t,n){return RegExp(g(t),\"m\"+(e.case_insensitive?\"i\":\"\")+(n?\"g\":\"\"))}\nclass n{constructor(){\nthis.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}\naddRule(e,t){\nt.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),\nthis.matchAt+=h(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null)\n;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(p(e,{joinWith:\"|\"\n}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex\n;const t=this.matcherRe.exec(e);if(!t)return null\n;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n]\n;return t.splice(0,n),Object.assign(t,i)}}class i{constructor(){\nthis.rules=[],this.multiRegexes=[],\nthis.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){\nif(this.multiRegexes[e])return this.multiRegexes[e];const t=new n\n;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))),\nt.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){\nreturn 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){\nthis.rules.push([e,t]),\"begin\"===t.type&&this.count++}exec(e){\nconst t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex\n;let n=t.exec(e)\n;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{\nconst t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}\nreturn n&&(this.regexIndex+=n.position+1,\nthis.regexIndex===this.count&&this.considerAll()),n}}\nif(e.compilerExtensions||(e.compilerExtensions=[]),\ne.contains&&e.contains.includes(\"self\"))throw Error(\"ERR: contains `self` is not supported at the top-level of a language.  See documentation.\")\n;return e.classNameAliases=s(e.classNameAliases||{}),function n(r,o){const a=r\n;if(r.isCompiled)return a\n;[j,B,X,L].forEach((e=>e(r,o))),e.compilerExtensions.forEach((e=>e(r,o))),\nr.__beforeBegin=null,[A,I,T].forEach((e=>e(r,o))),r.isCompiled=!0;let l=null\n;return\"object\"==typeof r.keywords&&r.keywords.$pattern&&(r.keywords=Object.assign({},r.keywords),\nl=r.keywords.$pattern,\ndelete r.keywords.$pattern),l=l||/\\w+/,r.keywords&&(r.keywords=P(r.keywords,e.case_insensitive)),\na.keywordPatternRe=t(l,!0),\no&&(r.begin||(r.begin=/\\B|\\b/),a.beginRe=t(r.begin),r.end||r.endsWithParent||(r.end=/\\B|\\b/),\nr.end&&(a.endRe=t(r.end)),\na.terminatorEnd=g(r.end)||\"\",r.endsWithParent&&o.terminatorEnd&&(a.terminatorEnd+=(r.end?\"|\":\"\")+o.terminatorEnd)),\nr.illegal&&(a.illegalRe=t(r.illegal)),\nr.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>s(e,{\nvariants:null},t)))),e.cachedVariants?e.cachedVariants:Z(e)?s(e,{\nstarts:e.starts?s(e.starts):null\n}):Object.isFrozen(e)?s(e):e))(\"self\"===e?r:e)))),r.contains.forEach((e=>{n(e,a)\n})),r.starts&&n(r.starts,o),a.matcher=(e=>{const t=new i\n;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:\"begin\"\n}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:\"end\"\n}),e.illegal&&t.addRule(e.illegal,{type:\"illegal\"}),t})(a),a}(e)}function Z(e){\nreturn!!e&&(e.endsWithParent||Z(e.starts))}const F=r,V=s,q=Symbol(\"nomatch\")\n;var J=(e=>{const t=Object.create(null),r=Object.create(null),s=[];let o=!0\n;const a=\"Could not find the language '{}', did you forget to load/include a language module?\",l={\ndisableAutodetect:!0,name:\"Plain text\",contains:[]};let g={\nignoreUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,\nlanguageDetectRe:/\\blang(?:uage)?-([\\w-]+)\\b/i,classPrefix:\"hljs-\",\ncssSelector:\"pre code\",languages:null,__emitter:c};function d(e){\nreturn g.noHighlightRe.test(e)}function u(e,t,n,i){let r=\"\",s=\"\"\n;\"object\"==typeof t?(r=e,\nn=t.ignoreIllegals,s=t.language,i=void 0):(z(\"10.7.0\",\"highlight(lang, code, ...args) has been deprecated.\"),\nz(\"10.7.0\",\"Please use highlight(code, options) instead.\\nhttps://github.com/highlightjs/highlight.js/issues/2277\"),\ns=e,r=t),void 0===n&&(n=!0);const o={code:r,language:s};w(\"before:highlight\",o)\n;const a=o.result?o.result:h(o.language,o.code,n,i)\n;return a.code=o.code,w(\"after:highlight\",a),a}function h(e,n,r,s){\nconst l=Object.create(null);function c(){if(!k.keywords)return void S.addText(M)\n;let e=0;k.keywordPatternRe.lastIndex=0;let t=k.keywordPatternRe.exec(M),n=\"\"\n;for(;t;){n+=M.substring(e,t.index)\n;const r=_.case_insensitive?t[0].toLowerCase():t[0],s=(i=r,k.keywords[i]);if(s){\nconst[e,i]=s\n;if(S.addText(n),n=\"\",l[r]=(l[r]||0)+1,l[r]<=7&&(R+=i),e.startsWith(\"_\"))n+=t[0];else{\nconst n=_.classNameAliases[e]||e;S.addKeyword(t[0],n)}}else n+=t[0]\n;e=k.keywordPatternRe.lastIndex,t=k.keywordPatternRe.exec(M)}var i\n;n+=M.substr(e),S.addText(n)}function d(){null!=k.subLanguage?(()=>{\nif(\"\"===M)return;let e=null;if(\"string\"==typeof k.subLanguage){\nif(!t[k.subLanguage])return void S.addText(M)\n;e=h(k.subLanguage,M,!0,N[k.subLanguage]),N[k.subLanguage]=e._top\n}else e=f(M,k.subLanguage.length?k.subLanguage:null)\n;k.relevance>0&&(R+=e.relevance),S.addSublanguage(e._emitter,e.language)\n})():c(),M=\"\"}function u(e,t){let n=1;for(;void 0!==t[n];){if(!e._emit[n]){n++\n;continue}const i=_.classNameAliases[e[n]]||e[n],r=t[n]\n;i?S.addKeyword(r,i):(M=r,c(),M=\"\"),n++}}function p(e,t){\nreturn e.scope&&\"string\"==typeof e.scope&&S.openNode(_.classNameAliases[e.scope]||e.scope),\ne.beginScope&&(e.beginScope._wrap?(S.addKeyword(M,_.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap),\nM=\"\"):e.beginScope._multi&&(u(e.beginScope,t),M=\"\")),k=Object.create(e,{parent:{\nvalue:k}}),k}function b(e,t,n){let r=((e,t)=>{const n=e&&e.exec(t)\n;return n&&0===n.index})(e.endRe,n);if(r){if(e[\"on:end\"]){const n=new i(e)\n;e[\"on:end\"](t,n),n.isMatchIgnored&&(r=!1)}if(r){\nfor(;e.endsParent&&e.parent;)e=e.parent;return e}}\nif(e.endsWithParent)return b(e.parent,t,n)}function m(e){\nreturn 0===k.matcher.regexIndex?(M+=e[0],1):(I=!0,0)}function x(e){\nconst t=e[0],i=n.substr(e.index),r=b(k,e,i);if(!r)return q;const s=k\n;k.endScope&&k.endScope._wrap?(d(),\nS.addKeyword(t,k.endScope._wrap)):k.endScope&&k.endScope._multi?(d(),\nu(k.endScope,e)):s.skip?M+=t:(s.returnEnd||s.excludeEnd||(M+=t),\nd(),s.excludeEnd&&(M=t));do{\nk.scope&&!k.isMultiClass&&S.closeNode(),k.skip||k.subLanguage||(R+=k.relevance),\nk=k.parent}while(k!==r.parent)\n;return r.starts&&p(r.starts,e),s.returnEnd?0:t.length}let y={};function w(t,s){\nconst a=s&&s[0];if(M+=t,null==a)return d(),0\n;if(\"begin\"===y.type&&\"end\"===s.type&&y.index===s.index&&\"\"===a){\nif(M+=n.slice(s.index,s.index+1),!o){const t=Error(`0 width match regex (${e})`)\n;throw t.languageName=e,t.badRule=y.rule,t}return 1}\nif(y=s,\"begin\"===s.type)return(e=>{\nconst t=e[0],n=e.rule,r=new i(n),s=[n.__beforeBegin,n[\"on:begin\"]]\n;for(const n of s)if(n&&(n(e,r),r.isMatchIgnored))return m(t)\n;return n.skip?M+=t:(n.excludeBegin&&(M+=t),\nd(),n.returnBegin||n.excludeBegin||(M=t)),p(n,e),n.returnBegin?0:t.length})(s)\n;if(\"illegal\"===s.type&&!r){\nconst e=Error('Illegal lexeme \"'+a+'\" for mode \"'+(k.scope||\"<unnamed>\")+'\"')\n;throw e.mode=k,e}if(\"end\"===s.type){const e=x(s);if(e!==q)return e}\nif(\"illegal\"===s.type&&\"\"===a)return 1\n;if(A>1e5&&A>3*s.index)throw Error(\"potential infinite loop, way more iterations than matches\")\n;return M+=a,a.length}const _=E(e)\n;if(!_)throw $(a.replace(\"{}\",e)),Error('Unknown language: \"'+e+'\"')\n;const v=G(_);let O=\"\",k=s||v;const N={},S=new g.__emitter(g);(()=>{const e=[]\n;for(let t=k;t!==_;t=t.parent)t.scope&&e.unshift(t.scope)\n;e.forEach((e=>S.openNode(e)))})();let M=\"\",R=0,j=0,A=0,I=!1;try{\nfor(k.matcher.considerAll();;){\nA++,I?I=!1:k.matcher.considerAll(),k.matcher.lastIndex=j\n;const e=k.matcher.exec(n);if(!e)break;const t=w(n.substring(j,e.index),e)\n;j=e.index+t}return w(n.substr(j)),S.closeAllNodes(),S.finalize(),O=S.toHTML(),{\nlanguage:e,value:O,relevance:R,illegal:!1,_emitter:S,_top:k}}catch(t){\nif(t.message&&t.message.includes(\"Illegal\"))return{language:e,value:F(n),\nillegal:!0,relevance:0,_illegalBy:{message:t.message,index:j,\ncontext:n.slice(j-100,j+100),mode:t.mode,resultSoFar:O},_emitter:S};if(o)return{\nlanguage:e,value:F(n),illegal:!1,relevance:0,errorRaised:t,_emitter:S,_top:k}\n;throw t}}function f(e,n){n=n||g.languages||Object.keys(t);const i=(e=>{\nconst t={value:F(e),illegal:!1,relevance:0,_top:l,_emitter:new g.__emitter(g)}\n;return t._emitter.addText(e),t})(e),r=n.filter(E).filter(y).map((t=>h(t,e,!1)))\n;r.unshift(i);const s=r.sort(((e,t)=>{\nif(e.relevance!==t.relevance)return t.relevance-e.relevance\n;if(e.language&&t.language){if(E(e.language).supersetOf===t.language)return 1\n;if(E(t.language).supersetOf===e.language)return-1}return 0})),[o,a]=s,c=o\n;return c.secondBest=a,c}function p(e){let t=null;const n=(e=>{\nlet t=e.className+\" \";t+=e.parentNode?e.parentNode.className:\"\"\n;const n=g.languageDetectRe.exec(t);if(n){const t=E(n[1])\n;return t||(U(a.replace(\"{}\",n[1])),\nU(\"Falling back to no-highlight mode for this block.\",e)),t?n[1]:\"no-highlight\"}\nreturn t.split(/\\s+/).find((e=>d(e)||E(e)))})(e);if(d(n))return\n;w(\"before:highlightElement\",{el:e,language:n\n}),!g.ignoreUnescapedHTML&&e.children.length>0&&(console.warn(\"One of your code blocks includes unescaped HTML. This is a potentially serious security risk.\"),\nconsole.warn(\"https://github.com/highlightjs/highlight.js/issues/2886\"),\nconsole.warn(e)),t=e;const i=t.textContent,s=n?u(i,{language:n,ignoreIllegals:!0\n}):f(i);e.innerHTML=s.value,((e,t,n)=>{const i=t&&r[t]||n\n;e.classList.add(\"hljs\"),e.classList.add(\"language-\"+i)\n})(e,n,s.language),e.result={language:s.language,re:s.relevance,\nrelevance:s.relevance},s.secondBest&&(e.secondBest={\nlanguage:s.secondBest.language,relevance:s.secondBest.relevance\n}),w(\"after:highlightElement\",{el:e,result:s,text:i})}let b=!1;function m(){\n\"loading\"!==document.readyState?document.querySelectorAll(g.cssSelector).forEach(p):b=!0\n}function E(e){return e=(e||\"\").toLowerCase(),t[e]||t[r[e]]}\nfunction x(e,{languageName:t}){\"string\"==typeof e&&(e=[e]),e.forEach((e=>{\nr[e.toLowerCase()]=t}))}function y(e){const t=E(e)\n;return t&&!t.disableAutodetect}function w(e,t){const n=e;s.forEach((e=>{\ne[n]&&e[n](t)}))}\n\"undefined\"!=typeof window&&window.addEventListener&&window.addEventListener(\"DOMContentLoaded\",(()=>{\nb&&m()}),!1),Object.assign(e,{highlight:u,highlightAuto:f,highlightAll:m,\nhighlightElement:p,\nhighlightBlock:e=>(z(\"10.7.0\",\"highlightBlock will be removed entirely in v12.0\"),\nz(\"10.7.0\",\"Please use highlightElement now.\"),p(e)),configure:e=>{g=V(g,e)},\ninitHighlighting:()=>{\nm(),z(\"10.6.0\",\"initHighlighting() deprecated.  Use highlightAll() now.\")},\ninitHighlightingOnLoad:()=>{\nm(),z(\"10.6.0\",\"initHighlightingOnLoad() deprecated.  Use highlightAll() now.\")\n},registerLanguage:(n,i)=>{let r=null;try{r=i(e)}catch(e){\nif($(\"Language definition for '{}' could not be registered.\".replace(\"{}\",n)),\n!o)throw e;$(e),r=l}\nr.name||(r.name=n),t[n]=r,r.rawDefinition=i.bind(null,e),r.aliases&&x(r.aliases,{\nlanguageName:n})},unregisterLanguage:e=>{delete t[e]\n;for(const t of Object.keys(r))r[t]===e&&delete r[t]},\nlistLanguages:()=>Object.keys(t),getLanguage:E,registerAliases:x,\nautoDetection:y,inherit:V,addPlugin:e=>{(e=>{\ne[\"before:highlightBlock\"]&&!e[\"before:highlightElement\"]&&(e[\"before:highlightElement\"]=t=>{\ne[\"before:highlightBlock\"](Object.assign({block:t.el},t))\n}),e[\"after:highlightBlock\"]&&!e[\"after:highlightElement\"]&&(e[\"after:highlightElement\"]=t=>{\ne[\"after:highlightBlock\"](Object.assign({block:t.el},t))})})(e),s.push(e)}\n}),e.debugMode=()=>{o=!1},e.safeMode=()=>{o=!0},e.versionString=\"11.0.1\"\n;for(const e in M)\"object\"==typeof M[e]&&n(M[e]);return Object.assign(e,M),e\n})({}),Y=Object.freeze({__proto__:null});const Q=J\n;for(const e of Object.keys(Y)){const t=e.replace(\"grmr_\",\"\")\n;Q.registerLanguage(t,Y[e])}return Q}()\n;\"object\"==typeof exports&&\"undefined\"!=typeof module&&(module.exports=hljs);hljs.registerLanguage(\"scheme\",(()=>{\"use strict\";return e=>{\nconst t=\"[^\\\\(\\\\)\\\\[\\\\]\\\\{\\\\}\\\",'`;#|\\\\\\\\\\\\s]+\",n={$pattern:t,\nbuilt_in:\"case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules ' * + , ,@ - ... / ; < <= = => > >= ` abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci<? char-ci=? char-ci>=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char<? char=? char>=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci<? string-ci=? string-ci>=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string<? string=? string>=? string>? string? substring symbol->string symbol? tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?\"\n},r={className:\"literal\",begin:\"(#t|#f|#\\\\\\\\\"+t+\"|#\\\\\\\\.)\"},a={\nclassName:\"number\",variants:[{begin:\"(-|\\\\+)?\\\\d+([./]\\\\d+)?\",relevance:0},{\nbegin:\"(-|\\\\+)?\\\\d+([./]\\\\d+)?[+\\\\-](-|\\\\+)?\\\\d+([./]\\\\d+)?i\",relevance:0},{\nbegin:\"#b[0-1]+(/[0-1]+)?\"},{begin:\"#o[0-7]+(/[0-7]+)?\"},{\nbegin:\"#x[0-9a-f]+(/[0-9a-f]+)?\"}]},i=e.QUOTE_STRING_MODE,c=[e.COMMENT(\";\",\"$\",{\nrelevance:0}),e.COMMENT(\"#\\\\|\",\"\\\\|#\")],s={begin:t,relevance:0},l={\nclassName:\"symbol\",begin:\"'\"+t},o={endsWithParent:!0,relevance:0},g={variants:[{\nbegin:/'/},{begin:\"`\"}],contains:[{begin:\"\\\\(\",end:\"\\\\)\",\ncontains:[\"self\",r,i,a,s,l]}]},u={className:\"name\",relevance:0,begin:t,\nkeywords:n},d={variants:[{begin:\"\\\\(\",end:\"\\\\)\"},{begin:\"\\\\[\",end:\"\\\\]\"}],\ncontains:[{begin:/lambda/,endsWithParent:!0,returnBegin:!0,contains:[u,{\nendsParent:!0,variants:[{begin:/\\(/,end:/\\)/},{begin:/\\[/,end:/\\]/}],\ncontains:[s]}]},u,o]};return o.contains=[r,a,i,s,l,g,d].concat(c),{\nname:\"Scheme\",illegal:/\\S/,contains:[e.SHEBANG(),a,i,l,g,d].concat(c)}}})());hljs.registerLanguage(\"clojure\",(()=>{\"use strict\";return e=>{\nconst t=\"a-zA-Z_\\\\-!.?+*=<>&#'\",n=\"[\"+t+\"][\"+t+\"0-9/;:]*\",r=\"def defonce defprotocol defstruct defmulti defmethod defn- defn defmacro deftype defrecord\",a={\n$pattern:n,\nbuilt_in:r+\" cond apply if-not if-let if not not= =|0 <|0 >|0 <=|0 >=|0 ==|0 +|0 /|0 *|0 -|0 rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy first rest cons cast coll last butlast sigs reify second ffirst fnext nfirst nnext meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize\"\n},s={begin:n,relevance:0},o={className:\"number\",begin:\"[-+]?\\\\d+(\\\\.\\\\d+)?\",\nrelevance:0},i=e.inherit(e.QUOTE_STRING_MODE,{illegal:null\n}),c=e.COMMENT(\";\",\"$\",{relevance:0}),d={className:\"literal\",\nbegin:/\\b(true|false|nil)\\b/},l={begin:\"[\\\\[\\\\{]\",end:\"[\\\\]\\\\}]\",relevance:0\n},m={className:\"comment\",begin:\"\\\\^\"+n},p=e.COMMENT(\"\\\\^\\\\{\",\"\\\\}\"),u={\nclassName:\"symbol\",begin:\"[:]{1,2}\"+n},f={begin:\"\\\\(\",end:\"\\\\)\"},h={\nendsWithParent:!0,relevance:0},y={keywords:a,className:\"name\",begin:n,\nrelevance:0,starts:h},g=[f,i,m,p,c,u,l,o,d,s],b={beginKeywords:r,keywords:{\n$pattern:n,keyword:r},end:'(\\\\[|#|\\\\d|\"|:|\\\\{|\\\\)|\\\\(|$)',contains:[{\nclassName:\"title\",begin:n,relevance:0,excludeEnd:!0,endsParent:!0}].concat(g)}\n;return f.contains=[e.COMMENT(\"comment\",\"\"),b,y,h],\nh.contains=g,l.contains=g,p.contains=[l],{name:\"Clojure\",aliases:[\"clj\"],\nillegal:/\\S/,contains:[f,i,m,p,c,u,l,o,d]}}})());hljs.registerLanguage(\"bash\",(()=>{\"use strict\";function e(...e){\nreturn e.map((e=>{return(s=e)?\"string\"==typeof s?s:s.source:null;var s\n})).join(\"\")}return s=>{const n={},t={begin:/\\$\\{/,end:/\\}/,contains:[\"self\",{\nbegin:/:-/,contains:[n]}]};Object.assign(n,{className:\"variable\",variants:[{\nbegin:e(/\\$[\\w\\d#@][\\w\\d_]*/,\"(?![\\\\w\\\\d])(?![$])\")},t]});const a={\nclassName:\"subst\",begin:/\\$\\(/,end:/\\)/,contains:[s.BACKSLASH_ESCAPE]},i={\nbegin:/<<-?\\s*(?=\\w+)/,starts:{contains:[s.END_SAME_AS_BEGIN({begin:/(\\w+)/,\nend:/(\\w+)/,className:\"string\"})]}},c={className:\"string\",begin:/\"/,end:/\"/,\ncontains:[s.BACKSLASH_ESCAPE,n,a]};a.contains.push(c);const o={begin:/\\$\\(\\(/,\nend:/\\)\\)/,contains:[{begin:/\\d+#[0-9a-f]+/,className:\"number\"},s.NUMBER_MODE,n]\n},r=s.SHEBANG({binary:\"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)\",relevance:10\n}),l={className:\"function\",begin:/\\w[\\w\\d_]*\\s*\\(\\s*\\)\\s*\\{/,returnBegin:!0,\ncontains:[s.inherit(s.TITLE_MODE,{begin:/\\w[\\w\\d_]*/})],relevance:0};return{\nname:\"Bash\",aliases:[\"sh\"],keywords:{$pattern:/\\b[a-z._-]+\\b/,\nkeyword:[\"if\",\"then\",\"else\",\"elif\",\"fi\",\"for\",\"while\",\"in\",\"do\",\"done\",\"case\",\"esac\",\"function\"],\nliteral:[\"true\",\"false\"],\nbuilt_in:\"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp\"\n},contains:[r,s.SHEBANG(),l,o,s.HASH_COMMENT_MODE,i,c,{className:\"\",begin:/\\\\\"/\n},{className:\"string\",begin:/'/,end:/'/},n]}}})());hljs.registerLanguage(\"lisp\",(()=>{\"use strict\";return e=>{\nvar n=\"[a-zA-Z_\\\\-+\\\\*\\\\/<=>&#][a-zA-Z0-9_\\\\-+*\\\\/<=>&#!]*\",a=\"\\\\|[^]*?\\\\|\",i=\"(-|\\\\+)?\\\\d+(\\\\.\\\\d+|\\\\/\\\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\\\+|-)?\\\\d+)?\",s={\nclassName:\"literal\",begin:\"\\\\b(t{1}|nil)\\\\b\"},l={className:\"number\",variants:[{\nbegin:i,relevance:0},{begin:\"#(b|B)[0-1]+(/[0-1]+)?\"},{\nbegin:\"#(o|O)[0-7]+(/[0-7]+)?\"},{begin:\"#(x|X)[0-9a-fA-F]+(/[0-9a-fA-F]+)?\"},{\nbegin:\"#(c|C)\\\\(\"+i+\" +\"+i,end:\"\\\\)\"}]},b=e.inherit(e.QUOTE_STRING_MODE,{\nillegal:null}),g=e.COMMENT(\";\",\"$\",{relevance:0}),r={begin:\"\\\\*\",end:\"\\\\*\"},t={\nclassName:\"symbol\",begin:\"[:&]\"+n},c={begin:n,relevance:0},d={begin:a},o={\ncontains:[l,b,r,t,{begin:\"\\\\(\",end:\"\\\\)\",contains:[\"self\",s,b,l,c]},c],\nvariants:[{begin:\"['`]\\\\(\",end:\"\\\\)\"},{begin:\"\\\\(quote \",end:\"\\\\)\",keywords:{\nname:\"quote\"}},{begin:\"'\"+a}]},v={variants:[{begin:\"'\"+n},{\nbegin:\"#'\"+n+\"(::\"+n+\")*\"}]},m={begin:\"\\\\(\\\\s*\",end:\"\\\\)\"},u={endsWithParent:!0,\nrelevance:0};return m.contains=[{className:\"name\",variants:[{begin:n,relevance:0\n},{begin:a}]},u],u.contains=[o,v,m,s,l,b,g,r,t,d,c],{name:\"Lisp\",illegal:/\\S/,\ncontains:[l,e.SHEBANG(),s,b,g,o,v,m,c]}}})());hljs.registerLanguage(\"css\",(()=>{\"use strict\"\n;const e=[\"a\",\"abbr\",\"address\",\"article\",\"aside\",\"audio\",\"b\",\"blockquote\",\"body\",\"button\",\"canvas\",\"caption\",\"cite\",\"code\",\"dd\",\"del\",\"details\",\"dfn\",\"div\",\"dl\",\"dt\",\"em\",\"fieldset\",\"figcaption\",\"figure\",\"footer\",\"form\",\"h1\",\"h2\",\"h3\",\"h4\",\"h5\",\"h6\",\"header\",\"hgroup\",\"html\",\"i\",\"iframe\",\"img\",\"input\",\"ins\",\"kbd\",\"label\",\"legend\",\"li\",\"main\",\"mark\",\"menu\",\"nav\",\"object\",\"ol\",\"p\",\"q\",\"quote\",\"samp\",\"section\",\"span\",\"strong\",\"summary\",\"sup\",\"table\",\"tbody\",\"td\",\"textarea\",\"tfoot\",\"th\",\"thead\",\"time\",\"tr\",\"ul\",\"var\",\"video\"],t=[\"any-hover\",\"any-pointer\",\"aspect-ratio\",\"color\",\"color-gamut\",\"color-index\",\"device-aspect-ratio\",\"device-height\",\"device-width\",\"display-mode\",\"forced-colors\",\"grid\",\"height\",\"hover\",\"inverted-colors\",\"monochrome\",\"orientation\",\"overflow-block\",\"overflow-inline\",\"pointer\",\"prefers-color-scheme\",\"prefers-contrast\",\"prefers-reduced-motion\",\"prefers-reduced-transparency\",\"resolution\",\"scan\",\"scripting\",\"update\",\"width\",\"min-width\",\"max-width\",\"min-height\",\"max-height\"],i=[\"active\",\"any-link\",\"blank\",\"checked\",\"current\",\"default\",\"defined\",\"dir\",\"disabled\",\"drop\",\"empty\",\"enabled\",\"first\",\"first-child\",\"first-of-type\",\"fullscreen\",\"future\",\"focus\",\"focus-visible\",\"focus-within\",\"has\",\"host\",\"host-context\",\"hover\",\"indeterminate\",\"in-range\",\"invalid\",\"is\",\"lang\",\"last-child\",\"last-of-type\",\"left\",\"link\",\"local-link\",\"not\",\"nth-child\",\"nth-col\",\"nth-last-child\",\"nth-last-col\",\"nth-last-of-type\",\"nth-of-type\",\"only-child\",\"only-of-type\",\"optional\",\"out-of-range\",\"past\",\"placeholder-shown\",\"read-only\",\"read-write\",\"required\",\"right\",\"root\",\"scope\",\"target\",\"target-within\",\"user-invalid\",\"valid\",\"visited\",\"where\"],o=[\"after\",\"backdrop\",\"before\",\"cue\",\"cue-region\",\"first-letter\",\"first-line\",\"grammar-error\",\"marker\",\"part\",\"placeholder\",\"selection\",\"slotted\",\"spelling-error\"],r=[\"align-content\",\"align-items\",\"align-self\",\"animation\",\"animation-delay\",\"animation-direction\",\"animation-duration\",\"animation-fill-mode\",\"animation-iteration-count\",\"animation-name\",\"animation-play-state\",\"animation-timing-function\",\"auto\",\"backface-visibility\",\"background\",\"background-attachment\",\"background-clip\",\"background-color\",\"background-image\",\"background-origin\",\"background-position\",\"background-repeat\",\"background-size\",\"border\",\"border-bottom\",\"border-bottom-color\",\"border-bottom-left-radius\",\"border-bottom-right-radius\",\"border-bottom-style\",\"border-bottom-width\",\"border-collapse\",\"border-color\",\"border-image\",\"border-image-outset\",\"border-image-repeat\",\"border-image-slice\",\"border-image-source\",\"border-image-width\",\"border-left\",\"border-left-color\",\"border-left-style\",\"border-left-width\",\"border-radius\",\"border-right\",\"border-right-color\",\"border-right-style\",\"border-right-width\",\"border-spacing\",\"border-style\",\"border-top\",\"border-top-color\",\"border-top-left-radius\",\"border-top-right-radius\",\"border-top-style\",\"border-top-width\",\"border-width\",\"bottom\",\"box-decoration-break\",\"box-shadow\",\"box-sizing\",\"break-after\",\"break-before\",\"break-inside\",\"caption-side\",\"clear\",\"clip\",\"clip-path\",\"color\",\"column-count\",\"column-fill\",\"column-gap\",\"column-rule\",\"column-rule-color\",\"column-rule-style\",\"column-rule-width\",\"column-span\",\"column-width\",\"columns\",\"content\",\"counter-increment\",\"counter-reset\",\"cursor\",\"direction\",\"display\",\"empty-cells\",\"filter\",\"flex\",\"flex-basis\",\"flex-direction\",\"flex-flow\",\"flex-grow\",\"flex-shrink\",\"flex-wrap\",\"float\",\"font\",\"font-display\",\"font-family\",\"font-feature-settings\",\"font-kerning\",\"font-language-override\",\"font-size\",\"font-size-adjust\",\"font-smoothing\",\"font-stretch\",\"font-style\",\"font-variant\",\"font-variant-ligatures\",\"font-variation-settings\",\"font-weight\",\"height\",\"hyphens\",\"icon\",\"image-orientation\",\"image-rendering\",\"image-resolution\",\"ime-mode\",\"inherit\",\"initial\",\"justify-content\",\"left\",\"letter-spacing\",\"line-height\",\"list-style\",\"list-style-image\",\"list-style-position\",\"list-style-type\",\"margin\",\"margin-bottom\",\"margin-left\",\"margin-right\",\"margin-top\",\"marks\",\"mask\",\"max-height\",\"max-width\",\"min-height\",\"min-width\",\"nav-down\",\"nav-index\",\"nav-left\",\"nav-right\",\"nav-up\",\"none\",\"normal\",\"object-fit\",\"object-position\",\"opacity\",\"order\",\"orphans\",\"outline\",\"outline-color\",\"outline-offset\",\"outline-style\",\"outline-width\",\"overflow\",\"overflow-wrap\",\"overflow-x\",\"overflow-y\",\"padding\",\"padding-bottom\",\"padding-left\",\"padding-right\",\"padding-top\",\"page-break-after\",\"page-break-before\",\"page-break-inside\",\"perspective\",\"perspective-origin\",\"pointer-events\",\"position\",\"quotes\",\"resize\",\"right\",\"src\",\"tab-size\",\"table-layout\",\"text-align\",\"text-align-last\",\"text-decoration\",\"text-decoration-color\",\"text-decoration-line\",\"text-decoration-style\",\"text-indent\",\"text-overflow\",\"text-rendering\",\"text-shadow\",\"text-transform\",\"text-underline-position\",\"top\",\"transform\",\"transform-origin\",\"transform-style\",\"transition\",\"transition-delay\",\"transition-duration\",\"transition-property\",\"transition-timing-function\",\"unicode-bidi\",\"vertical-align\",\"visibility\",\"white-space\",\"widows\",\"width\",\"word-break\",\"word-spacing\",\"word-wrap\",\"z-index\"].reverse()\n;return n=>{const a=(e=>({IMPORTANT:{scope:\"meta\",begin:\"!important\"},HEXCOLOR:{\nscope:\"number\",begin:\"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})\"},\nATTRIBUTE_SELECTOR_MODE:{scope:\"selector-attr\",begin:/\\[/,end:/\\]/,illegal:\"$\",\ncontains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{\nscope:\"number\",\nbegin:e.NUMBER_RE+\"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?\",\nrelevance:0}}))(n),l=[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE];return{name:\"CSS\",\ncase_insensitive:!0,illegal:/[=|'\\$]/,keywords:{keyframePosition:\"from to\"},\nclassNameAliases:{keyframePosition:\"selector-tag\"},\ncontains:[n.C_BLOCK_COMMENT_MODE,{begin:/-(webkit|moz|ms|o)-(?=[a-z])/\n},a.CSS_NUMBER_MODE,{className:\"selector-id\",begin:/#[A-Za-z0-9_-]+/,relevance:0\n},{className:\"selector-class\",begin:\"\\\\.[a-zA-Z-][a-zA-Z0-9_-]*\",relevance:0\n},a.ATTRIBUTE_SELECTOR_MODE,{className:\"selector-pseudo\",variants:[{\nbegin:\":(\"+i.join(\"|\")+\")\"},{begin:\"::(\"+o.join(\"|\")+\")\"}]},{\nclassName:\"attribute\",begin:\"\\\\b(\"+r.join(\"|\")+\")\\\\b\"},{begin:\":\",end:\"[;}]\",\ncontains:[a.HEXCOLOR,a.IMPORTANT,a.CSS_NUMBER_MODE,...l,{\nbegin:/(url|data-uri)\\(/,end:/\\)/,relevance:0,keywords:{built_in:\"url data-uri\"\n},contains:[{className:\"string\",begin:/[^)]/,endsWithParent:!0,excludeEnd:!0}]\n},{className:\"built_in\",begin:/[\\w-]+(?=\\()/}]},{\nbegin:(s=/@/,((...e)=>e.map((e=>(e=>e?\"string\"==typeof e?e:e.source:null)(e))).join(\"\"))(\"(?=\",s,\")\")),\nend:\"[{;]\",relevance:0,illegal:/:/,contains:[{className:\"keyword\",\nbegin:/@-?\\w[\\w]*(-\\w+)*/},{begin:/\\s/,endsWithParent:!0,excludeEnd:!0,\nrelevance:0,keywords:{$pattern:/[a-z-]+/,keyword:\"and or not only\",\nattribute:t.join(\" \")},contains:[{begin:/[a-z-]+(?=:)/,className:\"attribute\"\n},...l,a.CSS_NUMBER_MODE]}]},{className:\"selector-tag\",\nbegin:\"\\\\b(\"+e.join(\"|\")+\")\\\\b\"}]};var s}})());hljs.registerLanguage(\"vim\",(()=>{\"use strict\";return e=>({name:\"Vim Script\",\nkeywords:{$pattern:/[!#@\\w]+/,\nkeyword:\"N|0 P|0 X|0 a|0 ab abc abo al am an|0 ar arga argd arge argdo argg argl argu as au aug aun b|0 bN ba bad bd be bel bf bl bm bn bo bp br brea breaka breakd breakl bro bufdo buffers bun bw c|0 cN cNf ca cabc caddb cad caddf cal cat cb cc ccl cd ce cex cf cfir cgetb cgete cg changes chd che checkt cl cla clo cm cmapc cme cn cnew cnf cno cnorea cnoreme co col colo com comc comp con conf cope cp cpf cq cr cs cst cu cuna cunme cw delm deb debugg delc delf dif diffg diffo diffp diffpu diffs diffthis dig di dl dell dj dli do doautoa dp dr ds dsp e|0 ea ec echoe echoh echom echon el elsei em en endfo endf endt endw ene ex exe exi exu f|0 files filet fin fina fini fir fix fo foldc foldd folddoc foldo for fu go gr grepa gu gv ha helpf helpg helpt hi hid his ia iabc if ij il im imapc ime ino inorea inoreme int is isp iu iuna iunme j|0 ju k|0 keepa kee keepj lN lNf l|0 lad laddb laddf la lan lat lb lc lch lcl lcs le lefta let lex lf lfir lgetb lgete lg lgr lgrepa lh ll lla lli lmak lm lmapc lne lnew lnf ln loadk lo loc lockv lol lope lp lpf lr ls lt lu lua luad luaf lv lvimgrepa lw m|0 ma mak map mapc marks mat me menut mes mk mks mksp mkv mkvie mod mz mzf nbc nb nbs new nm nmapc nme nn nnoreme noa no noh norea noreme norm nu nun nunme ol o|0 om omapc ome on ono onoreme opt ou ounme ow p|0 profd prof pro promptr pc ped pe perld po popu pp pre prev ps pt ptN ptf ptj ptl ptn ptp ptr pts pu pw py3 python3 py3d py3f py pyd pyf quita qa rec red redi redr redraws reg res ret retu rew ri rightb rub rubyd rubyf rund ru rv sN san sa sal sav sb sbN sba sbf sbl sbm sbn sbp sbr scrip scripte scs se setf setg setl sf sfir sh sim sig sil sl sla sm smap smapc sme sn sni sno snor snoreme sor so spelld spe spelli spellr spellu spellw sp spr sre st sta startg startr star stopi stj sts sun sunm sunme sus sv sw sy synti sync tN tabN tabc tabdo tabe tabf tabfir tabl tabm tabnew tabn tabo tabp tabr tabs tab ta tags tc tcld tclf te tf th tj tl tm tn to tp tr try ts tu u|0 undoj undol una unh unl unlo unm unme uns up ve verb vert vim vimgrepa vi viu vie vm vmapc vme vne vn vnoreme vs vu vunme windo w|0 wN wa wh wi winc winp wn wp wq wqa ws wu wv x|0 xa xmapc xm xme xn xnoreme xu xunme y|0 z|0 ~ Next Print append abbreviate abclear aboveleft all amenu anoremenu args argadd argdelete argedit argglobal arglocal argument ascii autocmd augroup aunmenu buffer bNext ball badd bdelete behave belowright bfirst blast bmodified bnext botright bprevious brewind break breakadd breakdel breaklist browse bunload bwipeout change cNext cNfile cabbrev cabclear caddbuffer caddexpr caddfile call catch cbuffer cclose center cexpr cfile cfirst cgetbuffer cgetexpr cgetfile chdir checkpath checktime clist clast close cmap cmapclear cmenu cnext cnewer cnfile cnoremap cnoreabbrev cnoremenu copy colder colorscheme command comclear compiler continue confirm copen cprevious cpfile cquit crewind cscope cstag cunmap cunabbrev cunmenu cwindow delete delmarks debug debuggreedy delcommand delfunction diffupdate diffget diffoff diffpatch diffput diffsplit digraphs display deletel djump dlist doautocmd doautoall deletep drop dsearch dsplit edit earlier echo echoerr echohl echomsg else elseif emenu endif endfor endfunction endtry endwhile enew execute exit exusage file filetype find finally finish first fixdel fold foldclose folddoopen folddoclosed foldopen function global goto grep grepadd gui gvim hardcopy help helpfind helpgrep helptags highlight hide history insert iabbrev iabclear ijump ilist imap imapclear imenu inoremap inoreabbrev inoremenu intro isearch isplit iunmap iunabbrev iunmenu join jumps keepalt keepmarks keepjumps lNext lNfile list laddexpr laddbuffer laddfile last language later lbuffer lcd lchdir lclose lcscope left leftabove lexpr lfile lfirst lgetbuffer lgetexpr lgetfile lgrep lgrepadd lhelpgrep llast llist lmake lmap lmapclear lnext lnewer lnfile lnoremap loadkeymap loadview lockmarks lockvar lolder lopen lprevious lpfile lrewind ltag lunmap luado luafile lvimgrep lvimgrepadd lwindow move mark make mapclear match menu menutranslate messages mkexrc mksession mkspell mkvimrc mkview mode mzscheme mzfile nbclose nbkey nbsart next nmap nmapclear nmenu nnoremap nnoremenu noautocmd noremap nohlsearch noreabbrev noremenu normal number nunmap nunmenu oldfiles open omap omapclear omenu only onoremap onoremenu options ounmap ounmenu ownsyntax print profdel profile promptfind promptrepl pclose pedit perl perldo pop popup ppop preserve previous psearch ptag ptNext ptfirst ptjump ptlast ptnext ptprevious ptrewind ptselect put pwd py3do py3file python pydo pyfile quit quitall qall read recover redo redir redraw redrawstatus registers resize retab return rewind right rightbelow ruby rubydo rubyfile rundo runtime rviminfo substitute sNext sandbox sargument sall saveas sbuffer sbNext sball sbfirst sblast sbmodified sbnext sbprevious sbrewind scriptnames scriptencoding scscope set setfiletype setglobal setlocal sfind sfirst shell simalt sign silent sleep slast smagic smapclear smenu snext sniff snomagic snoremap snoremenu sort source spelldump spellgood spellinfo spellrepall spellundo spellwrong split sprevious srewind stop stag startgreplace startreplace startinsert stopinsert stjump stselect sunhide sunmap sunmenu suspend sview swapname syntax syntime syncbind tNext tabNext tabclose tabedit tabfind tabfirst tablast tabmove tabnext tabonly tabprevious tabrewind tag tcl tcldo tclfile tearoff tfirst throw tjump tlast tmenu tnext topleft tprevious trewind tselect tunmenu undo undojoin undolist unabbreviate unhide unlet unlockvar unmap unmenu unsilent update vglobal version verbose vertical vimgrep vimgrepadd visual viusage view vmap vmapclear vmenu vnew vnoremap vnoremenu vsplit vunmap vunmenu write wNext wall while winsize wincmd winpos wnext wprevious wqall wsverb wundo wviminfo xit xall xmapclear xmap xmenu xnoremap xnoremenu xunmap xunmenu yank\",\nbuilt_in:\"synIDtrans atan2 range matcharg did_filetype asin feedkeys xor argv complete_check add getwinposx getqflist getwinposy screencol clearmatches empty extend getcmdpos mzeval garbagecollect setreg ceil sqrt diff_hlID inputsecret get getfperm getpid filewritable shiftwidth max sinh isdirectory synID system inputrestore winline atan visualmode inputlist tabpagewinnr round getregtype mapcheck hasmapto histdel argidx findfile sha256 exists toupper getcmdline taglist string getmatches bufnr strftime winwidth bufexists strtrans tabpagebuflist setcmdpos remote_read printf setloclist getpos getline bufwinnr float2nr len getcmdtype diff_filler luaeval resolve libcallnr foldclosedend reverse filter has_key bufname str2float strlen setline getcharmod setbufvar index searchpos shellescape undofile foldclosed setqflist buflisted strchars str2nr virtcol floor remove undotree remote_expr winheight gettabwinvar reltime cursor tabpagenr finddir localtime acos getloclist search tanh matchend rename gettabvar strdisplaywidth type abs py3eval setwinvar tolower wildmenumode log10 spellsuggest bufloaded synconcealed nextnonblank server2client complete settabwinvar executable input wincol setmatches getftype hlID inputsave searchpair or screenrow line settabvar histadd deepcopy strpart remote_peek and eval getftime submatch screenchar winsaveview matchadd mkdir screenattr getfontname libcall reltimestr getfsize winnr invert pow getbufline byte2line soundfold repeat fnameescape tagfiles sin strwidth spellbadword trunc maparg log lispindent hostname setpos globpath remote_foreground getchar synIDattr fnamemodify cscope_connection stridx winbufnr indent min complete_add nr2char searchpairpos inputdialog values matchlist items hlexists strridx browsedir expand fmod pathshorten line2byte argc count getwinvar glob foldtextresult getreg foreground cosh matchdelete has char2nr simplify histget searchdecl iconv winrestcmd pumvisible writefile foldlevel haslocaldir keys cos matchstr foldtext histnr tan tempname getcwd byteidx getbufvar islocked escape eventhandler remote_send serverlist winrestview synstack pyeval prevnonblank readfile cindent filereadable changenr exp\"\n},illegal:/;/,contains:[e.NUMBER_MODE,{className:\"string\",begin:\"'\",end:\"'\",\nillegal:\"\\\\n\"},{className:\"string\",begin:/\"(\\\\\"|\\n\\\\|[^\"\\n])*\"/\n},e.COMMENT('\"',\"$\"),{className:\"variable\",begin:/[bwtglsav]:[\\w\\d_]+/},{\nbegin:[/\\b(?:function|function!)/,/\\s+/,e.IDENT_RE],className:{1:\"keyword\",\n3:\"title\"},end:\"$\",relevance:0,contains:[{className:\"params\",begin:\"\\\\(\",\nend:\"\\\\)\"}]},{className:\"symbol\",begin:/<[\\w-]+>/}]})})());hljs.registerLanguage(\"xml\",(()=>{\"use strict\";function e(e){\nreturn e?\"string\"==typeof e?e:e.source:null}function n(e){return a(\"(?=\",e,\")\")}\nfunction a(...n){return n.map((n=>e(n))).join(\"\")}function s(...n){\nreturn\"(\"+((e=>{const n=e[e.length-1]\n;return\"object\"==typeof n&&n.constructor===Object?(e.splice(e.length-1,1),n):{}\n})(n).capture?\"\":\"?:\")+n.map((n=>e(n))).join(\"|\")+\")\"}return e=>{\nconst t=a(/[A-Z_]/,a(\"(?:\",/[A-Z0-9_.-]*:/,\")?\"),/[A-Z0-9_.-]*/),i={\nclassName:\"symbol\",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},c={begin:/\\s/,\ncontains:[{className:\"keyword\",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\\n/}]\n},r=e.inherit(c,{begin:/\\(/,end:/\\)/}),l=e.inherit(e.APOS_STRING_MODE,{\nclassName:\"string\"}),g=e.inherit(e.QUOTE_STRING_MODE,{className:\"string\"}),m={\nendsWithParent:!0,illegal:/</,relevance:0,contains:[{className:\"attr\",\nbegin:/[A-Za-z0-9._:-]+/,relevance:0},{begin:/=\\s*/,relevance:0,contains:[{\nclassName:\"string\",endsParent:!0,variants:[{begin:/\"/,end:/\"/,contains:[i]},{\nbegin:/'/,end:/'/,contains:[i]},{begin:/[^\\s\"'=<>`]+/}]}]}]};return{\nname:\"HTML, XML\",\naliases:[\"html\",\"xhtml\",\"rss\",\"atom\",\"xjb\",\"xsd\",\"xsl\",\"plist\",\"wsf\",\"svg\"],\ncase_insensitive:!0,contains:[{className:\"meta\",begin:/<![a-z]/,end:/>/,\nrelevance:10,contains:[c,g,l,r,{begin:/\\[/,end:/\\]/,contains:[{className:\"meta\",\nbegin:/<![a-z]/,end:/>/,contains:[c,r,g,l]}]}]},e.COMMENT(/<!--/,/-->/,{\nrelevance:10}),{begin:/<!\\[CDATA\\[/,end:/\\]\\]>/,relevance:10},i,{\nclassName:\"meta\",begin:/<\\?xml/,end:/\\?>/,relevance:10},{className:\"tag\",\nbegin:/<style(?=\\s|>)/,end:/>/,keywords:{name:\"style\"},contains:[m],starts:{\nend:/<\\/style>/,returnEnd:!0,subLanguage:[\"css\",\"xml\"]}},{className:\"tag\",\nbegin:/<script(?=\\s|>)/,end:/>/,keywords:{name:\"script\"},contains:[m],starts:{\nend:/<\\/script>/,returnEnd:!0,subLanguage:[\"javascript\",\"handlebars\",\"xml\"]}},{\nclassName:\"tag\",begin:/<>|<\\/>/},{className:\"tag\",\nbegin:a(/</,n(a(t,s(/\\/>/,/>/,/\\s/)))),end:/\\/?>/,contains:[{className:\"name\",\nbegin:t,relevance:0,starts:m}]},{className:\"tag\",begin:a(/<\\//,n(a(t,/>/))),\ncontains:[{className:\"name\",begin:t,relevance:0},{begin:/>/,relevance:0,\nendsParent:!0}]}]}}})());hljs.registerLanguage(\"markdown\",(()=>{\"use strict\";function n(...n){\nreturn n.map((n=>{return(e=n)?\"string\"==typeof e?e:e.source:null;var e\n})).join(\"\")}return e=>{const a={begin:/<\\/?[A-Za-z_]/,end:\">\",\nsubLanguage:\"xml\",relevance:0},i={variants:[{begin:/\\[.+?\\]\\[.*?\\]/,relevance:0\n},{begin:/\\[.+?\\]\\(((data|javascript|mailto):|(?:http|ftp)s?:\\/\\/).*?\\)/,\nrelevance:2},{begin:n(/\\[.+?\\]\\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\\/\\/.*?\\)/),\nrelevance:2},{begin:/\\[.+?\\]\\([./?&#].*?\\)/,relevance:1},{\nbegin:/\\[.+?\\]\\(.*?\\)/,relevance:0}],returnBegin:!0,contains:[{\nclassName:\"string\",relevance:0,begin:\"\\\\[\",end:\"\\\\]\",excludeBegin:!0,\nreturnEnd:!0},{className:\"link\",relevance:0,begin:\"\\\\]\\\\(\",end:\"\\\\)\",\nexcludeBegin:!0,excludeEnd:!0},{className:\"symbol\",relevance:0,begin:\"\\\\]\\\\[\",\nend:\"\\\\]\",excludeBegin:!0,excludeEnd:!0}]},s={className:\"strong\",contains:[],\nvariants:[{begin:/_{2}/,end:/_{2}/},{begin:/\\*{2}/,end:/\\*{2}/}]},c={\nclassName:\"emphasis\",contains:[],variants:[{begin:/\\*(?!\\*)/,end:/\\*/},{\nbegin:/_(?!_)/,end:/_/,relevance:0}]};s.contains.push(c),c.contains.push(s)\n;let t=[a,i]\n;return s.contains=s.contains.concat(t),c.contains=c.contains.concat(t),\nt=t.concat(s,c),{name:\"Markdown\",aliases:[\"md\",\"mkdown\",\"mkd\"],contains:[{\nclassName:\"section\",variants:[{begin:\"^#{1,6}\",end:\"$\",contains:t},{\nbegin:\"(?=^.+?\\\\n[=-]{2,}$)\",contains:[{begin:\"^[=-]*$\"},{begin:\"^\",end:\"\\\\n\",\ncontains:t}]}]},a,{className:\"bullet\",begin:\"^[ \\t]*([*+-]|(\\\\d+\\\\.))(?=\\\\s+)\",\nend:\"\\\\s+\",excludeEnd:!0},s,c,{className:\"quote\",begin:\"^>\\\\s+\",contains:t,\nend:\"$\"},{className:\"code\",variants:[{begin:\"(`{3,})[^`](.|\\\\n)*?\\\\1`*[ ]*\"},{\nbegin:\"(~{3,})[^~](.|\\\\n)*?\\\\1~*[ ]*\"},{begin:\"```\",end:\"```+[ ]*$\"},{\nbegin:\"~~~\",end:\"~~~+[ ]*$\"},{begin:\"`.+?`\"},{begin:\"(?=^( {4}|\\\\t))\",\ncontains:[{begin:\"^( {4}|\\\\t)\",end:\"(\\\\n)$\"}],relevance:0}]},{\nbegin:\"^[-\\\\*]{3,}\",end:\"$\"},i,{begin:/^\\[[^\\n]+\\]:/,returnBegin:!0,contains:[{\nclassName:\"symbol\",begin:/\\[/,end:/\\]/,excludeBegin:!0,excludeEnd:!0},{\nclassName:\"link\",begin:/:\\s*/,end:/$/,excludeBegin:!0}]}]}}})());hljs.registerLanguage(\"scss\",(()=>{\"use strict\"\n;const e=[\"a\",\"abbr\",\"address\",\"article\",\"aside\",\"audio\",\"b\",\"blockquote\",\"body\",\"button\",\"canvas\",\"caption\",\"cite\",\"code\",\"dd\",\"del\",\"details\",\"dfn\",\"div\",\"dl\",\"dt\",\"em\",\"fieldset\",\"figcaption\",\"figure\",\"footer\",\"form\",\"h1\",\"h2\",\"h3\",\"h4\",\"h5\",\"h6\",\"header\",\"hgroup\",\"html\",\"i\",\"iframe\",\"img\",\"input\",\"ins\",\"kbd\",\"label\",\"legend\",\"li\",\"main\",\"mark\",\"menu\",\"nav\",\"object\",\"ol\",\"p\",\"q\",\"quote\",\"samp\",\"section\",\"span\",\"strong\",\"summary\",\"sup\",\"table\",\"tbody\",\"td\",\"textarea\",\"tfoot\",\"th\",\"thead\",\"time\",\"tr\",\"ul\",\"var\",\"video\"],t=[\"any-hover\",\"any-pointer\",\"aspect-ratio\",\"color\",\"color-gamut\",\"color-index\",\"device-aspect-ratio\",\"device-height\",\"device-width\",\"display-mode\",\"forced-colors\",\"grid\",\"height\",\"hover\",\"inverted-colors\",\"monochrome\",\"orientation\",\"overflow-block\",\"overflow-inline\",\"pointer\",\"prefers-color-scheme\",\"prefers-contrast\",\"prefers-reduced-motion\",\"prefers-reduced-transparency\",\"resolution\",\"scan\",\"scripting\",\"update\",\"width\",\"min-width\",\"max-width\",\"min-height\",\"max-height\"],i=[\"active\",\"any-link\",\"blank\",\"checked\",\"current\",\"default\",\"defined\",\"dir\",\"disabled\",\"drop\",\"empty\",\"enabled\",\"first\",\"first-child\",\"first-of-type\",\"fullscreen\",\"future\",\"focus\",\"focus-visible\",\"focus-within\",\"has\",\"host\",\"host-context\",\"hover\",\"indeterminate\",\"in-range\",\"invalid\",\"is\",\"lang\",\"last-child\",\"last-of-type\",\"left\",\"link\",\"local-link\",\"not\",\"nth-child\",\"nth-col\",\"nth-last-child\",\"nth-last-col\",\"nth-last-of-type\",\"nth-of-type\",\"only-child\",\"only-of-type\",\"optional\",\"out-of-range\",\"past\",\"placeholder-shown\",\"read-only\",\"read-write\",\"required\",\"right\",\"root\",\"scope\",\"target\",\"target-within\",\"user-invalid\",\"valid\",\"visited\",\"where\"],r=[\"after\",\"backdrop\",\"before\",\"cue\",\"cue-region\",\"first-letter\",\"first-line\",\"grammar-error\",\"marker\",\"part\",\"placeholder\",\"selection\",\"slotted\",\"spelling-error\"],o=[\"align-content\",\"align-items\",\"align-self\",\"animation\",\"animation-delay\",\"animation-direction\",\"animation-duration\",\"animation-fill-mode\",\"animation-iteration-count\",\"animation-name\",\"animation-play-state\",\"animation-timing-function\",\"auto\",\"backface-visibility\",\"background\",\"background-attachment\",\"background-clip\",\"background-color\",\"background-image\",\"background-origin\",\"background-position\",\"background-repeat\",\"background-size\",\"border\",\"border-bottom\",\"border-bottom-color\",\"border-bottom-left-radius\",\"border-bottom-right-radius\",\"border-bottom-style\",\"border-bottom-width\",\"border-collapse\",\"border-color\",\"border-image\",\"border-image-outset\",\"border-image-repeat\",\"border-image-slice\",\"border-image-source\",\"border-image-width\",\"border-left\",\"border-left-color\",\"border-left-style\",\"border-left-width\",\"border-radius\",\"border-right\",\"border-right-color\",\"border-right-style\",\"border-right-width\",\"border-spacing\",\"border-style\",\"border-top\",\"border-top-color\",\"border-top-left-radius\",\"border-top-right-radius\",\"border-top-style\",\"border-top-width\",\"border-width\",\"bottom\",\"box-decoration-break\",\"box-shadow\",\"box-sizing\",\"break-after\",\"break-before\",\"break-inside\",\"caption-side\",\"clear\",\"clip\",\"clip-path\",\"color\",\"column-count\",\"column-fill\",\"column-gap\",\"column-rule\",\"column-rule-color\",\"column-rule-style\",\"column-rule-width\",\"column-span\",\"column-width\",\"columns\",\"content\",\"counter-increment\",\"counter-reset\",\"cursor\",\"direction\",\"display\",\"empty-cells\",\"filter\",\"flex\",\"flex-basis\",\"flex-direction\",\"flex-flow\",\"flex-grow\",\"flex-shrink\",\"flex-wrap\",\"float\",\"font\",\"font-display\",\"font-family\",\"font-feature-settings\",\"font-kerning\",\"font-language-override\",\"font-size\",\"font-size-adjust\",\"font-smoothing\",\"font-stretch\",\"font-style\",\"font-variant\",\"font-variant-ligatures\",\"font-variation-settings\",\"font-weight\",\"height\",\"hyphens\",\"icon\",\"image-orientation\",\"image-rendering\",\"image-resolution\",\"ime-mode\",\"inherit\",\"initial\",\"justify-content\",\"left\",\"letter-spacing\",\"line-height\",\"list-style\",\"list-style-image\",\"list-style-position\",\"list-style-type\",\"margin\",\"margin-bottom\",\"margin-left\",\"margin-right\",\"margin-top\",\"marks\",\"mask\",\"max-height\",\"max-width\",\"min-height\",\"min-width\",\"nav-down\",\"nav-index\",\"nav-left\",\"nav-right\",\"nav-up\",\"none\",\"normal\",\"object-fit\",\"object-position\",\"opacity\",\"order\",\"orphans\",\"outline\",\"outline-color\",\"outline-offset\",\"outline-style\",\"outline-width\",\"overflow\",\"overflow-wrap\",\"overflow-x\",\"overflow-y\",\"padding\",\"padding-bottom\",\"padding-left\",\"padding-right\",\"padding-top\",\"page-break-after\",\"page-break-before\",\"page-break-inside\",\"perspective\",\"perspective-origin\",\"pointer-events\",\"position\",\"quotes\",\"resize\",\"right\",\"src\",\"tab-size\",\"table-layout\",\"text-align\",\"text-align-last\",\"text-decoration\",\"text-decoration-color\",\"text-decoration-line\",\"text-decoration-style\",\"text-indent\",\"text-overflow\",\"text-rendering\",\"text-shadow\",\"text-transform\",\"text-underline-position\",\"top\",\"transform\",\"transform-origin\",\"transform-style\",\"transition\",\"transition-delay\",\"transition-duration\",\"transition-property\",\"transition-timing-function\",\"unicode-bidi\",\"vertical-align\",\"visibility\",\"white-space\",\"widows\",\"width\",\"word-break\",\"word-spacing\",\"word-wrap\",\"z-index\"].reverse()\n;return a=>{const n=(e=>({IMPORTANT:{scope:\"meta\",begin:\"!important\"},HEXCOLOR:{\nscope:\"number\",begin:\"#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})\"},\nATTRIBUTE_SELECTOR_MODE:{scope:\"selector-attr\",begin:/\\[/,end:/\\]/,illegal:\"$\",\ncontains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{\nscope:\"number\",\nbegin:e.NUMBER_RE+\"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?\",\nrelevance:0}}))(a),l=r,s=i,d=\"@[a-z-]+\",c={className:\"variable\",\nbegin:\"(\\\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\\\b\"};return{name:\"SCSS\",case_insensitive:!0,\nillegal:\"[=/|']\",contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{\nclassName:\"selector-id\",begin:\"#[A-Za-z0-9_-]+\",relevance:0},{\nclassName:\"selector-class\",begin:\"\\\\.[A-Za-z0-9_-]+\",relevance:0\n},n.ATTRIBUTE_SELECTOR_MODE,{className:\"selector-tag\",\nbegin:\"\\\\b(\"+e.join(\"|\")+\")\\\\b\",relevance:0},{className:\"selector-pseudo\",\nbegin:\":(\"+s.join(\"|\")+\")\"},{className:\"selector-pseudo\",\nbegin:\"::(\"+l.join(\"|\")+\")\"},c,{begin:/\\(/,end:/\\)/,contains:[n.CSS_NUMBER_MODE]\n},{className:\"attribute\",begin:\"\\\\b(\"+o.join(\"|\")+\")\\\\b\"},{\nbegin:\"\\\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\\\b\"\n},{begin:\":\",end:\";\",\ncontains:[c,n.HEXCOLOR,n.CSS_NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,n.IMPORTANT]\n},{begin:\"@(page|font-face)\",keywords:{$pattern:d,keyword:\"@page @font-face\"}},{\nbegin:\"@\",end:\"[{;]\",returnBegin:!0,keywords:{$pattern:/[a-z-]+/,\nkeyword:\"and or not only\",attribute:t.join(\" \")},contains:[{begin:d,\nclassName:\"keyword\"},{begin:/[a-z-]+(?=:)/,className:\"attribute\"\n},c,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,n.HEXCOLOR,n.CSS_NUMBER_MODE]}]}}\n})());hljs.registerLanguage(\"diff\",(()=>{\"use strict\";function e(...e){\nreturn\"(\"+((e=>{const n=e[e.length-1]\n;return\"object\"==typeof n&&n.constructor===Object?(e.splice(e.length-1,1),n):{}\n})(e).capture?\"\":\"?:\")+e.map((e=>{return(n=e)?\"string\"==typeof n?n:n.source:null\n;var n})).join(\"|\")+\")\"}return n=>({name:\"Diff\",aliases:[\"patch\"],contains:[{\nclassName:\"meta\",relevance:10,\nmatch:e(/^@@ +-\\d+,\\d+ +\\+\\d+,\\d+ +@@/,/^\\*\\*\\* +\\d+,\\d+ +\\*\\*\\*\\*$/,/^--- +\\d+,\\d+ +----$/)\n},{className:\"comment\",variants:[{\nbegin:e(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\\*{3} /,/^\\+{3}/,/^diff --git/),\nend:/$/},{match:/^\\*{15}$/}]},{className:\"addition\",begin:/^\\+/,end:/$/},{\nclassName:\"deletion\",begin:/^-/,end:/$/},{className:\"addition\",begin:/^!/,\nend:/$/}]})})());hljs.registerLanguage(\"plaintext\",(()=>{\"use strict\";return t=>({\nname:\"Plain text\",aliases:[\"text\",\"txt\"],disableAutodetect:!0})})());hljs.registerLanguage(\"ruby\",(()=>{\"use strict\";function e(e){\nreturn n(\"(?=\",e,\")\")}function n(...e){return e.map((e=>{\nreturn(n=e)?\"string\"==typeof n?n:n.source:null;var n})).join(\"\")}return a=>{\nconst i=\"([a-zA-Z_]\\\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\\\*\\\\*|[-/+%^&*~`|]|\\\\[\\\\]=?)\",s={\nkeyword:\"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor __FILE__\",\nbuilt_in:\"proc lambda\",literal:\"true false nil\"},r={className:\"doctag\",\nbegin:\"@[A-Za-z]+\"},b={begin:\"#<\",end:\">\"},c=[a.COMMENT(\"#\",\"$\",{contains:[r]\n}),a.COMMENT(\"^=begin\",\"^=end\",{contains:[r],relevance:10\n}),a.COMMENT(\"^__END__\",\"\\\\n$\")],t={className:\"subst\",begin:/#\\{/,end:/\\}/,\nkeywords:s},g={className:\"string\",contains:[a.BACKSLASH_ESCAPE,t],variants:[{\nbegin:/'/,end:/'/},{begin:/\"/,end:/\"/},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\\(/,\nend:/\\)/},{begin:/%[qQwWx]?\\[/,end:/\\]/},{begin:/%[qQwWx]?\\{/,end:/\\}/},{\nbegin:/%[qQwWx]?</,end:/>/},{begin:/%[qQwWx]?\\//,end:/\\//},{begin:/%[qQwWx]?%/,\nend:/%/},{begin:/%[qQwWx]?-/,end:/-/},{begin:/%[qQwWx]?\\|/,end:/\\|/},{\nbegin:/\\B\\?(\\\\\\d{1,3})/},{begin:/\\B\\?(\\\\x[A-Fa-f0-9]{1,2})/},{\nbegin:/\\B\\?(\\\\u\\{?[A-Fa-f0-9]{1,6}\\}?)/},{\nbegin:/\\B\\?(\\\\M-\\\\C-|\\\\M-\\\\c|\\\\c\\\\M-|\\\\M-|\\\\C-\\\\M-)[\\x20-\\x7e]/},{\nbegin:/\\B\\?\\\\(c|C-)[\\x20-\\x7e]/},{begin:/\\B\\?\\\\?\\S/},{\nbegin:n(/<<[-~]?'?/,e(/(\\w+)(?=\\W)[^\\n]*\\n(?:[^\\n]*\\n)*?\\s*\\1\\b/)),\ncontains:[a.END_SAME_AS_BEGIN({begin:/(\\w+)/,end:/(\\w+)/,\ncontains:[a.BACKSLASH_ESCAPE,t]})]}]},d=\"[0-9](_?[0-9])*\",l={className:\"number\",\nrelevance:0,variants:[{\nbegin:`\\\\b([1-9](_?[0-9])*|0)(\\\\.(${d}))?([eE][+-]?(${d})|r)?i?\\\\b`},{\nbegin:\"\\\\b0[dD][0-9](_?[0-9])*r?i?\\\\b\"},{begin:\"\\\\b0[bB][0-1](_?[0-1])*r?i?\\\\b\"\n},{begin:\"\\\\b0[oO][0-7](_?[0-7])*r?i?\\\\b\"},{\nbegin:\"\\\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\\\b\"},{\nbegin:\"\\\\b0(_?[0-7])+r?i?\\\\b\"}]},o={className:\"params\",begin:\"\\\\(\",end:\"\\\\)\",\nendsParent:!0,keywords:s},_=[g,{className:\"class\",beginKeywords:\"class module\",\nend:\"$|;\",illegal:/=/,contains:[a.inherit(a.TITLE_MODE,{\nbegin:\"[A-Za-z_]\\\\w*(::\\\\w+)*(\\\\?|!)?\"}),{begin:\"<\\\\s*\",contains:[{\nbegin:\"(\"+a.IDENT_RE+\"::)?\"+a.IDENT_RE,relevance:0}]}].concat(c)},{\nclassName:\"function\",begin:n(/def\\s+/,e(i+\"\\\\s*(\\\\(|;|$)\")),relevance:0,\nkeywords:\"def\",end:\"$|;\",contains:[a.inherit(a.TITLE_MODE,{begin:i\n}),o].concat(c)},{begin:a.IDENT_RE+\"::\"},{className:\"symbol\",\nbegin:a.UNDERSCORE_IDENT_RE+\"(!|\\\\?)?:\",relevance:0},{className:\"symbol\",\nbegin:\":(?!\\\\s)\",contains:[g,{begin:i}],relevance:0},l,{className:\"variable\",\nbegin:\"(\\\\$\\\\W)|((\\\\$|@@?)(\\\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])\"},{\nclassName:\"params\",begin:/\\|/,end:/\\|/,relevance:0,keywords:s},{\nbegin:\"(\"+a.RE_STARTERS_RE+\"|unless)\\\\s*\",keywords:\"unless\",contains:[{\nclassName:\"regexp\",contains:[a.BACKSLASH_ESCAPE,t],illegal:/\\n/,variants:[{\nbegin:\"/\",end:\"/[a-z]*\"},{begin:/%r\\{/,end:/\\}[a-z]*/},{begin:\"%r\\\\(\",\nend:\"\\\\)[a-z]*\"},{begin:\"%r!\",end:\"![a-z]*\"},{begin:\"%r\\\\[\",end:\"\\\\][a-z]*\"}]\n}].concat(b,c),relevance:0}].concat(b,c);t.contains=_,o.contains=_;const E=[{\nbegin:/^\\s*=>/,starts:{end:\"$\",contains:_}},{className:\"meta\",\nbegin:\"^([>?]>|[\\\\w#]+\\\\(\\\\w+\\\\):\\\\d+:\\\\d+>|(\\\\w+-)?\\\\d+\\\\.\\\\d+\\\\.\\\\d+(p\\\\d+)?[^\\\\d][^>]+>)(?=[ ])\",\nstarts:{end:\"$\",contains:_}}];return c.unshift(b),{name:\"Ruby\",\naliases:[\"rb\",\"gemspec\",\"podspec\",\"thor\",\"irb\"],keywords:s,illegal:/\\/\\*/,\ncontains:[a.SHEBANG({binary:\"ruby\"})].concat(E).concat(c).concat(_)}}})());hljs.registerLanguage(\"yaml\",(()=>{\"use strict\";return e=>{\nconst n=\"true false yes no null\",a=\"[\\\\w#;/?:@&=+$,.~*'()[\\\\]]+\",s={\nclassName:\"string\",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/\"/,end:/\"/\n},{begin:/\\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:\"template-variable\",\nvariants:[{begin:/\\{\\{/,end:/\\}\\}/},{begin:/%\\{/,end:/\\}/}]}]},i=e.inherit(s,{\nvariants:[{begin:/'/,end:/'/},{begin:/\"/,end:/\"/},{begin:/[^\\s,{}[\\]]+/}]}),l={\nend:\",\",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},t={begin:/\\{/,\nend:/\\}/,contains:[l],illegal:\"\\\\n\",relevance:0},g={begin:\"\\\\[\",end:\"\\\\]\",\ncontains:[l],illegal:\"\\\\n\",relevance:0},b=[{className:\"attr\",variants:[{\nbegin:\"\\\\w[\\\\w :\\\\/.-]*:(?=[ \\t]|$)\"},{begin:'\"\\\\w[\\\\w :\\\\/.-]*\":(?=[ \\t]|$)'},{\nbegin:\"'\\\\w[\\\\w :\\\\/.-]*':(?=[ \\t]|$)\"}]},{className:\"meta\",begin:\"^---\\\\s*$\",\nrelevance:10},{className:\"string\",\nbegin:\"[\\\\|>]([1-9]?[+-])?[ ]*\\\\n( +)[^ ][^\\\\n]*\\\\n(\\\\2[^\\\\n]+\\\\n?)*\"},{\nbegin:\"<%[%=-]?\",end:\"[%-]?%>\",subLanguage:\"ruby\",excludeBegin:!0,excludeEnd:!0,\nrelevance:0},{className:\"type\",begin:\"!\\\\w+!\"+a},{className:\"type\",\nbegin:\"!<\"+a+\">\"},{className:\"type\",begin:\"!\"+a},{className:\"type\",begin:\"!!\"+a\n},{className:\"meta\",begin:\"&\"+e.UNDERSCORE_IDENT_RE+\"$\"},{className:\"meta\",\nbegin:\"\\\\*\"+e.UNDERSCORE_IDENT_RE+\"$\"},{className:\"bullet\",begin:\"-(?=[ ]|$)\",\nrelevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{\nclassName:\"number\",\nbegin:\"\\\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\\\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\\\.[0-9]*)?([ \\\\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\\\b\"\n},{className:\"number\",begin:e.C_NUMBER_RE+\"\\\\b\",relevance:0},t,g,s],c=[...b]\n;return c.pop(),c.push(i),l.contains=c,{name:\"YAML\",case_insensitive:!0,\naliases:[\"yml\"],contains:b}}})());hljs.registerLanguage(\"rust\",(()=>{\"use strict\";function e(...e){\nreturn e.map((e=>{return(t=e)?\"string\"==typeof t?t:t.source:null;var t\n})).join(\"\")}return t=>{const n={className:\"title.function.invoke\",relevance:0,\nbegin:e(/\\b/,/(?!let\\b)/,t.IDENT_RE,(a=/\\s*\\(/,e(\"(?=\",a,\")\")))};var a\n;const r=\"([ui](8|16|32|64|128|size)|f(32|64))?\",i=[\"drop \",\"Copy\",\"Send\",\"Sized\",\"Sync\",\"Drop\",\"Fn\",\"FnMut\",\"FnOnce\",\"ToOwned\",\"Clone\",\"Debug\",\"PartialEq\",\"PartialOrd\",\"Eq\",\"Ord\",\"AsRef\",\"AsMut\",\"Into\",\"From\",\"Default\",\"Iterator\",\"Extend\",\"IntoIterator\",\"DoubleEndedIterator\",\"ExactSizeIterator\",\"SliceConcatExt\",\"ToString\",\"assert!\",\"assert_eq!\",\"bitflags!\",\"bytes!\",\"cfg!\",\"col!\",\"concat!\",\"concat_idents!\",\"debug_assert!\",\"debug_assert_eq!\",\"env!\",\"panic!\",\"file!\",\"format!\",\"format_args!\",\"include_bin!\",\"include_str!\",\"line!\",\"local_data_key!\",\"module_path!\",\"option_env!\",\"print!\",\"println!\",\"select!\",\"stringify!\",\"try!\",\"unimplemented!\",\"unreachable!\",\"vec!\",\"write!\",\"writeln!\",\"macro_rules!\",\"assert_ne!\",\"debug_assert_ne!\"]\n;return{name:\"Rust\",aliases:[\"rs\"],keywords:{$pattern:t.IDENT_RE+\"!?\",\ntype:[\"i8\",\"i16\",\"i32\",\"i64\",\"i128\",\"isize\",\"u8\",\"u16\",\"u32\",\"u64\",\"u128\",\"usize\",\"f32\",\"f64\",\"str\",\"char\",\"bool\",\"Box\",\"Option\",\"Result\",\"String\",\"Vec\"],\nkeyword:[\"abstract\",\"as\",\"async\",\"await\",\"become\",\"box\",\"break\",\"const\",\"continue\",\"crate\",\"do\",\"dyn\",\"else\",\"enum\",\"extern\",\"false\",\"final\",\"fn\",\"for\",\"if\",\"impl\",\"in\",\"let\",\"loop\",\"macro\",\"match\",\"mod\",\"move\",\"mut\",\"override\",\"priv\",\"pub\",\"ref\",\"return\",\"self\",\"Self\",\"static\",\"struct\",\"super\",\"trait\",\"true\",\"try\",\"type\",\"typeof\",\"unsafe\",\"unsized\",\"use\",\"virtual\",\"where\",\"while\",\"yield\"],\nliteral:[\"true\",\"false\",\"Some\",\"None\",\"Ok\",\"Err\"],built_in:i},illegal:\"</\",\ncontains:[t.C_LINE_COMMENT_MODE,t.COMMENT(\"/\\\\*\",\"\\\\*/\",{contains:[\"self\"]\n}),t.inherit(t.QUOTE_STRING_MODE,{begin:/b?\"/,illegal:null}),{\nclassName:\"string\",variants:[{begin:/b?r(#*)\"(.|\\n)*?\"\\1(?!#)/},{\nbegin:/b?'\\\\?(x\\w{2}|u\\w{4}|U\\w{8}|.)'/}]},{className:\"symbol\",\nbegin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:\"number\",variants:[{\nbegin:\"\\\\b0b([01_]+)\"+r},{begin:\"\\\\b0o([0-7_]+)\"+r},{\nbegin:\"\\\\b0x([A-Fa-f0-9_]+)\"+r},{\nbegin:\"\\\\b(\\\\d[\\\\d_]*(\\\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)\"+r}],relevance:0},{\nbegin:[/fn/,/\\s+/,t.UNDERSCORE_IDENT_RE],className:{1:\"keyword\",\n3:\"title.function\"}},{className:\"meta\",begin:\"#!?\\\\[\",end:\"\\\\]\",contains:[{\nclassName:\"string\",begin:/\"/,end:/\"/}]},{\nbegin:[/let/,/\\s+/,/(?:mut\\s+)?/,t.UNDERSCORE_IDENT_RE],className:{1:\"keyword\",\n3:\"keyword\",4:\"variable\"}},{\nbegin:[/for/,/\\s+/,t.UNDERSCORE_IDENT_RE,/\\s+/,/in/],className:{1:\"keyword\",\n3:\"variable\",5:\"keyword\"}},{begin:[/type/,/\\s+/,t.UNDERSCORE_IDENT_RE],\nclassName:{1:\"keyword\",3:\"title.class\"}},{\nbegin:[/(?:trait|enum|struct|union|impl|for)/,/\\s+/,t.UNDERSCORE_IDENT_RE],\nclassName:{1:\"keyword\",3:\"title.class\"}},{begin:t.IDENT_RE+\"::\",keywords:{\nkeyword:\"Self\",built_in:i}},{className:\"punctuation\",begin:\"->\"},n]}}})());hljs.registerLanguage(\"javascript\",(()=>{\"use strict\"\n;const e=\"[A-Za-z$_][0-9A-Za-z$_]*\",n=[\"as\",\"in\",\"of\",\"if\",\"for\",\"while\",\"finally\",\"var\",\"new\",\"function\",\"do\",\"return\",\"void\",\"else\",\"break\",\"catch\",\"instanceof\",\"with\",\"throw\",\"case\",\"default\",\"try\",\"switch\",\"continue\",\"typeof\",\"delete\",\"let\",\"yield\",\"const\",\"class\",\"debugger\",\"async\",\"await\",\"static\",\"import\",\"from\",\"export\",\"extends\"],a=[\"true\",\"false\",\"null\",\"undefined\",\"NaN\",\"Infinity\"],t=[\"Intl\",\"DataView\",\"Number\",\"Math\",\"Date\",\"String\",\"RegExp\",\"Object\",\"Function\",\"Boolean\",\"Error\",\"Symbol\",\"Set\",\"Map\",\"WeakSet\",\"WeakMap\",\"Proxy\",\"Reflect\",\"JSON\",\"Promise\",\"Float64Array\",\"Int16Array\",\"Int32Array\",\"Int8Array\",\"Uint16Array\",\"Uint32Array\",\"Float32Array\",\"Array\",\"Uint8Array\",\"Uint8ClampedArray\",\"ArrayBuffer\",\"BigInt64Array\",\"BigUint64Array\",\"BigInt\"],s=[\"EvalError\",\"InternalError\",\"RangeError\",\"ReferenceError\",\"SyntaxError\",\"TypeError\",\"URIError\"],r=[\"setInterval\",\"setTimeout\",\"clearInterval\",\"clearTimeout\",\"require\",\"exports\",\"eval\",\"isFinite\",\"isNaN\",\"parseFloat\",\"parseInt\",\"decodeURI\",\"decodeURIComponent\",\"encodeURI\",\"encodeURIComponent\",\"escape\",\"unescape\"],i=[\"arguments\",\"this\",\"super\",\"console\",\"window\",\"document\",\"localStorage\",\"module\",\"global\"],c=[].concat(r,t,s)\n;function o(e){return l(\"(?=\",e,\")\")}function l(...e){return e.map((e=>{\nreturn(n=e)?\"string\"==typeof n?n:n.source:null;var n})).join(\"\")}return b=>{\nconst g=e,d={begin:/<[A-Za-z0-9\\\\._:-]+/,end:/\\/[A-Za-z0-9\\\\._:-]+>|\\/>/,\nisTrulyOpeningTag:(e,n)=>{const a=e[0].length+e.index,t=e.input[a]\n;\"<\"!==t?\">\"===t&&(((e,{after:n})=>{const a=\"</\"+e[0].slice(1)\n;return-1!==e.input.indexOf(a,n)})(e,{after:a\n})||n.ignoreMatch()):n.ignoreMatch()}},u={$pattern:e,keyword:n,literal:a,\nbuilt_in:c,\"variable.language\":i\n},m=\"\\\\.([0-9](_?[0-9])*)\",E=\"0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*\",y={\nclassName:\"number\",variants:[{\nbegin:`(\\\\b(${E})((${m})|\\\\.)?|(${m}))[eE][+-]?([0-9](_?[0-9])*)\\\\b`},{\nbegin:`\\\\b(${E})\\\\b((${m})\\\\b|\\\\.)?|(${m})\\\\b`},{\nbegin:\"\\\\b(0|[1-9](_?[0-9])*)n\\\\b\"},{\nbegin:\"\\\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\\\b\"},{\nbegin:\"\\\\b0[bB][0-1](_?[0-1])*n?\\\\b\"},{begin:\"\\\\b0[oO][0-7](_?[0-7])*n?\\\\b\"},{\nbegin:\"\\\\b0[0-7]+n?\\\\b\"}],relevance:0},_={className:\"subst\",begin:\"\\\\$\\\\{\",\nend:\"\\\\}\",keywords:u,contains:[]},N={begin:\"html`\",end:\"\",starts:{end:\"`\",\nreturnEnd:!1,contains:[b.BACKSLASH_ESCAPE,_],subLanguage:\"xml\"}},f={\nbegin:\"css`\",end:\"\",starts:{end:\"`\",returnEnd:!1,\ncontains:[b.BACKSLASH_ESCAPE,_],subLanguage:\"css\"}},A={className:\"string\",\nbegin:\"`\",end:\"`\",contains:[b.BACKSLASH_ESCAPE,_]},v={className:\"comment\",\nvariants:[b.COMMENT(/\\/\\*\\*(?!\\/)/,\"\\\\*/\",{relevance:0,contains:[{\nbegin:\"(?=@[A-Za-z]+)\",relevance:0,contains:[{className:\"doctag\",\nbegin:\"@[A-Za-z]+\"},{className:\"type\",begin:\"\\\\{\",end:\"\\\\}\",excludeEnd:!0,\nexcludeBegin:!0,relevance:0},{className:\"variable\",begin:g+\"(?=\\\\s*(-)|$)\",\nendsParent:!0,relevance:0},{begin:/(?=[^\\n])\\s/,relevance:0}]}]\n}),b.C_BLOCK_COMMENT_MODE,b.C_LINE_COMMENT_MODE]\n},p=[b.APOS_STRING_MODE,b.QUOTE_STRING_MODE,N,f,A,y,b.REGEXP_MODE]\n;_.contains=p.concat({begin:/\\{/,end:/\\}/,keywords:u,contains:[\"self\"].concat(p)\n});const h=[].concat(v,_.contains),S=h.concat([{begin:/\\(/,end:/\\)/,keywords:u,\ncontains:[\"self\"].concat(h)}]),w={className:\"params\",begin:/\\(/,end:/\\)/,\nexcludeBegin:!0,excludeEnd:!0,keywords:u,contains:S},R={variants:[{\nmatch:[/class/,/\\s+/,g],scope:{1:\"keyword\",3:\"title.class\"}},{\nmatch:[/extends/,/\\s+/,l(g,\"(\",l(/\\./,g),\")*\")],scope:{1:\"keyword\",\n3:\"title.class.inherited\"}}]},O={relevance:0,\nmatch:/\\b[A-Z][a-z]+([A-Z][a-z]+)*/,className:\"title.class\",keywords:{\n_:[...t,...s]}},I={variants:[{match:[/function/,/\\s+/,g,/(?=\\s*\\()/]},{\nmatch:[/function/,/\\s*(?=\\()/]}],className:{1:\"keyword\",3:\"title.function\"},\nlabel:\"func.def\",contains:[w],illegal:/%/},T={\nmatch:l(/\\b/,(x=[...r,\"super\"],l(\"(?!\",x.join(\"|\"),\")\")),g,o(/\\(/)),\nclassName:\"title.function\",relevance:0};var x;const M={\nbegin:l(/\\./,o(l(g,/(?![0-9A-Za-z$_(])/))),end:g,excludeBegin:!0,\nkeywords:\"prototype\",className:\"property\",relevance:0},k={\nmatch:[/get|set/,/\\s+/,g,/(?=\\()/],className:{1:\"keyword\",3:\"title.function\"},\ncontains:[{begin:/\\(\\)/},w]\n},C=\"(\\\\([^()]*(\\\\([^()]*(\\\\([^()]*\\\\)[^()]*)*\\\\)[^()]*)*\\\\)|\"+b.UNDERSCORE_IDENT_RE+\")\\\\s*=>\",B={\nmatch:[/const|var|let/,/\\s+/,g,/\\s*/,/=\\s*/,o(C)],className:{1:\"keyword\",\n3:\"title.function\"},contains:[w]};return{name:\"Javascript\",\naliases:[\"js\",\"jsx\",\"mjs\",\"cjs\"],keywords:u,exports:{PARAMS_CONTAINS:S},\nillegal:/#(?![$_A-z])/,contains:[b.SHEBANG({label:\"shebang\",binary:\"node\",\nrelevance:5}),{label:\"use_strict\",className:\"meta\",relevance:10,\nbegin:/^\\s*['\"]use (strict|asm)['\"]/\n},b.APOS_STRING_MODE,b.QUOTE_STRING_MODE,N,f,A,v,y,O,{className:\"attr\",\nbegin:g+o(\":\"),relevance:0},B,{\nbegin:\"(\"+b.RE_STARTERS_RE+\"|\\\\b(case|return|throw)\\\\b)\\\\s*\",\nkeywords:\"return throw case\",relevance:0,contains:[v,b.REGEXP_MODE,{\nclassName:\"function\",begin:C,returnBegin:!0,end:\"\\\\s*=>\",contains:[{\nclassName:\"params\",variants:[{begin:b.UNDERSCORE_IDENT_RE,relevance:0},{\nclassName:null,begin:/\\(\\s*\\)/,skip:!0},{begin:/\\(/,end:/\\)/,excludeBegin:!0,\nexcludeEnd:!0,keywords:u,contains:S}]}]},{begin:/,/,relevance:0},{match:/\\s+/,\nrelevance:0},{variants:[{begin:\"<>\",end:\"</>\"},{begin:d.begin,\n\"on:begin\":d.isTrulyOpeningTag,end:d.end}],subLanguage:\"xml\",contains:[{\nbegin:d.begin,end:d.end,skip:!0,contains:[\"self\"]}]}]},I,{\nbeginKeywords:\"while if switch catch for\"},{\nbegin:\"\\\\b(?!function)\"+b.UNDERSCORE_IDENT_RE+\"\\\\([^()]*(\\\\([^()]*(\\\\([^()]*\\\\)[^()]*)*\\\\)[^()]*)*\\\\)\\\\s*\\\\{\",\nreturnBegin:!0,label:\"func.def\",contains:[w,b.inherit(b.TITLE_MODE,{begin:g,\nclassName:\"title.function\"})]},{match:/\\.\\.\\./,relevance:0},M,{match:\"\\\\$\"+g,\nrelevance:0},{match:[/\\bconstructor(?=\\s*\\()/],className:{1:\"title.function\"},\ncontains:[w]},T,{relevance:0,match:/\\b[A-Z][A-Z_]+\\b/,\nclassName:\"variable.constant\"},R,k,{match:/\\$[(.]/}]}}})());hljs.registerLanguage(\"json\",(()=>{\"use strict\";return e=>({name:\"JSON\",\ncontains:[{className:\"attr\",begin:/\"(\\\\.|[^\\\\\"\\r\\n])*\"(?=\\s*:)/,relevance:1.01\n},{match:/[{}[\\],:]/,className:\"punctuation\",relevance:0},e.QUOTE_STRING_MODE,{\nbeginKeywords:\"true false null\"\n},e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],illegal:\"\\\\S\"})\n})());"
  },
  {
    "path": "docs/theme/index.hbs",
    "content": "<!DOCTYPE HTML>\n<html lang=\"{{ language }}\" class=\"sidebar-visible no-js {{ default_theme }}\">\n    <head>\n        <!-- Book generated using mdBook -->\n        <meta charset=\"UTF-8\">\n        <title>{{ title }}</title>\n        {{#if is_print }}\n        <meta name=\"robots\" content=\"noindex\" />\n        {{/if}}\n        {{#if base_url}}\n        <base href=\"{{ base_url }}\">\n        {{/if}}\n\n\n        <!-- Custom HTML head -->\n        {{> head}}\n\n        <meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n        <meta name=\"description\" content=\"{{ description }}\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n        <meta name=\"theme-color\" content=\"#ffffff\" />\n\n        {{#if favicon_svg}}\n        <link rel=\"icon\" href=\"{{ path_to_root }}favicon.svg\">\n        {{/if}}\n        {{#if favicon_png}}\n        <link rel=\"shortcut icon\" href=\"{{ path_to_root }}favicon.png\">\n        {{/if}}\n        <link rel=\"stylesheet\" href=\"{{ path_to_root }}css/variables.css\">\n        <link rel=\"stylesheet\" href=\"{{ path_to_root }}css/general.css\">\n        <link rel=\"stylesheet\" href=\"{{ path_to_root }}css/chrome.css\">\n        {{#if print_enable}}\n        <link rel=\"stylesheet\" href=\"{{ path_to_root }}css/print.css\" media=\"print\">\n        {{/if}}\n\n        <!-- Fonts -->\n        <link rel=\"stylesheet\" href=\"{{ path_to_root }}FontAwesome/css/font-awesome.css\">\n        {{#if copy_fonts}}\n        <link rel=\"stylesheet\" href=\"{{ path_to_root }}fonts/fonts.css\">\n        {{/if}}\n\n        <!-- Highlight.js Stylesheets -->\n        <link rel=\"stylesheet\" href=\"{{ path_to_root }}highlight.css\">\n        <link rel=\"stylesheet\" href=\"{{ path_to_root }}tomorrow-night.css\">\n        <link rel=\"stylesheet\" href=\"{{ path_to_root }}ayu-highlight.css\">\n\n        <!-- Custom theme stylesheets -->\n        {{#each additional_css}}\n        <link rel=\"stylesheet\" href=\"{{ ../path_to_root }}{{ this }}\">\n        {{/each}}\n\n        {{#if mathjax_support}}\n        <!-- MathJax -->\n        <script async type=\"text/javascript\" src=\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML\"></script>\n        {{/if}}\n    </head>\n    <body>\n        <!-- Provide site root to javascript -->\n        <script type=\"text/javascript\">\n            var path_to_root = \"{{ path_to_root }}\";\n            var default_theme = window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"{{ preferred_dark_theme }}\" : \"{{ default_theme }}\";\n        </script>\n\n        <!-- Work around some values being stored in localStorage wrapped in quotes -->\n        <script type=\"text/javascript\">\n            try {\n                var theme = localStorage.getItem('mdbook-theme');\n                var sidebar = localStorage.getItem('mdbook-sidebar');\n\n                if (theme.startsWith('\"') && theme.endsWith('\"')) {\n                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));\n                }\n\n                if (sidebar.startsWith('\"') && sidebar.endsWith('\"')) {\n                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));\n                }\n            } catch (e) { }\n        </script>\n\n        <!-- Set the theme before any content is loaded, prevents flash -->\n        <script type=\"text/javascript\">\n            var theme;\n            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }\n            if (theme === null || theme === undefined) { theme = default_theme; }\n            var html = document.querySelector('html');\n            html.classList.remove('no-js')\n            html.classList.remove('{{ default_theme }}')\n            html.classList.add(theme);\n            html.classList.add('js');\n        </script>\n\n        <!-- Hide / unhide sidebar before it is displayed -->\n        <script type=\"text/javascript\">\n            var html = document.querySelector('html');\n            var sidebar = 'hidden';\n            if (document.body.clientWidth >= 1080) {\n                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }\n                sidebar = sidebar || 'visible';\n            }\n            html.classList.remove('sidebar-visible');\n            html.classList.add(\"sidebar-\" + sidebar);\n        </script>\n\n        <nav id=\"sidebar\" class=\"sidebar\" aria-label=\"Table of contents\">\n            <div class=\"sidebar-scrollbox\">\n                {{#toc}}{{/toc}}\n            </div>\n            <div id=\"sidebar-resize-handle\" class=\"sidebar-resize-handle\"></div>\n        </nav>\n\n        <div id=\"page-wrapper\" class=\"page-wrapper\">\n\n            <div class=\"page\">\n                {{> header}}\n                <div id=\"menu-bar-hover-placeholder\"></div>\n                <div id=\"menu-bar\" class=\"menu-bar sticky bordered\">\n                    <div class=\"left-buttons\">\n                        <button id=\"sidebar-toggle\" class=\"icon-button\" type=\"button\" title=\"Toggle Table of Contents\" aria-label=\"Toggle Table of Contents\" aria-controls=\"sidebar\">\n                            <i class=\"fa fa-bars\"></i>\n                        </button>\n                        <button id=\"theme-toggle\" class=\"icon-button\" type=\"button\" title=\"Change theme\" aria-label=\"Change theme\" aria-haspopup=\"true\" aria-expanded=\"false\" aria-controls=\"theme-list\">\n                            <i class=\"fa fa-paint-brush\"></i>\n                        </button>\n                        <ul id=\"theme-list\" class=\"theme-popup\" aria-label=\"Themes\" role=\"menu\">\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"light\">{{ theme_option \"Light\" }}</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"rust\">{{ theme_option \"Rust\" }}</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"coal\">{{ theme_option \"Coal\" }}</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"navy\">{{ theme_option \"Navy\" }}</button></li>\n                            <li role=\"none\"><button role=\"menuitem\" class=\"theme\" id=\"ayu\">{{ theme_option \"Ayu\" }}</button></li>\n                        </ul>\n                        {{#if search_enabled}}\n                        <button id=\"search-toggle\" class=\"icon-button\" type=\"button\" title=\"Search. (Shortkey: s)\" aria-label=\"Toggle Searchbar\" aria-expanded=\"false\" aria-keyshortcuts=\"S\" aria-controls=\"searchbar\">\n                            <i class=\"fa fa-search\"></i>\n                        </button>\n                        {{/if}}\n                    </div>\n\n                    <h1 class=\"menu-title\">{{ book_title }}</h1>\n\n                    <div class=\"right-buttons\">\n                        {{#if print_enable}}\n                        <a href=\"{{ path_to_root }}print.html\" title=\"Print this book\" aria-label=\"Print this book\">\n                            <i id=\"print-button\" class=\"fa fa-print\"></i>\n                        </a>\n                        {{/if}}\n                        {{#if git_repository_url}}\n                        <a href=\"{{git_repository_url}}\" title=\"Git repository\" aria-label=\"Git repository\">\n                            <i id=\"git-repository-button\" class=\"fa {{git_repository_icon}}\"></i>\n                        </a>\n                        {{/if}}\n                        {{#if git_repository_edit_url}}\n                        <a href=\"{{git_repository_edit_url}}\" title=\"Suggest an edit\" aria-label=\"Suggest an edit\">\n                            <i id=\"git-edit-button\" class=\"fa fa-edit\"></i>\n                        </a>\n                        {{/if}}\n\n                    </div>\n                </div>\n\n                {{#if search_enabled}}\n                <div id=\"search-wrapper\" class=\"hidden\">\n                    <form id=\"searchbar-outer\" class=\"searchbar-outer\">\n                        <input type=\"search\" id=\"searchbar\" name=\"searchbar\" placeholder=\"Search this book ...\" aria-controls=\"searchresults-outer\" aria-describedby=\"searchresults-header\">\n                    </form>\n                    <div id=\"searchresults-outer\" class=\"searchresults-outer hidden\">\n                        <div id=\"searchresults-header\" class=\"searchresults-header\"></div>\n                        <ul id=\"searchresults\">\n                        </ul>\n                    </div>\n                </div>\n                {{/if}}\n\n                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->\n                <script type=\"text/javascript\">\n                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');\n                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');\n                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {\n                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);\n                    });\n                </script>\n\n                <div id=\"content\" class=\"content\">\n                    <main>\n                        {{{ content }}}\n                    </main>\n\n                    <nav class=\"nav-wrapper\" aria-label=\"Page navigation\">\n                        <!-- Mobile navigation buttons -->\n                        {{#previous}}\n                            <a rel=\"prev\" href=\"{{ path_to_root }}{{link}}\" class=\"mobile-nav-chapters previous\" title=\"Previous chapter\" aria-label=\"Previous chapter\" aria-keyshortcuts=\"Left\">\n                                <i class=\"fa fa-angle-left\"></i>\n                            </a>\n                        {{/previous}}\n\n                        {{#next}}\n                            <a rel=\"next\" href=\"{{ path_to_root }}{{link}}\" class=\"mobile-nav-chapters next\" title=\"Next chapter\" aria-label=\"Next chapter\" aria-keyshortcuts=\"Right\">\n                                <i class=\"fa fa-angle-right\"></i>\n                            </a>\n                        {{/next}}\n\n                        <div style=\"clear: both\"></div>\n                    </nav>\n                </div>\n            </div>\n\n            <nav class=\"nav-wide-wrapper\" aria-label=\"Page navigation\">\n                {{#previous}}\n                    <a rel=\"prev\" href=\"{{ path_to_root }}{{link}}\" class=\"nav-chapters previous\" title=\"Previous chapter\" aria-label=\"Previous chapter\" aria-keyshortcuts=\"Left\">\n                        <i class=\"fa fa-angle-left\"></i>\n                    </a>\n                {{/previous}}\n\n                {{#next}}\n                    <a rel=\"next\" href=\"{{ path_to_root }}{{link}}\" class=\"nav-chapters next\" title=\"Next chapter\" aria-label=\"Next chapter\" aria-keyshortcuts=\"Right\">\n                        <i class=\"fa fa-angle-right\"></i>\n                    </a>\n                {{/next}}\n            </nav>\n\n        </div>\n\n        {{#if livereload}}\n        <!-- Livereload script (if served using the cli tool) -->\n        <script type=\"text/javascript\">\n            var socket = new WebSocket(\"{{{livereload}}}\");\n            socket.onmessage = function (event) {\n                if (event.data === \"reload\") {\n                    socket.close();\n                    location.reload();\n                }\n            };\n\n            window.onbeforeunload = function() {\n                socket.close();\n            }\n        </script>\n        {{/if}}\n\n        {{#if google_analytics}}\n        <!-- Google Analytics Tag -->\n        <script type=\"text/javascript\">\n            var localAddrs = [\"localhost\", \"127.0.0.1\", \"\"];\n\n            // make sure we don't activate google analytics if the developer is\n            // inspecting the book locally...\n            if (localAddrs.indexOf(document.location.hostname) === -1) {\n                (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n                (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n                m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n                })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');\n\n                ga('create', '{{google_analytics}}', 'auto');\n                ga('send', 'pageview');\n            }\n        </script>\n        {{/if}}\n\n        {{#if playground_line_numbers}}\n        <script type=\"text/javascript\">\n            window.playground_line_numbers = true;\n        </script>\n        {{/if}}\n\n        {{#if playground_copyable}}\n        <script type=\"text/javascript\">\n            window.playground_copyable = true;\n        </script>\n        {{/if}}\n\n        {{#if playground_js}}\n        <script src=\"{{ path_to_root }}ace.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n        <script src=\"{{ path_to_root }}editor.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n        <script src=\"{{ path_to_root }}mode-rust.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n        <script src=\"{{ path_to_root }}theme-dawn.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n        <script src=\"{{ path_to_root }}theme-tomorrow_night.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n        {{/if}}\n\n        {{#if search_js}}\n        <script src=\"{{ path_to_root }}elasticlunr.min.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n        <script src=\"{{ path_to_root }}mark.min.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n        <script src=\"{{ path_to_root }}searcher.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n        {{/if}}\n\n        <script src=\"{{ path_to_root }}clipboard.min.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n        <script src=\"{{ path_to_root }}highlight.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n        <script src=\"{{ path_to_root }}book.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n\n        <!-- Custom JS scripts -->\n        {{#each additional_js}}\n        <script type=\"text/javascript\" src=\"{{ ../path_to_root }}{{this}}\"></script>\n        {{/each}}\n\n        {{#if is_print}}\n        {{#if mathjax_support}}\n        <script type=\"text/javascript\">\n        window.addEventListener('load', function() {\n            MathJax.Hub.Register.StartupHook('End', function() {\n                window.setTimeout(window.print, 100);\n            });\n        });\n        </script>\n        {{else}}\n        <script type=\"text/javascript\">\n        window.addEventListener('load', function() {\n            window.setTimeout(window.print, 100);\n        });\n        </script>\n        {{/if}}\n        {{/if}}\n\n    </body>\n</html>\n"
  },
  {
    "path": "examples/data-structures/eww.scss",
    "content": "* {\n  all: unset;\n}\n\n.layout {\n  padding: 8px;\n  border: 1px solid black;\n  border-radius: 4px;\n  background-color: bisque;\n  font-size: 16px;\n  color: black;\n}\n\n.animalLayout {\n  margin: 0 4px;\n}\n\n.animal {\n  font-size: 24px;\n  transition: 0.2s;\n  border-radius: 4px;\n  background-color: rgba(0, 0, 0, 0);\n  border: 0 solid lightcoral;\n}\n\n.animal.selected {\n  background-color: rgba(0, 0, 0, 0.2);\n  border-width: 2px;\n}\n"
  },
  {
    "path": "examples/data-structures/eww.yuck",
    "content": "(defvar stringArray `[\n  \"🦝\",\n  \"🐱\",\n  \"🐵\",\n  \"🦁\",\n  \"🐹\",\n  \"🦊\"\n]`)\n\n(defvar object `{\n  \"🦝\": \"racoon\",\n  \"🐱\": \"cat\",\n  \"🐵\": \"ape\",\n  \"🦁\": \"lion\",\n  \"🐹\": \"hamster\",\n  \"🦊\": \"fox\"\n}`)\n\n; You could also create an array of objects:\n; (defvar objectArray `[{ \"emoji\": \"🦝\", \"name\": \"racoon\" }, { \"emoji\": \"🦊\", \"name\": \"fox\" }]`)\n\n(defvar selected `🦝`)\n\n(defwidget animalButton [emoji]\n  (box\n    :class \"animalLayout\"\n    (eventbox\n      :class `animal ${selected == emoji ? \"selected\" : \"\"}`\n      :cursor \"pointer\"\n      :onhover \"eww update selected=${emoji}\"\n      emoji\n    )\n  )\n)\n\n(defwidget animalRow []\n  (box\n    :class \"animals\"\n    :orientation \"horizontal\"\n    :halign \"center\"\n    (for animal in stringArray\n      (animalButton\n        :emoji animal\n      )\n    )\n  )\n)\n\n(defwidget currentAnimal []\n  (box\n    `${object[selected]} ${selected}`\n  )\n)\n\n(defwidget layout []\n  (box\n    :class \"layout\"\n    :orientation \"vertical\"\n    :halign \"center\"\n    (animalRow)\n    (currentAnimal)\n  )\n)\n\n(defwindow data-structures\n  :monitor 0\n  :exclusive false\n  :focusable none\n  :geometry (geometry\n    :anchor \"center\"\n  )\n  (layout)\n)\n"
  },
  {
    "path": "examples/eww-bar/eww.scss",
    "content": "* {\n  all: unset; // Unsets everything so you can style everything from scratch\n}\n\n// Global Styles\n.bar {\n  background-color: #3a3a3a;\n  color: #b0b4bc;\n  padding: 10px;\n}\n\n// Styles on classes (see eww.yuck for more information)\n\n.sidestuff slider {\n  all: unset;\n  color: #ffd5cd;\n}\n\n.metric scale trough highlight {\n  all: unset;\n  background-color: #D35D6E;\n  color: #000000;\n  border-radius: 10px;\n}\n\n.metric scale trough {\n  all: unset;\n  background-color: #4e4e4e;\n  border-radius: 50px;\n  min-height: 3px;\n  min-width: 50px;\n  margin-left: 10px;\n  margin-right: 20px;\n}\n\n.label-ram {\n  font-size: large;\n}\n\n.workspaces button:hover {\n  color: #D35D6E;\n}\n\n"
  },
  {
    "path": "examples/eww-bar/eww.yuck",
    "content": "(defwidget bar []\n  (centerbox :orientation \"h\"\n    (workspaces)\n    (music)\n    (sidestuff)))\n\n(defwidget sidestuff []\n  (box :class \"sidestuff\" :orientation \"h\" :space-evenly false :halign \"end\"\n    (metric :label \"🔊\"\n            :value volume\n            :onchange \"amixer -D pulse sset Master {}%\")\n    (metric :label \"\"\n            :value {EWW_RAM.used_mem_perc}\n            :onchange \"\")\n    (metric :label \"💾\"\n            :value {round((1 - (EWW_DISK[\"/\"].free / EWW_DISK[\"/\"].total)) * 100, 0)}\n            :onchange \"\")\n    time))\n\n(defwidget workspaces []\n  (box :class \"workspaces\"\n       :orientation \"h\"\n       :space-evenly true\n       :halign \"start\"\n       :spacing 10\n    (button :onclick \"wmctrl -s 0\" 1)\n    (button :onclick \"wmctrl -s 1\" 2)\n    (button :onclick \"wmctrl -s 2\" 3)\n    (button :onclick \"wmctrl -s 3\" 4)\n    (button :onclick \"wmctrl -s 4\" 5)\n    (button :onclick \"wmctrl -s 5\" 6)\n    (button :onclick \"wmctrl -s 6\" 7)\n    (button :onclick \"wmctrl -s 7\" 8)\n    (button :onclick \"wmctrl -s 8\" 9)))\n\n(defwidget music []\n  (box :class \"music\"\n       :orientation \"h\"\n       :space-evenly false\n       :halign \"center\"\n    {music != \"\" ? \"🎵${music}\" : \"\"}))\n\n\n(defwidget metric [label value onchange]\n  (box :orientation \"h\"\n       :class \"metric\"\n       :space-evenly false\n    (box :class \"label\" label)\n    (scale :min 0\n           :max 101\n           :active {onchange != \"\"}\n           :value value\n           :onchange onchange)))\n\n\n\n(deflisten music :initial \"\"\n  \"playerctl --follow metadata --format '{{ artist }} - {{ title }}' || true\")\n\n(defpoll volume :interval \"1s\"\n  \"scripts/getvol\")\n\n(defpoll time :interval \"10s\"\n  \"date '+%H:%M %b %d, %Y'\")\n\n(defwindow bar\n  :monitor 0\n  :windowtype \"dock\"\n  :geometry (geometry :x \"0%\"\n                      :y \"0%\"\n                      :width \"90%\"\n                      :height \"10px\"\n                      :anchor \"top center\")\n  :reserve (struts :side \"top\" :distance \"4%\")\n  (bar))\n"
  },
  {
    "path": "examples/eww-bar/scripts/getvol",
    "content": "#!/bin/sh\n\nif command -v pamixer &>/dev/null; then\n    if [ true == $(pamixer --get-mute) ]; then\n        echo 0\n        exit\n    else\n        pamixer --get-volume\n    fi\nelse\n    amixer -D pulse sget Master | awk -F '[^0-9]+' '/Left:/{print $3}'\nfi\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  inputs = {\n    flake-compat.url = \"github:edolstra/flake-compat/refs/pull/65/head\";\n    nixpkgs.url = \"github:nixos/nixpkgs/nixpkgs-unstable\";\n    rust-overlay = {\n      url = \"github:oxalica/rust-overlay\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n  };\n\n  outputs =\n    {\n      self,\n      nixpkgs,\n      rust-overlay,\n      flake-compat,\n    }:\n    let\n      overlays = [\n        (import rust-overlay)\n        self.overlays.default\n      ];\n      pkgsFor = system: import nixpkgs { inherit system overlays; };\n\n      targetSystems = [\n        \"aarch64-linux\"\n        \"x86_64-linux\"\n      ];\n      mkRustToolchain = pkgs: pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;\n    in\n    {\n      overlays.default = final: prev: { inherit (self.packages.${prev.system}) eww eww-wayland; };\n\n      packages = nixpkgs.lib.genAttrs targetSystems (\n        system:\n        let\n          pkgs = pkgsFor system;\n          rust = mkRustToolchain pkgs;\n          rustPlatform = pkgs.makeRustPlatform {\n            cargo = rust;\n            rustc = rust;\n          };\n          version = (builtins.fromTOML (builtins.readFile ./crates/eww/Cargo.toml)).package.version;\n        in\n        rec {\n          eww = rustPlatform.buildRustPackage {\n            version = \"${version}-dirty\";\n            pname = \"eww\";\n\n            src = ./.;\n            cargoLock.lockFile = ./Cargo.lock;\n            cargoBuildFlags = [\n              \"--bin\"\n              \"eww\"\n            ];\n\n            nativeBuildInputs = with pkgs; [\n              pkg-config\n              wrapGAppsHook\n            ];\n            buildInputs = with pkgs; [\n              gtk3\n              librsvg\n              gtk-layer-shell\n              libdbusmenu-gtk3\n            ];\n          };\n\n          eww-wayland = nixpkgs.lib.warn \"`eww-wayland` is deprecated due to eww building with both X11 and wayland support by default. Use `eww` instead.\" eww;\n          default = eww;\n        }\n      );\n\n      devShells = nixpkgs.lib.genAttrs targetSystems (\n        system:\n        let\n          pkgs = pkgsFor system;\n          rust = mkRustToolchain pkgs;\n        in\n        {\n          default = pkgs.mkShell {\n            inputsFrom = [ self.packages.${system}.eww ];\n            packages = with pkgs; [\n              deno\n              mdbook\n              zbus-xmlgen\n            ];\n\n            RUST_SRC_PATH = \"${rust}/lib/rustlib/src/rust/library\";\n          };\n        }\n      );\n\n      formatter = nixpkgs.lib.genAttrs targetSystems (system: (pkgsFor system).nixfmt-rfc-style);\n    };\n}\n"
  },
  {
    "path": "gen-docs.ts",
    "content": "interface WidgetData {\n    name: string;\n    desc: string;\n    type: string; // this should be an enum.. maybe\n}\n\ninterface Widget {\n    name: string;\n    exts: string[];\n    desc: string;\n    props: WidgetData[];\n    isVisible: boolean;\n}\n\ninterface MagicVar {\n    name: string;\n    desc: string;\n    prop?: string;\n}\n\nfunction parseMagicVariables(data: string) {\n    const desc_pattern = /^.*\\/\\/\\s*@desc\\s*(\\w+)\\s*-\\s*(.*)$/; // matches `// @desc <name> - <desc>`\n    const prop_pattern = /^.*\\/\\/\\s+@prop +\\s*(.*)$/; // matches `// @prop <prop>`\n    const continuation = /^.*\\/\\/\\s*(.*)$/; // matches `// <...>`\n    let defs: MagicVar[] = [];\n    let last: \"desc\" | \"prop\" | null = null; // what was the last line\n    for (const line of data.split(\"\\n\")) {\n        const desc = desc_pattern.exec(line);\n        const prop = prop_pattern.exec(line);\n        const cont = continuation.exec(line);\n        if(desc) {\n            defs.push({\n                name: desc[1],\n                desc: desc[2],\n                prop: undefined,\n            });\n            last = \"desc\";\n        } else if(prop && defs.length > 0) {\n            defs[defs.length - 1].prop = prop[1];\n            last = \"prop\";\n        } else if(cont && defs.length > 0) {\n            if(last == \"desc\") {\n                defs[defs.length - 1].desc += `\\n\\n${cont[1]}`;\n            } else if(last == \"prop\" && defs[defs.length - 1].prop) {\n                defs[defs.length - 1].prop += `\\n\\n${cont[1]}`;\n            } // else this is just a comment, we ignore\n        } else {\n            last = null;\n        }\n    }\n    let output = \"\";\n    for (const {name, desc, prop} of defs) {\n        output +=\n            `### \\`${name}\\`\\n` +\n            `${desc}\\n`;\n        if(prop != null) {\n            output +=\n                '#### Structure\\n' +\n                '```\\n' +\n                `${prop}\\n` +\n                '```\\n';\n        }\n        output += '\\n';\n    }\n    return output;\n}\n\nfunction parseVars(code: string): Record<string, string> {\n    const VAR_PATTERN = /^.*\\/\\/+ *@var +(.*?) +- +(.*)$/;\n    const vars: Record<string, string> = {};\n\n    for (const line of code.split(\"\\n\")) {\n\n        const match = line.match(VAR_PATTERN);\n        if (match && match.length == 3) {\n            const name = match[1];\n            const value = match[2];\n            vars[name] = value;\n        }\n    }\n    return vars;\n}\n\nfunction replaceTypeNames(type: string) {\n    switch (type) {\n        case \"f64\":\n        case \"f32\":\n            return \"float\"\n        case \"i32\":\n        case \"i64\":\n            return \"int\"\n        default:\n            return type\n    }\n\n}\n\nfunction parseDocs(code: string) {\n    const NEW_WIDGET_PATTERN = /^.*\\/\\/+ *@widget +(!?)(.*?)(?: +extends +(.*))?$/;\n    const DESC_PATTERN = /^.*\\/\\/+ *@desc +(.*)$/;\n    const PROP_PATTERN = /^.*\\/\\/+ *@prop +(.*?) +- +(.*)$/;\n    const ARG_TYPE_PATTERN = /(\\w+):\\s+as_(\\w+)/g;\n\n    const widgets: Record<string, Widget> = {};\n    const lines = code.split(\"\\n\")\n\n    let currentWidget = \"\";\n\n    for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {\n\n        const line = lines[lineIndex]\n        const newWidgetMatch = NEW_WIDGET_PATTERN.exec(line);\n\n        if (newWidgetMatch && newWidgetMatch.length >= 3) {\n            const name = newWidgetMatch[2];\n            const exts: string[] = newWidgetMatch[3]\n                ? newWidgetMatch[3].split(/, */)\n                : [];\n            currentWidget = name;\n            widgets[currentWidget] = {\n                name,\n                exts,\n                desc: \"\",\n                props: [],\n                isVisible: newWidgetMatch[1] !== \"!\",\n            };\n            continue;\n        }\n\n        const descMatch = line.match(DESC_PATTERN);\n        if (descMatch && descMatch.length == 2) {\n            widgets[currentWidget].desc = descMatch[1];\n            continue;\n        }\n\n        // if we find a property, check through the following lines until we reach the actual property definition\n        const propMatch = line.match(PROP_PATTERN);\n        if (propMatch && propMatch.length == 3) {\n            let no = lineIndex + 1\n\n            while (/\\s*\\/\\//.test(lines[no])) {\n                no += 1\n            } // continue till you find the line with actual code\n\n            const matches = [...lines[no].matchAll(ARG_TYPE_PATTERN)].map(z => { z.shift(); return z }).flat() // try to use the iterator directly\n\n            const possibleMatch = matches.findIndex(x => x == propMatch[1].replaceAll(\"-\", \"_\"))\n            if (possibleMatch == -1) {\n                console.log(`Failed to find a match for \"${propMatch[1].replace(\"-\", \"_\")}\" ~ ${JSON.stringify(matches, null, 2)} ~ ${lines[no]}`)\n            }\n\n            if (!widgets[currentWidget].props.some(p => p.name == propMatch[1])) {\n                const type = replaceTypeNames(matches[possibleMatch + 1])\n\n                widgets[currentWidget].props.push({\n                    name: propMatch[1],\n                    desc: propMatch[2],\n                    type: type ?? \"no-type-found\"\n                });\n            }\n        }\n    }\n    return widgets;\n}\n\nfunction printDocs(vars: Record<string, string>, docs: Record<string, Widget>) {\n    const output = Object.values(docs)\n        .filter((x) => x.isVisible)\n        .map((x) => {\n            x.props = [\n                ...x.props,\n                ...x.exts.map((w) => docs[w]).flatMap((w) => w.props),\n            ];\n            return x;\n        })\n        .map((x) => printWidget(x))\n        .map((x) => x.replace(/\\$\\w+/g, (x) => vars[x.replace(\"$\", \"\")]))\n        .join(\"\\n\\n\");\n    return output;\n}\n\nfunction printWidget(widget: Widget) {\n    return `\n## \\`${widget.name}\\` ${widget.desc ? `\\n${widget.desc}` : \"\"}\n\n**Properties**\n${widget.props.map((prop) => `- **\\`${prop.name}\\`**: *\\`${prop.type}\\`* ${prop.desc}`).join(\"\\n\")}\n`;\n}\n\n// Deno args start from actual args\n// Redirect stderr to ignore deno checking messages so:\n// deno run --allow-read gen-docs.ts ./src/widgets/widget_definitions.ts 2> /dev/null\nDeno.readTextFile(Deno.args[0]).then(data => {\n    const vars = parseVars(data);\n    Deno.writeTextFile(\"./docs/src/widgets.md\", printDocs(vars, parseDocs(data)), {\"append\": true});\n}).catch(err => {\n    return console.error(err);\n})\n\nlet magic = Deno.readTextFile(Deno.args[1]).then(data => {\n    Deno.writeTextFile(\"./docs/src/magic-vars.md\", parseMagicVariables(data), {\"append\": true});\n}).catch(err => {\n    return console.error(err);\n})\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"1.81.0\"\ncomponents = [ \"rust-src\" ]\nprofile = \"default\"\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "use_small_heuristics = \"Max\"\nmax_width = 130\nuse_field_init_shorthand = true\n\n# these where set when we where still on nightly\n\n# unstable_features = true\n# fn_single_line = false\n# reorder_impl_items = true\n# imports_granularity = \"Crate\"\n# normalize_comments = true\n# wrap_comments = true\n# combine_control_expr = false\n# condense_wildcard_suffixes = true\n# format_code_in_doc_comments = true\n# format_macro_matchers = true\n# format_strings = true\n"
  },
  {
    "path": "shell.nix",
    "content": "(import (\n  let\n    lock = builtins.fromJSON (builtins.readFile ./flake.lock);\n  in\n  fetchTarball {\n    url =\n      lock.nodes.flake-compat.locked.url\n        or \"https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz\";\n    sha256 = lock.nodes.flake-compat.locked.narHash;\n  }\n) { src = ./.; }).shellNix\n"
  }
]