├── .gitignore ├── static └── mdns.gif ├── CODE_OF_CONDUCT.md ├── src ├── widget.rs ├── colors.rs ├── utils.rs ├── search.rs ├── info.rs ├── list.rs └── main.rs ├── CHANGELOG.md ├── .github └── workflows │ └── rust.yml ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── CONTRIBUTING.md ├── LICENSE-APACHE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .cargo-ok 3 | .DS_Store 4 | /.idea 5 | -------------------------------------------------------------------------------- /static/mdns.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustPretender/discovery-rs/HEAD/static/mdns.gif -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project adheres to the Rust Code of Conduct, which can be found [here](https://www.rust-lang.org/conduct.html). 4 | -------------------------------------------------------------------------------- /src/widget.rs: -------------------------------------------------------------------------------- 1 | use crossterm::event::KeyEvent; 2 | use ratatui::prelude::{Buffer, Rect}; 3 | 4 | pub trait DiscoveryWidget: Sized + std::fmt::Debug { 5 | fn title(&self) -> String; 6 | fn controls(&self) -> String; 7 | fn process_key_event(&mut self, key_event: &KeyEvent); 8 | fn render(&self, area: Rect, buf: &mut Buffer, selected: bool); 9 | } 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ### Added 11 | 12 | - v0.1.0 Initial implementation. 13 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /src/colors.rs: -------------------------------------------------------------------------------- 1 | use ratatui::style::palette::tailwind; 2 | use ratatui::style::Color; 3 | 4 | pub const NORMAL_ROW_COLOR: Color = tailwind::SLATE.c950; 5 | pub const ALT_ROW_COLOR: Color = tailwind::SLATE.c900; 6 | pub const TEXT_COLOR: Color = tailwind::SLATE.c200; 7 | pub const SELECTED_STYLE_FG: Color = tailwind::BLUE.c300; 8 | pub const HEADER_BG: Color = tailwind::BLUE.c950; 9 | pub const SEARCH_STYLE_BORDER: Color = tailwind::YELLOW.c300; 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "discovery-rs" 3 | version = "0.1.3" 4 | edition = "2021" 5 | description = "mDNS-SD TUI browser" 6 | repository = "https://github.com/JustPretender/discovery-rs" 7 | license = "MIT OR Apache-2.0" 8 | categories = ["network-programming", "command-line-utilities"] 9 | exclude = ["static/*"] 10 | homepage = "https://github.com/JustPretender/discovery-rs" 11 | keywords = ["networking", "mdns", "discovery", "cli"] 12 | readme = "README.md" 13 | rust-version = "1.74.0" 14 | 15 | [dependencies] 16 | anyhow = "1.0.99" 17 | crossterm = "0.29.0" 18 | ratatui = "0.29.0" 19 | color-eyre = "0.6.5" 20 | mdns-sd = "0.14.1" 21 | flume = { version = "0.11.1", features = ["default", "select"] } 22 | clap = "4.5.45" 23 | clap_derive = "4.5.45" 24 | regex = "1.11.1" 25 | textwrap = "0.16.2" 26 | tracing = "0.1.41" 27 | tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } 28 | tracing-appender = "0.2.3" 29 | parking_lot = "0.12.4" 30 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use ratatui::prelude::{Constraint, Direction, Layout, Rect}; 2 | 3 | pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { 4 | let popup_layout = Layout::default() 5 | .direction(Direction::Vertical) 6 | .constraints( 7 | [ 8 | Constraint::Percentage((100 - percent_y) / 2), 9 | Constraint::Percentage(percent_y), 10 | Constraint::Percentage((100 - percent_y) / 2), 11 | ] 12 | .as_ref(), 13 | ) 14 | .split(r); 15 | 16 | Layout::default() 17 | .direction(Direction::Horizontal) 18 | .constraints( 19 | [ 20 | Constraint::Percentage((100 - percent_x) / 2), 21 | Constraint::Percentage(percent_x), 22 | Constraint::Percentage((100 - percent_x) / 2), 23 | ] 24 | .as_ref(), 25 | ) 26 | .split(popup_layout[1])[1] 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Rostyslav Khudolii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # discovery-rs 2 | 3 | [![Build](https://github.com/keepsimple1/mdns-sd/actions/workflows/build.yml/badge.svg)](https://github.com/JustPretender/discovery-rs/actions)[![Rust version: 1.74+](https://img.shields.io/badge/rust%20version-1.74-orange)](https://blog.rust-lang.org/2023/11/16/Rust-1.74.0.html) 4 | 5 | A cli utility to discover **mDNS** services on your network. Built with [Ratatui](https://ratatui.rs/) and [mDNS-SD](https://github.com/keepsimple1/mdns-sd). 6 | 7 | ![Example](./static/mdns.gif) 8 | 9 | ## Installation 10 | 11 | ### Cargo 12 | 13 | * Install the rust toolchain in order to have cargo installed by following 14 | [this](https://www.rust-lang.org/tools/install) guide. 15 | * run `cargo install discovery-rs` 16 | 17 | ## License 18 | 19 | Licensed under either of 20 | 21 | * Apache License, Version 2.0 22 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 23 | * MIT license 24 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 25 | 26 | at your option. 27 | 28 | ## Contribution 29 | 30 | Unless you explicitly state otherwise, any contribution intentionally submitted 31 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 32 | dual licensed as above, without any additional terms or conditions. 33 | 34 | See [CONTRIBUTING.md](CONTRIBUTING.md). 35 | 36 | ## Acknowledgments 37 | 38 | * [Slumber](https://github.com/LucasPickering/slumber). List navigation code. 39 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | First off, thank you for considering contributing to discovery-rs. 4 | 5 | If your contribution is not straightforward, please first discuss the change you 6 | wish to make by creating a new issue before making the change. 7 | 8 | ## Reporting issues 9 | 10 | Before reporting an issue on the 11 | [issue tracker](https://github.com/JustPretender/discovery-rs/issues), 12 | please check that it has not already been reported by searching for some related 13 | keywords. 14 | 15 | ## Pull requests 16 | 17 | Try to do one pull request per change. 18 | 19 | ### Updating the changelog 20 | 21 | Update the changes you have made in 22 | [CHANGELOG](https://github.com/JustPretender/discovery-rs/blob/main/CHANGELOG.md) 23 | file under the **Unreleased** section. 24 | 25 | Add the changes of your pull request to one of the following subsections, 26 | depending on the types of changes defined by 27 | [Keep a changelog](https://keepachangelog.com/en/1.0.0/): 28 | 29 | - `Added` for new features. 30 | - `Changed` for changes in existing functionality. 31 | - `Deprecated` for soon-to-be removed features. 32 | - `Removed` for now removed features. 33 | - `Fixed` for any bug fixes. 34 | - `Security` in case of vulnerabilities. 35 | 36 | If the required subsection does not exist yet under **Unreleased**, create it! 37 | 38 | ## Developing 39 | 40 | ### Set up 41 | 42 | This is no different than other Rust projects. 43 | 44 | ```shell 45 | git clone https://github.com/JustPretender/discovery-rs 46 | cd discovery-rs 47 | cargo test 48 | ``` 49 | 50 | ### Useful Commands 51 | 52 | - Build and run release version: 53 | 54 | ```shell 55 | cargo build --release && cargo run --release 56 | ``` 57 | 58 | - Run Clippy: 59 | 60 | ```shell 61 | cargo clippy --all-targets --all-features --workspace 62 | ``` 63 | 64 | - Run all tests: 65 | 66 | ```shell 67 | cargo test --all-features --workspace 68 | ``` 69 | 70 | - Check to see if there are code formatting issues 71 | 72 | ```shell 73 | cargo fmt --all -- --check 74 | ``` 75 | 76 | - Format the code in the project 77 | 78 | ```shell 79 | cargo fmt --all 80 | ``` 81 | -------------------------------------------------------------------------------- /src/search.rs: -------------------------------------------------------------------------------- 1 | use crate::colors::{HEADER_BG, NORMAL_ROW_COLOR, SEARCH_STYLE_BORDER, TEXT_COLOR}; 2 | use crate::widget::DiscoveryWidget; 3 | use crossterm::event::{KeyCode, KeyEvent}; 4 | use ratatui::buffer::Buffer; 5 | use ratatui::layout::Rect; 6 | use ratatui::prelude::{Alignment, Constraint, Layout, Line, Stylize, Widget}; 7 | use ratatui::style::{Color, Style}; 8 | use ratatui::text::Span; 9 | use ratatui::widgets::{Block, Borders, Paragraph, Wrap}; 10 | use regex::Regex; 11 | 12 | #[derive(Debug, Default)] 13 | pub struct Search { 14 | search: Option, 15 | } 16 | 17 | impl Search { 18 | pub fn compile_regex(&self) -> anyhow::Result> { 19 | if let Some(search) = self.search.as_ref() { 20 | let regex = Regex::new(search)?; 21 | Ok(Some(regex)) 22 | } else { 23 | Ok(None) 24 | } 25 | } 26 | } 27 | 28 | impl DiscoveryWidget for Search { 29 | fn title(&self) -> String { 30 | "Search".to_string() 31 | } 32 | 33 | fn controls(&self) -> String { 34 | "Use ↵ to apply. Esc to exit".to_string() 35 | } 36 | 37 | fn process_key_event(&mut self, key_event: &KeyEvent) { 38 | match (self.search.as_mut(), key_event.code) { 39 | (Some(regex), KeyCode::Char(c)) => { 40 | regex.push(c); 41 | } 42 | (Some(regex), KeyCode::Backspace) => { 43 | regex.pop(); 44 | } 45 | (None, KeyCode::Char(c)) => { 46 | self.search = Some(c.to_string()); 47 | } 48 | _ => {} 49 | } 50 | 51 | if self 52 | .search 53 | .as_ref() 54 | .filter(|search| !search.is_empty()) 55 | .is_none() 56 | { 57 | self.search = None; 58 | } 59 | } 60 | 61 | fn render(&self, area: Rect, buf: &mut Buffer, _selected: bool) 62 | where 63 | Self: Sized, 64 | { 65 | let block = Block::new() 66 | .borders(Borders::ALL) 67 | .border_style(Style::new().fg(SEARCH_STYLE_BORDER).bold()) 68 | .title_alignment(Alignment::Center) 69 | .title(self.title()) 70 | .title_style(Style::new().bold()) 71 | .fg(TEXT_COLOR) 72 | .bg(HEADER_BG); 73 | let inner_area = block.inner(area); 74 | block.render(area, buf); 75 | 76 | let [search_area, footer_area] = 77 | Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]) 78 | .areas(inner_area); 79 | let block = Block::new() 80 | .borders(Borders::NONE) 81 | .fg(TEXT_COLOR) 82 | .bg(NORMAL_ROW_COLOR); 83 | let input = Paragraph::new(Line::from(vec![ 84 | Span::styled(" /", Style::default().fg(Color::DarkGray)), 85 | Span::from(self.search.as_deref().unwrap_or("")), 86 | ])) 87 | .block(block); 88 | 89 | Widget::render(input, search_area, buf); 90 | 91 | Paragraph::new(self.controls()) 92 | .centered() 93 | .wrap(Wrap::default()) 94 | .render(footer_area, buf); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/info.rs: -------------------------------------------------------------------------------- 1 | use crate::colors::*; 2 | use crate::list::ListEntry; 3 | use crate::widget::DiscoveryWidget; 4 | use crossterm::event::KeyEvent; 5 | use mdns_sd::ServiceInfo; 6 | use ratatui::{prelude::*, widgets::*}; 7 | 8 | /// [`ServiceInfo`] wrapper. 9 | /// 10 | /// Implements traits, necessary for the [`ServiceInfo`] to be 11 | /// rendered either as a [`Widget`] or simply as an entry in the [`List`] 12 | #[derive(Debug)] 13 | pub struct Info { 14 | pub info: ServiceInfo, 15 | } 16 | 17 | impl PartialEq for Info { 18 | fn eq(&self, other: &Self) -> bool { 19 | self.info.get_hostname() == other.info.get_hostname() 20 | } 21 | } 22 | 23 | impl ListEntry for Info { 24 | fn entry(&self) -> Line<'_> { 25 | Line::styled(format!("{}", self.info.get_hostname()), TEXT_COLOR) 26 | } 27 | 28 | fn id(&self) -> String { 29 | self.info.get_hostname().to_string() 30 | } 31 | } 32 | 33 | impl DiscoveryWidget for &Info { 34 | fn title(&self) -> String { 35 | self.id() 36 | } 37 | 38 | fn controls(&self) -> String { 39 | "".to_string() 40 | } 41 | 42 | fn process_key_event(&mut self, _key_event: &KeyEvent) {} 43 | 44 | fn render(&self, area: Rect, buf: &mut Buffer, selected: bool) { 45 | let outer_block = Block::new() 46 | .borders(Borders::ALL) 47 | .border_style(if selected { 48 | Style::new().fg(SELECTED_STYLE_FG) 49 | } else { 50 | Style::default() 51 | }) 52 | .title_alignment(Alignment::Center) 53 | .title(self.title()) 54 | .title_style(Style::new().bold()) 55 | .fg(TEXT_COLOR) 56 | .bg(HEADER_BG); 57 | let inner_area = outer_block.inner(area); 58 | outer_block.render(area, buf); 59 | 60 | let inner_block = Block::new() 61 | .borders(Borders::NONE) 62 | .padding(Padding::horizontal(1)) 63 | .bg(NORMAL_ROW_COLOR); 64 | let properties = textwrap::wrap( 65 | &self.info.get_properties().to_string(), 66 | // Fit to end, minus "properties" and cell spacing 67 | textwrap::Options::new((area.width as usize).saturating_sub(10 + 1)), 68 | ) 69 | .join("\n"); 70 | let rows = [ 71 | Row::new([ 72 | Cell::new("Hostname").bold().light_cyan(), 73 | self.info.get_hostname().into(), 74 | ]), 75 | Row::new([ 76 | Cell::new("Addresses").bold().light_cyan(), 77 | self.info 78 | .get_addresses() 79 | .into_iter() 80 | .map(|addr| addr.to_string()) 81 | .fold(String::new(), |acc, addr| acc + &addr + " ") 82 | .into(), 83 | ]), 84 | Row::new([ 85 | Cell::new("Port").bold().light_cyan(), 86 | self.info.get_port().to_string().into(), 87 | ]), 88 | Row::new([ 89 | Cell::new("Host TTL").bold().light_cyan(), 90 | self.info.get_host_ttl().to_string().into(), 91 | ]), 92 | Row::new([ 93 | Cell::new("Other TTL").bold().light_cyan(), 94 | self.info.get_other_ttl().to_string().into(), 95 | ]), 96 | Row::new([ 97 | Cell::new("Priority").bold().light_cyan(), 98 | self.info.get_priority().to_string().into(), 99 | ]), 100 | Row::new([ 101 | Cell::new("Weight").bold().light_cyan(), 102 | self.info.get_weight().to_string().into(), 103 | ]), 104 | Row::new([ 105 | Cell::new("Properties").bold().light_cyan(), 106 | Cell::new(properties), 107 | ]) 108 | .height(2), 109 | ]; 110 | let widths = [Constraint::Percentage(10), Constraint::Percentage(90)]; 111 | 112 | let table = Table::new(rows, widths) 113 | .block(inner_block) 114 | .column_spacing(1) 115 | .highlight_spacing(HighlightSpacing::Always) 116 | .style(Style::new().white()) 117 | .on_black(); 118 | 119 | Widget::render(table, inner_area, buf); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/list.rs: -------------------------------------------------------------------------------- 1 | use crossterm::event::{KeyCode, KeyEvent}; 2 | use ratatui::{prelude::*, widgets::*}; 3 | use regex::Regex; 4 | use std::cell::RefCell; 5 | use std::fmt::Display; 6 | use tracing::instrument; 7 | 8 | use crate::colors::*; 9 | use crate::search::Search; 10 | use crate::utils::centered_rect; 11 | use crate::widget::DiscoveryWidget; 12 | 13 | #[derive(Debug, Default)] 14 | enum Mode { 15 | #[default] 16 | Display, 17 | Search, 18 | } 19 | 20 | /// Custom [`List`] entry trait 21 | /// 22 | /// Implementing this trait for a type will make it possible 23 | /// for the type to be rendered as a line in the [`List`]. 24 | pub trait ListEntry { 25 | fn entry(&self) -> Line<'_>; 26 | fn id(&self) -> String; 27 | } 28 | 29 | impl ListEntry for D { 30 | fn entry(&self) -> Line<'_> { 31 | Line::styled(format!("{}", self), TEXT_COLOR) 32 | } 33 | 34 | fn id(&self) -> String { 35 | format!("{}", self) 36 | } 37 | } 38 | 39 | /// Custom [`List`] widget. 40 | /// 41 | /// Keeps track of the list elements and implements [`Widget`] so 42 | /// that the list can be rendered as part of the TUI. 43 | #[derive(Debug)] 44 | pub struct ListWidget { 45 | name: String, 46 | items: Vec, 47 | state: RefCell, 48 | search_regex: Option, 49 | search: Search, 50 | current_mode: Mode, 51 | } 52 | 53 | impl Default for ListWidget { 54 | fn default() -> Self { 55 | Self { 56 | name: "ListWidget".to_string(), 57 | items: Default::default(), 58 | state: RefCell::new(ListState::default()), 59 | search: Search::default(), 60 | search_regex: None, 61 | current_mode: Mode::default(), 62 | } 63 | } 64 | } 65 | 66 | impl ListWidget 67 | where 68 | Item: ListEntry + PartialEq + std::fmt::Debug, 69 | { 70 | pub fn name(mut self, name: String) -> Self { 71 | self.name = name; 72 | self 73 | } 74 | 75 | pub fn selected(&self) -> Option<&Item> { 76 | let filtered = self.filtered(); 77 | filtered 78 | .get(self.state.borrow().selected().unwrap_or(0)) 79 | .copied() 80 | } 81 | 82 | pub fn push(&mut self, item: Item) { 83 | if !self.items.contains(&item) { 84 | self.items.push(item); 85 | } 86 | 87 | // Select the first item once we have at least one 88 | let state = self.state.get_mut(); 89 | if state.selected().is_none() { 90 | state.select(Some(0)); 91 | } 92 | } 93 | 94 | pub fn remove(&mut self, id: &String) { 95 | if let Some((index, _)) = self.items.iter().enumerate().find(|(_, el)| el.id() == *id) { 96 | self.items.remove(index); 97 | } 98 | 99 | // Deselect when all the items are gone 100 | if self.items.is_empty() { 101 | self.state.get_mut().select(None); 102 | } 103 | } 104 | 105 | pub fn next(&mut self) { 106 | self.select_delta(1); 107 | } 108 | 109 | pub fn prev(&mut self) { 110 | self.select_delta(-1); 111 | } 112 | 113 | pub fn top(&mut self) { 114 | let s = self.state.get_mut().selected().unwrap_or(0) as isize; 115 | self.select_delta(-1 * s); 116 | } 117 | 118 | pub fn bottom(&mut self) { 119 | let s = self.state.get_mut().selected().unwrap_or(0) as isize; 120 | self.select_delta(s); 121 | } 122 | 123 | /// Move some number of items up or down the list. Selection will wrap if 124 | /// it underflows/overflows. 125 | #[instrument] 126 | fn select_delta(&mut self, delta: isize) { 127 | tracing::trace!( 128 | "List state before the update: {:?}", 129 | self.state.get_mut().selected() 130 | ); 131 | let filtered = self.filtered(); 132 | // If there's nothing in the list, we can't do anything 133 | if !filtered.is_empty() { 134 | let len = filtered.len() as isize; 135 | let index = match self.state.get_mut().selected() { 136 | Some(i) => (i as isize + delta).rem_euclid(len) as usize, 137 | // Nothing selected yet, pick the first item 138 | None => 0, 139 | }; 140 | self.state.get_mut().select(Some(index)); 141 | } 142 | tracing::trace!( 143 | "List state after the update: {:?}", 144 | self.state.get_mut().selected() 145 | ); 146 | } 147 | 148 | fn filtered(&self) -> Vec<&Item> { 149 | if let Some(regex) = self.search_regex.as_ref() { 150 | self.items 151 | .iter() 152 | .filter(|item| regex.is_match(&item.id())) 153 | .collect() 154 | } else { 155 | self.items.iter().collect() 156 | } 157 | } 158 | 159 | #[instrument] 160 | fn update_filter(&mut self, regex: Option) { 161 | self.search_regex = regex; 162 | let filtered = self.filtered(); 163 | if !filtered.is_empty() { 164 | self.state.get_mut().select(Some(0)); 165 | } 166 | tracing::debug!("Filter has been updated"); 167 | } 168 | } 169 | 170 | impl DiscoveryWidget for ListWidget 171 | where 172 | Item: ListEntry + PartialEq + std::fmt::Debug, 173 | { 174 | fn title(&self) -> String { 175 | format!( 176 | "{}{}", 177 | self.name, 178 | if let Some(regex) = self.search_regex.as_ref() { 179 | format!("(/{}/)", regex.to_string()) 180 | } else { 181 | "".to_string() 182 | } 183 | ) 184 | } 185 | 186 | fn controls(&self) -> String { 187 | "Use ↓↑ to select next/prev, g/G to go top/bottom, / to search".to_string() 188 | } 189 | 190 | fn process_key_event(&mut self, event: &KeyEvent) { 191 | match self.current_mode { 192 | Mode::Search => match event.code { 193 | KeyCode::Esc => { 194 | self.current_mode = Mode::Display; 195 | } 196 | KeyCode::Enter => { 197 | self.current_mode = Mode::Display; 198 | self.update_filter(self.search.compile_regex().ok().flatten()); 199 | } 200 | KeyCode::Char(_) | KeyCode::Backspace => { 201 | self.search.process_key_event(event); 202 | } 203 | _ => {} 204 | }, 205 | Mode::Display => match event.code { 206 | KeyCode::Down => self.next(), 207 | KeyCode::Up => self.prev(), 208 | KeyCode::Char('g') => self.top(), 209 | KeyCode::Char('G') => self.bottom(), 210 | KeyCode::Char('/') => self.current_mode = Mode::Search, 211 | _ => {} 212 | }, 213 | } 214 | } 215 | 216 | fn render(&self, area: Rect, buf: &mut Buffer, selected: bool) { 217 | let outer_block = Block::new() 218 | .borders(Borders::ALL) 219 | .border_style(if selected { 220 | Style::new().fg(SELECTED_STYLE_FG) 221 | } else { 222 | Style::default() 223 | }) 224 | .title_alignment(Alignment::Center) 225 | .title(self.title()) 226 | .title_style(Style::new().bold()) 227 | .fg(TEXT_COLOR) 228 | .bg(HEADER_BG); 229 | let inner_area = outer_block.inner(area); 230 | outer_block.render(area, buf); 231 | 232 | let inner_block = Block::new() 233 | .borders(Borders::NONE) 234 | .fg(TEXT_COLOR) 235 | .bg(NORMAL_ROW_COLOR); 236 | 237 | let items: Vec<_> = self 238 | .items 239 | .iter() 240 | .filter(|item| { 241 | if let Some(regex) = self.search_regex.as_ref() { 242 | regex.is_match(&item.id()) 243 | } else { 244 | true 245 | } 246 | }) 247 | .enumerate() 248 | .map(|(index, item)| { 249 | ListItem::new(item.entry()).bg(if (index % 2) == 0 { 250 | NORMAL_ROW_COLOR 251 | } else { 252 | ALT_ROW_COLOR 253 | }) 254 | }) 255 | .collect(); 256 | let list = List::new(items) 257 | .block(inner_block) 258 | .highlight_style( 259 | Style::default() 260 | .add_modifier(Modifier::BOLD) 261 | .add_modifier(Modifier::REVERSED) 262 | .fg(SELECTED_STYLE_FG), 263 | ) 264 | .highlight_symbol(">") 265 | .highlight_spacing(HighlightSpacing::Always); 266 | StatefulWidget::render(list, inner_area, buf, &mut self.state.borrow_mut()); 267 | 268 | if matches!(self.current_mode, Mode::Search) { 269 | let search_area = centered_rect(60, (5. / area.height as f64 * 100.) as u16, area); 270 | Clear.render(search_area, buf); 271 | self.search.render(search_area, buf, true); 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use std::cell::RefCell; 3 | use std::collections::HashMap; 4 | use std::fs::File; 5 | use std::rc::Rc; 6 | use std::sync::Arc; 7 | use std::thread::JoinHandle; 8 | use std::time::Duration; 9 | use std::{error::Error, io::stdout}; 10 | 11 | use clap::Parser; 12 | use clap_derive::Parser; 13 | use color_eyre::config::HookBuilder; 14 | use crossterm::event::KeyModifiers; 15 | use crossterm::{ 16 | event::{self, poll, Event, KeyCode, KeyEventKind}, 17 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 18 | ExecutableCommand, 19 | }; 20 | use flume::{Selector, Sender}; 21 | use mdns_sd::{IfKind, ServiceDaemon, ServiceEvent}; 22 | use parking_lot::Mutex; 23 | use ratatui::{prelude::*, widgets::*}; 24 | use tracing::{instrument, Level}; 25 | use tracing_appender::non_blocking; 26 | use tracing_appender::non_blocking::WorkerGuard; 27 | use tracing_subscriber::EnvFilter; 28 | 29 | use crate::info::Info; 30 | use crate::list::ListWidget; 31 | use crate::widget::DiscoveryWidget; 32 | 33 | mod colors; 34 | mod info; 35 | mod list; 36 | mod search; 37 | mod utils; 38 | mod widget; 39 | 40 | #[derive(Parser, Debug, Default)] 41 | #[command(author, version, about, long_about = None)] 42 | #[command(propagate_version = true)] 43 | struct CliOpts { 44 | #[arg(long)] 45 | /// mDNS service query, default: _services._dns-sd._udp.local. 46 | query: Option, 47 | #[arg(long)] 48 | /// Interface to perform discovery on, default: All 49 | interface: Option, 50 | #[arg(long, action)] 51 | /// Enable tracing and debug logging 52 | tracing: bool, 53 | } 54 | 55 | const K_SERVICE_TYPE_ENUMERATION: &'static str = "_services._dns-sd._udp.local."; 56 | const K_REFRESH_RATE: u8 = 24; 57 | 58 | fn main() -> Result<(), Box> { 59 | let opts = CliOpts::parse(); 60 | 61 | init_error_hooks()?; 62 | 63 | // setup tracing and keep its guard 64 | let mut _tracing_guard = None; 65 | if opts.tracing { 66 | _tracing_guard = Some(init_tracing()?); 67 | } 68 | 69 | let terminal = init_terminal()?; 70 | 71 | // create app and run it 72 | let mut app = App::new( 73 | opts.query 74 | .as_ref() 75 | .map(|q| q.as_str()) 76 | .unwrap_or(K_SERVICE_TYPE_ENUMERATION), 77 | opts.interface.unwrap_or(IfKind::All), 78 | )?; 79 | app.run(terminal)?; 80 | app.shutdown()?; 81 | 82 | restore_terminal()?; 83 | 84 | Ok(()) 85 | } 86 | 87 | fn init_error_hooks() -> color_eyre::Result<()> { 88 | let (panic, error) = HookBuilder::default().into_hooks(); 89 | let panic = panic.into_panic_hook(); 90 | let error = error.into_eyre_hook(); 91 | color_eyre::eyre::set_hook(Box::new(move |e| { 92 | let _ = restore_terminal(); 93 | error(e) 94 | }))?; 95 | std::panic::set_hook(Box::new(move |info| { 96 | let _ = restore_terminal(); 97 | panic(info); 98 | })); 99 | Ok(()) 100 | } 101 | 102 | fn init_terminal() -> color_eyre::Result> { 103 | enable_raw_mode()?; 104 | stdout().execute(EnterAlternateScreen)?; 105 | let backend = CrosstermBackend::new(stdout()); 106 | let terminal = Terminal::new(backend)?; 107 | Ok(terminal) 108 | } 109 | 110 | fn restore_terminal() -> color_eyre::Result<()> { 111 | disable_raw_mode()?; 112 | stdout().execute(LeaveAlternateScreen)?; 113 | Ok(()) 114 | } 115 | 116 | /// Initialize the tracing subscriber to log to a file 117 | /// 118 | /// This function initializes the tracing subscriber to log to a file named `tracing.log` in the 119 | /// current directory. The function returns a [`WorkerGuard`] that must be kept alive for the 120 | /// duration of the program to ensure that logs are flushed to the file on shutdown. The logs are 121 | /// written in a non-blocking fashion to ensure that the logs do not block the main thread. 122 | fn init_tracing() -> anyhow::Result { 123 | let file = File::create("tracing.log").context("Failed to create tracing.log")?; 124 | let (non_blocking, guard) = non_blocking(file); 125 | 126 | // By default, the subscriber is configured to log all events with a level of `DEBUG` or higher, 127 | // but this can be changed by setting the `RUST_LOG` environment variable. 128 | let env_filter = EnvFilter::builder() 129 | .with_default_directive(Level::INFO.into()) 130 | .from_env_lossy(); 131 | 132 | tracing_subscriber::fmt() 133 | .with_writer(non_blocking) 134 | .with_env_filter(env_filter) 135 | .init(); 136 | Ok(guard) 137 | } 138 | 139 | #[derive(Debug, Default)] 140 | enum Tab { 141 | #[default] 142 | Services, 143 | Instances, 144 | } 145 | 146 | #[derive(Debug, Default)] 147 | enum State { 148 | #[default] 149 | Running, 150 | Exit, 151 | } 152 | 153 | struct App { 154 | stop: Sender<()>, 155 | services: Arc>>, 156 | instances: Arc>>>, 157 | current_tab: Tab, 158 | worker_handle: Option>>, 159 | } 160 | 161 | impl App { 162 | #[instrument] 163 | fn new + std::fmt::Debug>(query: T, interface: IfKind) -> anyhow::Result { 164 | let mdns = ServiceDaemon::new()?; 165 | let mdns = Arc::new(Mutex::new(mdns)); 166 | let services = Arc::new(Mutex::new( 167 | ListWidget::default().name("Services".to_string()), 168 | )); 169 | let instances = Arc::new(Mutex::new(HashMap::new())); 170 | let (stop_tx, stop_rx) = flume::bounded(1); 171 | 172 | let worker = { 173 | let mdns = mdns.clone(); 174 | let services = services.clone(); 175 | let instances = instances.clone(); 176 | let query = query.as_ref().to_string(); 177 | std::thread::spawn(move || -> anyhow::Result<()> { 178 | let _span = tracing::span!(Level::TRACE, "mDNS worker").entered(); 179 | 180 | let base = { 181 | let mdns = mdns.lock(); 182 | mdns.enable_interface(interface.clone())?; 183 | mdns.browse(query.as_str())? 184 | }; 185 | 186 | tracing::info!("Started the mDNS browsing"); 187 | 188 | let receivers = Rc::new(RefCell::new(vec![base])); 189 | let event_handler = { 190 | let receivers = receivers.clone(); 191 | let mdns = mdns.clone(); 192 | move |event| -> anyhow::Result<()> { 193 | if let Ok(event) = event { 194 | match event { 195 | ServiceEvent::ServiceFound(service_type, full_name) => { 196 | tracing::debug!("New service found: {full_name}"); 197 | if service_type == query { 198 | services.lock().push(full_name.clone()); 199 | instances.lock().insert( 200 | full_name.clone(), 201 | ListWidget::default().name(full_name.clone()), 202 | ); 203 | let receiver = mdns.lock().browse(&full_name)?; 204 | let mut receivers = receivers.borrow_mut(); 205 | receivers.push(receiver); 206 | } 207 | } 208 | ServiceEvent::ServiceResolved(info) => { 209 | tracing::debug!("Service resolved: {info:#?}"); 210 | if let Some(resolved) = 211 | instances.lock().get_mut(info.get_type()) 212 | { 213 | resolved.push(Info { info }); 214 | } 215 | } 216 | ServiceEvent::ServiceRemoved(service_type, full_name) => { 217 | tracing::debug!("Service removed: {full_name}"); 218 | if service_type == query { 219 | services.lock().remove(&full_name); 220 | instances.lock().remove(&full_name); 221 | } else if let Some(resolved) = 222 | instances.lock().get_mut(&service_type) 223 | { 224 | resolved.remove(&full_name); 225 | } 226 | } 227 | ServiceEvent::SearchStarted(service) => { 228 | tracing::trace!("Search Started for {service}"); 229 | } 230 | ServiceEvent::SearchStopped(service) => { 231 | tracing::trace!("Search Stopped for {service}"); 232 | } 233 | _ => {} 234 | } 235 | } 236 | 237 | Ok(()) 238 | } 239 | }; 240 | 241 | let mut stop = false; 242 | while !stop { 243 | let receivers = receivers.borrow().clone(); 244 | let mut selector = Selector::new(); 245 | for receiver in receivers.iter() { 246 | selector = selector.recv(receiver, &event_handler); 247 | } 248 | selector = selector.recv(&stop_rx, |_| { 249 | stop = true; 250 | Ok(()) 251 | }); 252 | selector.wait()?; 253 | } 254 | 255 | mdns.lock().shutdown()?; 256 | 257 | tracing::info!("Stopped the mDNS browsing"); 258 | 259 | Ok(()) 260 | }) 261 | }; 262 | 263 | Ok(Self { 264 | services, 265 | instances, 266 | stop: stop_tx, 267 | current_tab: Tab::Services, 268 | worker_handle: Some(worker), 269 | }) 270 | } 271 | 272 | fn handle_event(&mut self, event: Event) -> anyhow::Result { 273 | if let Event::Key(key) = event { 274 | if key.kind == KeyEventKind::Press { 275 | match key.code { 276 | KeyCode::Char('q') if key.modifiers.contains(KeyModifiers::CONTROL) => { 277 | return Ok(State::Exit) 278 | } 279 | KeyCode::Left => self.current_tab = Tab::Services, 280 | KeyCode::Right => self.current_tab = Tab::Instances, 281 | _ => { 282 | let mut services = self.services.lock(); 283 | let mut instances = self.instances.lock(); 284 | 285 | match self.current_tab { 286 | Tab::Services => { 287 | services.process_key_event(&key); 288 | } 289 | Tab::Instances => { 290 | if let Some(selected) = services 291 | .selected() 292 | .and_then(|service| instances.get_mut(service)) 293 | { 294 | selected.process_key_event(&key); 295 | } 296 | } 297 | } 298 | } 299 | } 300 | } 301 | } 302 | 303 | Ok(State::Running) 304 | } 305 | 306 | fn run(&mut self, mut terminal: Terminal) -> anyhow::Result<()> { 307 | loop { 308 | terminal.draw(|frame| { 309 | frame.render_widget(self as &mut App, frame.area()); 310 | })?; 311 | 312 | if poll(Duration::from_millis( 313 | (K_REFRESH_RATE as f64 / 1000.) as u64, 314 | ))? { 315 | match self.handle_event(event::read()?)? { 316 | State::Exit => { 317 | return Ok(()); 318 | } 319 | _ => {} 320 | } 321 | } 322 | } 323 | } 324 | 325 | fn shutdown(&mut self) -> anyhow::Result<()> { 326 | self.stop.send(())?; 327 | if let Some(handle) = self.worker_handle.take() { 328 | handle 329 | .join() 330 | .expect("The worker being joined has panicked")?; 331 | } 332 | Ok(()) 333 | } 334 | } 335 | 336 | impl Widget for &mut App { 337 | fn render(self, area: Rect, buf: &mut Buffer) { 338 | let vertical = Layout::vertical([ 339 | Constraint::Length(2), 340 | Constraint::Min(0), 341 | Constraint::Length(12), 342 | Constraint::Length(2), 343 | ]); 344 | let [header_area, list_area, info_area, footer_area] = vertical.areas(area); 345 | 346 | Paragraph::new(format!( 347 | "{}, v{}", 348 | env!("CARGO_PKG_DESCRIPTION"), 349 | env!("CARGO_PKG_VERSION") 350 | )) 351 | .bold() 352 | .centered() 353 | .render(header_area, buf); 354 | 355 | let list_layout = 356 | Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]); 357 | let [service_area, instances_area] = list_layout.areas(list_area); 358 | 359 | let services = self.services.lock(); 360 | services.render(service_area, buf, matches!(self.current_tab, Tab::Services)); 361 | if let Some(selected) = services.selected() { 362 | let instances = self.instances.lock(); 363 | if let Some(resolved_instances) = instances.get(selected) { 364 | resolved_instances.render( 365 | instances_area, 366 | buf, 367 | matches!(self.current_tab, Tab::Instances), 368 | ); 369 | if let Some(info) = resolved_instances.selected() { 370 | info.render(info_area, buf, false); 371 | } 372 | } 373 | } 374 | 375 | Paragraph::new(vec![ 376 | Line::from(services.controls()), 377 | Line::from("←→ to switch panes, C-q to exit."), 378 | ]) 379 | .centered() 380 | .render(footer_area, buf); 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.8.11" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 25 | dependencies = [ 26 | "cfg-if", 27 | "once_cell", 28 | "version_check", 29 | "zerocopy", 30 | ] 31 | 32 | [[package]] 33 | name = "aho-corasick" 34 | version = "1.1.3" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 37 | dependencies = [ 38 | "memchr", 39 | ] 40 | 41 | [[package]] 42 | name = "allocator-api2" 43 | version = "0.2.18" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 46 | 47 | [[package]] 48 | name = "anstream" 49 | version = "0.6.14" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 52 | dependencies = [ 53 | "anstyle", 54 | "anstyle-parse", 55 | "anstyle-query", 56 | "anstyle-wincon", 57 | "colorchoice", 58 | "is_terminal_polyfill", 59 | "utf8parse", 60 | ] 61 | 62 | [[package]] 63 | name = "anstyle" 64 | version = "1.0.11" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 67 | 68 | [[package]] 69 | name = "anstyle-parse" 70 | version = "0.2.4" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 73 | dependencies = [ 74 | "utf8parse", 75 | ] 76 | 77 | [[package]] 78 | name = "anstyle-query" 79 | version = "1.0.3" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" 82 | dependencies = [ 83 | "windows-sys 0.52.0", 84 | ] 85 | 86 | [[package]] 87 | name = "anstyle-wincon" 88 | version = "3.0.3" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 91 | dependencies = [ 92 | "anstyle", 93 | "windows-sys 0.52.0", 94 | ] 95 | 96 | [[package]] 97 | name = "anyhow" 98 | version = "1.0.99" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" 101 | 102 | [[package]] 103 | name = "autocfg" 104 | version = "1.3.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 107 | 108 | [[package]] 109 | name = "backtrace" 110 | version = "0.3.71" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 113 | dependencies = [ 114 | "addr2line", 115 | "cc", 116 | "cfg-if", 117 | "libc", 118 | "miniz_oxide", 119 | "object", 120 | "rustc-demangle", 121 | ] 122 | 123 | [[package]] 124 | name = "bitflags" 125 | version = "2.9.2" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" 128 | 129 | [[package]] 130 | name = "bumpalo" 131 | version = "3.16.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 134 | 135 | [[package]] 136 | name = "cassowary" 137 | version = "0.3.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 140 | 141 | [[package]] 142 | name = "castaway" 143 | version = "0.2.4" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" 146 | dependencies = [ 147 | "rustversion", 148 | ] 149 | 150 | [[package]] 151 | name = "cc" 152 | version = "1.0.98" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" 155 | 156 | [[package]] 157 | name = "cfg-if" 158 | version = "1.0.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 161 | 162 | [[package]] 163 | name = "clap" 164 | version = "4.5.45" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" 167 | dependencies = [ 168 | "clap_builder", 169 | ] 170 | 171 | [[package]] 172 | name = "clap_builder" 173 | version = "4.5.44" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" 176 | dependencies = [ 177 | "anstream", 178 | "anstyle", 179 | "clap_lex", 180 | "strsim", 181 | ] 182 | 183 | [[package]] 184 | name = "clap_derive" 185 | version = "4.5.45" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" 188 | dependencies = [ 189 | "heck", 190 | "proc-macro2", 191 | "quote", 192 | "syn", 193 | ] 194 | 195 | [[package]] 196 | name = "clap_lex" 197 | version = "0.7.5" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 200 | 201 | [[package]] 202 | name = "color-eyre" 203 | version = "0.6.5" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" 206 | dependencies = [ 207 | "backtrace", 208 | "color-spantrace", 209 | "eyre", 210 | "indenter", 211 | "once_cell", 212 | "owo-colors", 213 | "tracing-error", 214 | ] 215 | 216 | [[package]] 217 | name = "color-spantrace" 218 | version = "0.3.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" 221 | dependencies = [ 222 | "once_cell", 223 | "owo-colors", 224 | "tracing-core", 225 | "tracing-error", 226 | ] 227 | 228 | [[package]] 229 | name = "colorchoice" 230 | version = "1.0.1" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 233 | 234 | [[package]] 235 | name = "compact_str" 236 | version = "0.8.1" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" 239 | dependencies = [ 240 | "castaway", 241 | "cfg-if", 242 | "itoa", 243 | "rustversion", 244 | "ryu", 245 | "static_assertions", 246 | ] 247 | 248 | [[package]] 249 | name = "convert_case" 250 | version = "0.7.1" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" 253 | dependencies = [ 254 | "unicode-segmentation", 255 | ] 256 | 257 | [[package]] 258 | name = "crossbeam-channel" 259 | version = "0.5.13" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" 262 | dependencies = [ 263 | "crossbeam-utils", 264 | ] 265 | 266 | [[package]] 267 | name = "crossbeam-utils" 268 | version = "0.8.20" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 271 | 272 | [[package]] 273 | name = "crossterm" 274 | version = "0.28.1" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 277 | dependencies = [ 278 | "bitflags", 279 | "crossterm_winapi", 280 | "mio", 281 | "parking_lot", 282 | "rustix 0.38.44", 283 | "signal-hook", 284 | "signal-hook-mio", 285 | "winapi", 286 | ] 287 | 288 | [[package]] 289 | name = "crossterm" 290 | version = "0.29.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" 293 | dependencies = [ 294 | "bitflags", 295 | "crossterm_winapi", 296 | "derive_more", 297 | "document-features", 298 | "mio", 299 | "parking_lot", 300 | "rustix 1.0.8", 301 | "signal-hook", 302 | "signal-hook-mio", 303 | "winapi", 304 | ] 305 | 306 | [[package]] 307 | name = "crossterm_winapi" 308 | version = "0.9.1" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 311 | dependencies = [ 312 | "winapi", 313 | ] 314 | 315 | [[package]] 316 | name = "darling" 317 | version = "0.20.11" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 320 | dependencies = [ 321 | "darling_core", 322 | "darling_macro", 323 | ] 324 | 325 | [[package]] 326 | name = "darling_core" 327 | version = "0.20.11" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 330 | dependencies = [ 331 | "fnv", 332 | "ident_case", 333 | "proc-macro2", 334 | "quote", 335 | "strsim", 336 | "syn", 337 | ] 338 | 339 | [[package]] 340 | name = "darling_macro" 341 | version = "0.20.11" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 344 | dependencies = [ 345 | "darling_core", 346 | "quote", 347 | "syn", 348 | ] 349 | 350 | [[package]] 351 | name = "deranged" 352 | version = "0.3.11" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 355 | dependencies = [ 356 | "powerfmt", 357 | ] 358 | 359 | [[package]] 360 | name = "derive_more" 361 | version = "2.0.1" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" 364 | dependencies = [ 365 | "derive_more-impl", 366 | ] 367 | 368 | [[package]] 369 | name = "derive_more-impl" 370 | version = "2.0.1" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" 373 | dependencies = [ 374 | "convert_case", 375 | "proc-macro2", 376 | "quote", 377 | "syn", 378 | ] 379 | 380 | [[package]] 381 | name = "discovery-rs" 382 | version = "0.1.3" 383 | dependencies = [ 384 | "anyhow", 385 | "clap", 386 | "clap_derive", 387 | "color-eyre", 388 | "crossterm 0.29.0", 389 | "flume", 390 | "mdns-sd", 391 | "parking_lot", 392 | "ratatui", 393 | "regex", 394 | "textwrap", 395 | "tracing", 396 | "tracing-appender", 397 | "tracing-subscriber", 398 | ] 399 | 400 | [[package]] 401 | name = "document-features" 402 | version = "0.2.11" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" 405 | dependencies = [ 406 | "litrs", 407 | ] 408 | 409 | [[package]] 410 | name = "either" 411 | version = "1.12.0" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" 414 | 415 | [[package]] 416 | name = "errno" 417 | version = "0.3.13" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" 420 | dependencies = [ 421 | "libc", 422 | "windows-sys 0.60.2", 423 | ] 424 | 425 | [[package]] 426 | name = "eyre" 427 | version = "0.6.12" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 430 | dependencies = [ 431 | "indenter", 432 | "once_cell", 433 | ] 434 | 435 | [[package]] 436 | name = "fastrand" 437 | version = "2.3.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 440 | 441 | [[package]] 442 | name = "flume" 443 | version = "0.11.1" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" 446 | dependencies = [ 447 | "futures-core", 448 | "futures-sink", 449 | "nanorand", 450 | "spin", 451 | ] 452 | 453 | [[package]] 454 | name = "fnv" 455 | version = "1.0.7" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 458 | 459 | [[package]] 460 | name = "futures-core" 461 | version = "0.3.30" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 464 | 465 | [[package]] 466 | name = "futures-sink" 467 | version = "0.3.30" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 470 | 471 | [[package]] 472 | name = "getrandom" 473 | version = "0.2.15" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 476 | dependencies = [ 477 | "cfg-if", 478 | "js-sys", 479 | "libc", 480 | "wasi", 481 | "wasm-bindgen", 482 | ] 483 | 484 | [[package]] 485 | name = "gimli" 486 | version = "0.28.1" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 489 | 490 | [[package]] 491 | name = "hashbrown" 492 | version = "0.14.5" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 495 | dependencies = [ 496 | "ahash", 497 | "allocator-api2", 498 | ] 499 | 500 | [[package]] 501 | name = "heck" 502 | version = "0.5.0" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 505 | 506 | [[package]] 507 | name = "ident_case" 508 | version = "1.0.1" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 511 | 512 | [[package]] 513 | name = "if-addrs" 514 | version = "0.14.0" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "bf39cc0423ee66021dc5eccface85580e4a001e0c5288bae8bea7ecb69225e90" 517 | dependencies = [ 518 | "libc", 519 | "windows-sys 0.59.0", 520 | ] 521 | 522 | [[package]] 523 | name = "indenter" 524 | version = "0.3.3" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 527 | 528 | [[package]] 529 | name = "indoc" 530 | version = "2.0.6" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" 533 | 534 | [[package]] 535 | name = "instability" 536 | version = "0.3.9" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" 539 | dependencies = [ 540 | "darling", 541 | "indoc", 542 | "proc-macro2", 543 | "quote", 544 | "syn", 545 | ] 546 | 547 | [[package]] 548 | name = "is_terminal_polyfill" 549 | version = "1.70.0" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 552 | 553 | [[package]] 554 | name = "itertools" 555 | version = "0.12.1" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 558 | dependencies = [ 559 | "either", 560 | ] 561 | 562 | [[package]] 563 | name = "itertools" 564 | version = "0.13.0" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 567 | dependencies = [ 568 | "either", 569 | ] 570 | 571 | [[package]] 572 | name = "itoa" 573 | version = "1.0.11" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 576 | 577 | [[package]] 578 | name = "js-sys" 579 | version = "0.3.69" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 582 | dependencies = [ 583 | "wasm-bindgen", 584 | ] 585 | 586 | [[package]] 587 | name = "lazy_static" 588 | version = "1.4.0" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 591 | 592 | [[package]] 593 | name = "libc" 594 | version = "0.2.175" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 597 | 598 | [[package]] 599 | name = "linux-raw-sys" 600 | version = "0.4.15" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 603 | 604 | [[package]] 605 | name = "linux-raw-sys" 606 | version = "0.9.4" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 609 | 610 | [[package]] 611 | name = "litrs" 612 | version = "0.4.2" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" 615 | 616 | [[package]] 617 | name = "lock_api" 618 | version = "0.4.13" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 621 | dependencies = [ 622 | "autocfg", 623 | "scopeguard", 624 | ] 625 | 626 | [[package]] 627 | name = "log" 628 | version = "0.4.21" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 631 | 632 | [[package]] 633 | name = "lru" 634 | version = "0.12.3" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" 637 | dependencies = [ 638 | "hashbrown", 639 | ] 640 | 641 | [[package]] 642 | name = "matchers" 643 | version = "0.1.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 646 | dependencies = [ 647 | "regex-automata 0.1.10", 648 | ] 649 | 650 | [[package]] 651 | name = "mdns-sd" 652 | version = "0.14.1" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "8e0a59b04e17a195b0674198b3182931801c4759d00f36acad51b5a97210a692" 655 | dependencies = [ 656 | "fastrand", 657 | "flume", 658 | "if-addrs", 659 | "log", 660 | "mio", 661 | "socket-pktinfo", 662 | "socket2", 663 | ] 664 | 665 | [[package]] 666 | name = "memchr" 667 | version = "2.7.2" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 670 | 671 | [[package]] 672 | name = "miniz_oxide" 673 | version = "0.7.3" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" 676 | dependencies = [ 677 | "adler", 678 | ] 679 | 680 | [[package]] 681 | name = "mio" 682 | version = "1.0.4" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 685 | dependencies = [ 686 | "libc", 687 | "log", 688 | "wasi", 689 | "windows-sys 0.59.0", 690 | ] 691 | 692 | [[package]] 693 | name = "nanorand" 694 | version = "0.7.0" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 697 | dependencies = [ 698 | "getrandom", 699 | ] 700 | 701 | [[package]] 702 | name = "nu-ansi-term" 703 | version = "0.46.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 706 | dependencies = [ 707 | "overload", 708 | "winapi", 709 | ] 710 | 711 | [[package]] 712 | name = "num-conv" 713 | version = "0.1.0" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 716 | 717 | [[package]] 718 | name = "object" 719 | version = "0.32.2" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 722 | dependencies = [ 723 | "memchr", 724 | ] 725 | 726 | [[package]] 727 | name = "once_cell" 728 | version = "1.19.0" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 731 | 732 | [[package]] 733 | name = "overload" 734 | version = "0.1.1" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 737 | 738 | [[package]] 739 | name = "owo-colors" 740 | version = "4.2.2" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" 743 | 744 | [[package]] 745 | name = "parking_lot" 746 | version = "0.12.4" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 749 | dependencies = [ 750 | "lock_api", 751 | "parking_lot_core", 752 | ] 753 | 754 | [[package]] 755 | name = "parking_lot_core" 756 | version = "0.9.11" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 759 | dependencies = [ 760 | "cfg-if", 761 | "libc", 762 | "redox_syscall", 763 | "smallvec", 764 | "windows-targets 0.52.6", 765 | ] 766 | 767 | [[package]] 768 | name = "paste" 769 | version = "1.0.15" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 772 | 773 | [[package]] 774 | name = "pin-project-lite" 775 | version = "0.2.14" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 778 | 779 | [[package]] 780 | name = "powerfmt" 781 | version = "0.2.0" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 784 | 785 | [[package]] 786 | name = "proc-macro2" 787 | version = "1.0.101" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 790 | dependencies = [ 791 | "unicode-ident", 792 | ] 793 | 794 | [[package]] 795 | name = "quote" 796 | version = "1.0.40" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 799 | dependencies = [ 800 | "proc-macro2", 801 | ] 802 | 803 | [[package]] 804 | name = "ratatui" 805 | version = "0.29.0" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 808 | dependencies = [ 809 | "bitflags", 810 | "cassowary", 811 | "compact_str", 812 | "crossterm 0.28.1", 813 | "indoc", 814 | "instability", 815 | "itertools 0.13.0", 816 | "lru", 817 | "paste", 818 | "strum", 819 | "unicode-segmentation", 820 | "unicode-truncate", 821 | "unicode-width 0.2.0", 822 | ] 823 | 824 | [[package]] 825 | name = "redox_syscall" 826 | version = "0.5.1" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" 829 | dependencies = [ 830 | "bitflags", 831 | ] 832 | 833 | [[package]] 834 | name = "regex" 835 | version = "1.11.1" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 838 | dependencies = [ 839 | "aho-corasick", 840 | "memchr", 841 | "regex-automata 0.4.9", 842 | "regex-syntax 0.8.5", 843 | ] 844 | 845 | [[package]] 846 | name = "regex-automata" 847 | version = "0.1.10" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 850 | dependencies = [ 851 | "regex-syntax 0.6.29", 852 | ] 853 | 854 | [[package]] 855 | name = "regex-automata" 856 | version = "0.4.9" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 859 | dependencies = [ 860 | "aho-corasick", 861 | "memchr", 862 | "regex-syntax 0.8.5", 863 | ] 864 | 865 | [[package]] 866 | name = "regex-syntax" 867 | version = "0.6.29" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 870 | 871 | [[package]] 872 | name = "regex-syntax" 873 | version = "0.8.5" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 876 | 877 | [[package]] 878 | name = "rustc-demangle" 879 | version = "0.1.24" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 882 | 883 | [[package]] 884 | name = "rustix" 885 | version = "0.38.44" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 888 | dependencies = [ 889 | "bitflags", 890 | "errno", 891 | "libc", 892 | "linux-raw-sys 0.4.15", 893 | "windows-sys 0.59.0", 894 | ] 895 | 896 | [[package]] 897 | name = "rustix" 898 | version = "1.0.8" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" 901 | dependencies = [ 902 | "bitflags", 903 | "errno", 904 | "libc", 905 | "linux-raw-sys 0.9.4", 906 | "windows-sys 0.60.2", 907 | ] 908 | 909 | [[package]] 910 | name = "rustversion" 911 | version = "1.0.17" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 914 | 915 | [[package]] 916 | name = "ryu" 917 | version = "1.0.18" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 920 | 921 | [[package]] 922 | name = "scopeguard" 923 | version = "1.2.0" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 926 | 927 | [[package]] 928 | name = "serde" 929 | version = "1.0.204" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" 932 | dependencies = [ 933 | "serde_derive", 934 | ] 935 | 936 | [[package]] 937 | name = "serde_derive" 938 | version = "1.0.204" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" 941 | dependencies = [ 942 | "proc-macro2", 943 | "quote", 944 | "syn", 945 | ] 946 | 947 | [[package]] 948 | name = "sharded-slab" 949 | version = "0.1.7" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 952 | dependencies = [ 953 | "lazy_static", 954 | ] 955 | 956 | [[package]] 957 | name = "signal-hook" 958 | version = "0.3.17" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 961 | dependencies = [ 962 | "libc", 963 | "signal-hook-registry", 964 | ] 965 | 966 | [[package]] 967 | name = "signal-hook-mio" 968 | version = "0.2.4" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 971 | dependencies = [ 972 | "libc", 973 | "mio", 974 | "signal-hook", 975 | ] 976 | 977 | [[package]] 978 | name = "signal-hook-registry" 979 | version = "1.4.2" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 982 | dependencies = [ 983 | "libc", 984 | ] 985 | 986 | [[package]] 987 | name = "smallvec" 988 | version = "1.13.2" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 991 | 992 | [[package]] 993 | name = "smawk" 994 | version = "0.3.2" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" 997 | 998 | [[package]] 999 | name = "socket-pktinfo" 1000 | version = "0.3.2" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "927136cc2ae6a1b0e66ac6b1210902b75c3f726db004a73bc18686dcd0dcd22f" 1003 | dependencies = [ 1004 | "libc", 1005 | "socket2", 1006 | "windows-sys 0.60.2", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "socket2" 1011 | version = "0.6.0" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" 1014 | dependencies = [ 1015 | "libc", 1016 | "windows-sys 0.59.0", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "spin" 1021 | version = "0.9.8" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1024 | dependencies = [ 1025 | "lock_api", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "static_assertions" 1030 | version = "1.1.0" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1033 | 1034 | [[package]] 1035 | name = "strsim" 1036 | version = "0.11.1" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1039 | 1040 | [[package]] 1041 | name = "strum" 1042 | version = "0.26.3" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 1045 | dependencies = [ 1046 | "strum_macros", 1047 | ] 1048 | 1049 | [[package]] 1050 | name = "strum_macros" 1051 | version = "0.26.4" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 1054 | dependencies = [ 1055 | "heck", 1056 | "proc-macro2", 1057 | "quote", 1058 | "rustversion", 1059 | "syn", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "syn" 1064 | version = "2.0.106" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 1067 | dependencies = [ 1068 | "proc-macro2", 1069 | "quote", 1070 | "unicode-ident", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "textwrap" 1075 | version = "0.16.2" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" 1078 | dependencies = [ 1079 | "smawk", 1080 | "unicode-linebreak", 1081 | "unicode-width 0.2.0", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "thiserror" 1086 | version = "1.0.61" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 1089 | dependencies = [ 1090 | "thiserror-impl", 1091 | ] 1092 | 1093 | [[package]] 1094 | name = "thiserror-impl" 1095 | version = "1.0.61" 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" 1097 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 1098 | dependencies = [ 1099 | "proc-macro2", 1100 | "quote", 1101 | "syn", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "thread_local" 1106 | version = "1.1.8" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1109 | dependencies = [ 1110 | "cfg-if", 1111 | "once_cell", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "time" 1116 | version = "0.3.36" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1119 | dependencies = [ 1120 | "deranged", 1121 | "itoa", 1122 | "num-conv", 1123 | "powerfmt", 1124 | "serde", 1125 | "time-core", 1126 | "time-macros", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "time-core" 1131 | version = "0.1.2" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1134 | 1135 | [[package]] 1136 | name = "time-macros" 1137 | version = "0.2.18" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1140 | dependencies = [ 1141 | "num-conv", 1142 | "time-core", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "tracing" 1147 | version = "0.1.41" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1150 | dependencies = [ 1151 | "pin-project-lite", 1152 | "tracing-attributes", 1153 | "tracing-core", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "tracing-appender" 1158 | version = "0.2.3" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" 1161 | dependencies = [ 1162 | "crossbeam-channel", 1163 | "thiserror", 1164 | "time", 1165 | "tracing-subscriber", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "tracing-attributes" 1170 | version = "0.1.30" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" 1173 | dependencies = [ 1174 | "proc-macro2", 1175 | "quote", 1176 | "syn", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "tracing-core" 1181 | version = "0.1.34" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 1184 | dependencies = [ 1185 | "once_cell", 1186 | "valuable", 1187 | ] 1188 | 1189 | [[package]] 1190 | name = "tracing-error" 1191 | version = "0.2.0" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" 1194 | dependencies = [ 1195 | "tracing", 1196 | "tracing-subscriber", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "tracing-log" 1201 | version = "0.2.0" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1204 | dependencies = [ 1205 | "log", 1206 | "once_cell", 1207 | "tracing-core", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "tracing-subscriber" 1212 | version = "0.3.19" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 1215 | dependencies = [ 1216 | "matchers", 1217 | "nu-ansi-term", 1218 | "once_cell", 1219 | "regex", 1220 | "sharded-slab", 1221 | "smallvec", 1222 | "thread_local", 1223 | "tracing", 1224 | "tracing-core", 1225 | "tracing-log", 1226 | ] 1227 | 1228 | [[package]] 1229 | name = "unicode-ident" 1230 | version = "1.0.12" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1233 | 1234 | [[package]] 1235 | name = "unicode-linebreak" 1236 | version = "0.1.5" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" 1239 | 1240 | [[package]] 1241 | name = "unicode-segmentation" 1242 | version = "1.11.0" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 1245 | 1246 | [[package]] 1247 | name = "unicode-truncate" 1248 | version = "1.0.0" 1249 | source = "registry+https://github.com/rust-lang/crates.io-index" 1250 | checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" 1251 | dependencies = [ 1252 | "itertools 0.12.1", 1253 | "unicode-width 0.1.12", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "unicode-width" 1258 | version = "0.1.12" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" 1261 | 1262 | [[package]] 1263 | name = "unicode-width" 1264 | version = "0.2.0" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1267 | 1268 | [[package]] 1269 | name = "utf8parse" 1270 | version = "0.2.1" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1273 | 1274 | [[package]] 1275 | name = "valuable" 1276 | version = "0.1.0" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1279 | 1280 | [[package]] 1281 | name = "version_check" 1282 | version = "0.9.4" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1285 | 1286 | [[package]] 1287 | name = "wasi" 1288 | version = "0.11.0+wasi-snapshot-preview1" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1291 | 1292 | [[package]] 1293 | name = "wasm-bindgen" 1294 | version = "0.2.92" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 1297 | dependencies = [ 1298 | "cfg-if", 1299 | "wasm-bindgen-macro", 1300 | ] 1301 | 1302 | [[package]] 1303 | name = "wasm-bindgen-backend" 1304 | version = "0.2.92" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 1307 | dependencies = [ 1308 | "bumpalo", 1309 | "log", 1310 | "once_cell", 1311 | "proc-macro2", 1312 | "quote", 1313 | "syn", 1314 | "wasm-bindgen-shared", 1315 | ] 1316 | 1317 | [[package]] 1318 | name = "wasm-bindgen-macro" 1319 | version = "0.2.92" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 1322 | dependencies = [ 1323 | "quote", 1324 | "wasm-bindgen-macro-support", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "wasm-bindgen-macro-support" 1329 | version = "0.2.92" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 1332 | dependencies = [ 1333 | "proc-macro2", 1334 | "quote", 1335 | "syn", 1336 | "wasm-bindgen-backend", 1337 | "wasm-bindgen-shared", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "wasm-bindgen-shared" 1342 | version = "0.2.92" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 1345 | 1346 | [[package]] 1347 | name = "winapi" 1348 | version = "0.3.9" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1351 | dependencies = [ 1352 | "winapi-i686-pc-windows-gnu", 1353 | "winapi-x86_64-pc-windows-gnu", 1354 | ] 1355 | 1356 | [[package]] 1357 | name = "winapi-i686-pc-windows-gnu" 1358 | version = "0.4.0" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1361 | 1362 | [[package]] 1363 | name = "winapi-x86_64-pc-windows-gnu" 1364 | version = "0.4.0" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1367 | 1368 | [[package]] 1369 | name = "windows-link" 1370 | version = "0.1.3" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 1373 | 1374 | [[package]] 1375 | name = "windows-sys" 1376 | version = "0.52.0" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1379 | dependencies = [ 1380 | "windows-targets 0.52.6", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "windows-sys" 1385 | version = "0.59.0" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1388 | dependencies = [ 1389 | "windows-targets 0.52.6", 1390 | ] 1391 | 1392 | [[package]] 1393 | name = "windows-sys" 1394 | version = "0.60.2" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 1397 | dependencies = [ 1398 | "windows-targets 0.53.3", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "windows-targets" 1403 | version = "0.52.6" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1406 | dependencies = [ 1407 | "windows_aarch64_gnullvm 0.52.6", 1408 | "windows_aarch64_msvc 0.52.6", 1409 | "windows_i686_gnu 0.52.6", 1410 | "windows_i686_gnullvm 0.52.6", 1411 | "windows_i686_msvc 0.52.6", 1412 | "windows_x86_64_gnu 0.52.6", 1413 | "windows_x86_64_gnullvm 0.52.6", 1414 | "windows_x86_64_msvc 0.52.6", 1415 | ] 1416 | 1417 | [[package]] 1418 | name = "windows-targets" 1419 | version = "0.53.3" 1420 | source = "registry+https://github.com/rust-lang/crates.io-index" 1421 | checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" 1422 | dependencies = [ 1423 | "windows-link", 1424 | "windows_aarch64_gnullvm 0.53.0", 1425 | "windows_aarch64_msvc 0.53.0", 1426 | "windows_i686_gnu 0.53.0", 1427 | "windows_i686_gnullvm 0.53.0", 1428 | "windows_i686_msvc 0.53.0", 1429 | "windows_x86_64_gnu 0.53.0", 1430 | "windows_x86_64_gnullvm 0.53.0", 1431 | "windows_x86_64_msvc 0.53.0", 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "windows_aarch64_gnullvm" 1436 | version = "0.52.6" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1439 | 1440 | [[package]] 1441 | name = "windows_aarch64_gnullvm" 1442 | version = "0.53.0" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 1445 | 1446 | [[package]] 1447 | name = "windows_aarch64_msvc" 1448 | version = "0.52.6" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1451 | 1452 | [[package]] 1453 | name = "windows_aarch64_msvc" 1454 | version = "0.53.0" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 1457 | 1458 | [[package]] 1459 | name = "windows_i686_gnu" 1460 | version = "0.52.6" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1463 | 1464 | [[package]] 1465 | name = "windows_i686_gnu" 1466 | version = "0.53.0" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 1469 | 1470 | [[package]] 1471 | name = "windows_i686_gnullvm" 1472 | version = "0.52.6" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1475 | 1476 | [[package]] 1477 | name = "windows_i686_gnullvm" 1478 | version = "0.53.0" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 1481 | 1482 | [[package]] 1483 | name = "windows_i686_msvc" 1484 | version = "0.52.6" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1487 | 1488 | [[package]] 1489 | name = "windows_i686_msvc" 1490 | version = "0.53.0" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 1493 | 1494 | [[package]] 1495 | name = "windows_x86_64_gnu" 1496 | version = "0.52.6" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1499 | 1500 | [[package]] 1501 | name = "windows_x86_64_gnu" 1502 | version = "0.53.0" 1503 | source = "registry+https://github.com/rust-lang/crates.io-index" 1504 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 1505 | 1506 | [[package]] 1507 | name = "windows_x86_64_gnullvm" 1508 | version = "0.52.6" 1509 | source = "registry+https://github.com/rust-lang/crates.io-index" 1510 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1511 | 1512 | [[package]] 1513 | name = "windows_x86_64_gnullvm" 1514 | version = "0.53.0" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 1517 | 1518 | [[package]] 1519 | name = "windows_x86_64_msvc" 1520 | version = "0.52.6" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1523 | 1524 | [[package]] 1525 | name = "windows_x86_64_msvc" 1526 | version = "0.53.0" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 1529 | 1530 | [[package]] 1531 | name = "zerocopy" 1532 | version = "0.7.34" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" 1535 | dependencies = [ 1536 | "zerocopy-derive", 1537 | ] 1538 | 1539 | [[package]] 1540 | name = "zerocopy-derive" 1541 | version = "0.7.34" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" 1544 | dependencies = [ 1545 | "proc-macro2", 1546 | "quote", 1547 | "syn", 1548 | ] 1549 | --------------------------------------------------------------------------------