diff --git a/.github/workflows/homebrew_tap_update.yaml b/.github/workflows/homebrew_tap_update.yaml index 61bfb02e..efbbdc03 100644 --- a/.github/workflows/homebrew_tap_update.yaml +++ b/.github/workflows/homebrew_tap_update.yaml @@ -16,7 +16,7 @@ jobs: if: "!contains(github.ref, '-')" # skip prereleases with: formula-name: cyme - formula-path: cyme.rb + formula-path: Formula/cyme.rb homebrew-tap: tuna-f1sh/homebrew-cyme download-url: https://github.com/tuna-f1sh/cyme/releases/download/${{ steps.extract-version.outputs.tag-name }}/cyme-v${{ steps.extract-version.outputs.tag-name }}-x86_64-apple-darwin.tar.gz commit-message: | diff --git a/Cargo.lock b/Cargo.lock index 30c10ebf..4e6ee825 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,7 +200,7 @@ dependencies = [ [[package]] name = "cyme" -version = "0.8.5" +version = "0.9.0" dependencies = [ "clap", "clap_complete", diff --git a/Cargo.toml b/Cargo.toml index 2f5c75ee..f11c6755 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ description = "List system USB buses and devices; a modern and compatiable `lsus repository = "https://github.com/tuna-f1sh/cyme" readme = "README.md" license = "GPL-3.0-or-later" -version = "0.8.5" +version = "0.9.0" edition = "2021" keywords = ["usb", "lsusb", "system_profiler", "macos", "libusb"] categories = ["command-line-utilities"] diff --git a/README.md b/README.md index 2931b76f..d6b6d856 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ If wishing to use only macOS `system_profiler` and not obtain more verbose infor I also have a Homebrew tap, which will also install a man page and completions: ``` -brew tap tuna-f1sh/cyme +brew tap tuna-f1sh/taps brew install cyme ``` diff --git a/doc/_cyme b/doc/_cyme index 98adec57..81ed2593 100644 --- a/doc/_cyme +++ b/doc/_cyme @@ -109,6 +109,8 @@ no-sort\:"No sorting; whatever order it was parsed"))' \ '--group-devices=[Group devices by value when listing]:GROUP_DEVICES:((no-group\:"No grouping" bus\:"Group into buses with bus info as heading - like a flat tree"))' \ '--from-json=[Read from json output rather than profiling system - must use --tree json dump]:FROM_JSON: ' \ +'-c+[Path to user config file to use for custom icons and colours]:CONFIG: ' \ +'--config=[Path to user config file to use for custom icons and colours]:CONFIG: ' \ '-l[Attempt to maintain compatibility with lsusb output]' \ '--lsusb[Attempt to maintain compatibility with lsusb output]' \ '-t[Dump USB device hierarchy as a tree]' \ @@ -128,7 +130,7 @@ bus\:"Group into buses with bus info as heading - like a flat tree"))' \ '--json[Output as json format after sorting, filters and tree settings are applied; without -tree will be flattened dump of devices]' \ '-F[Force libusb profiler on macOS rather than using/combining system_profiler output]' \ '--force-libusb[Force libusb profiler on macOS rather than using/combining system_profiler output]' \ -'*-c[Turn debugging information on. Alternatively can use RUST_LOG env: INFO, DEBUG, TRACE]' \ +'*-z[Turn debugging information on. Alternatively can use RUST_LOG env: INFO, DEBUG, TRACE]' \ '*--debug[Turn debugging information on. Alternatively can use RUST_LOG env: INFO, DEBUG, TRACE]' \ '--gen[Generate cli completions and man page]' \ '-h[Print help information (use `--help` for more detail)]' \ diff --git a/doc/_cyme.ps1 b/doc/_cyme.ps1 index c3606c6a..f7bf1a99 100644 --- a/doc/_cyme.ps1 +++ b/doc/_cyme.ps1 @@ -38,6 +38,8 @@ Register-ArgumentCompleter -Native -CommandName 'cyme' -ScriptBlock { [CompletionResult]::new('--sort-devices', 'sort-devices', [CompletionResultType]::ParameterName, 'Sort devices by value') [CompletionResult]::new('--group-devices', 'group-devices', [CompletionResultType]::ParameterName, 'Group devices by value when listing') [CompletionResult]::new('--from-json', 'from-json', [CompletionResultType]::ParameterName, 'Read from json output rather than profiling system - must use --tree json dump') + [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Path to user config file to use for custom icons and colours') + [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'Path to user config file to use for custom icons and colours') [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'Attempt to maintain compatibility with lsusb output') [CompletionResult]::new('--lsusb', 'lsusb', [CompletionResultType]::ParameterName, 'Attempt to maintain compatibility with lsusb output') [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Dump USB device hierarchy as a tree') @@ -57,7 +59,7 @@ Register-ArgumentCompleter -Native -CommandName 'cyme' -ScriptBlock { [CompletionResult]::new('--json', 'json', [CompletionResultType]::ParameterName, 'Output as json format after sorting, filters and tree settings are applied; without -tree will be flattened dump of devices') [CompletionResult]::new('-F', 'F', [CompletionResultType]::ParameterName, 'Force libusb profiler on macOS rather than using/combining system_profiler output') [CompletionResult]::new('--force-libusb', 'force-libusb', [CompletionResultType]::ParameterName, 'Force libusb profiler on macOS rather than using/combining system_profiler output') - [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Turn debugging information on. Alternatively can use RUST_LOG env: INFO, DEBUG, TRACE') + [CompletionResult]::new('-z', 'z', [CompletionResultType]::ParameterName, 'Turn debugging information on. Alternatively can use RUST_LOG env: INFO, DEBUG, TRACE') [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'Turn debugging information on. Alternatively can use RUST_LOG env: INFO, DEBUG, TRACE') [CompletionResult]::new('--gen', 'gen', [CompletionResultType]::ParameterName, 'Generate cli completions and man page') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information (use `--help` for more detail)') diff --git a/doc/cyme.1 b/doc/cyme.1 index ab26b95f..b2822ac1 100644 --- a/doc/cyme.1 +++ b/doc/cyme.1 @@ -1,10 +1,10 @@ .ie \n(.g .ds Aq \(aq .el .ds Aq ' -.TH cyme 1 "cyme 0.8.5" +.TH cyme 1 "cyme 0.9.0" .SH NAME cyme \- List system USB buses and devices; a modern and compatiable `lsusb` .SH SYNOPSIS -\fBcyme\fR [\fB\-l\fR|\fB\-\-lsusb\fR] [\fB\-t\fR|\fB\-\-tree\fR] [\fB\-d\fR|\fB\-\-vidpid\fR] [\fB\-s\fR|\fB\-\-show\fR] [\fB\-D\fR|\fB\-\-device\fR] [\fB\-\-filter\-name\fR] [\fB\-\-filter\-serial\fR] [\fB\-v\fR|\fB\-\-verbose\fR]... [\fB\-b\fR|\fB\-\-blocks\fR] [\fB\-\-bus\-blocks\fR] [\fB\-\-config\-blocks\fR] [\fB\-\-interface\-blocks\fR] [\fB\-\-endpoint\-blocks\fR] [\fB\-m\fR|\fB\-\-more\fR] [\fB\-\-sort\-devices\fR] [\fB\-\-sort\-buses\fR] [\fB\-\-group\-devices\fR] [\fB\-\-hide\-buses\fR] [\fB\-\-hide\-hubs\fR] [\fB\-\-decimal\fR] [\fB\-\-no\-padding\fR] [\fB\-\-no\-colour\fR] [\fB\-\-ascii\fR] [\fB\-\-headings\fR] [\fB\-\-json\fR] [\fB\-\-from\-json\fR] [\fB\-F\fR|\fB\-\-force\-libusb\fR] [\fB\-c\fR|\fB\-\-debug\fR]... [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] +\fBcyme\fR [\fB\-l\fR|\fB\-\-lsusb\fR] [\fB\-t\fR|\fB\-\-tree\fR] [\fB\-d\fR|\fB\-\-vidpid\fR] [\fB\-s\fR|\fB\-\-show\fR] [\fB\-D\fR|\fB\-\-device\fR] [\fB\-\-filter\-name\fR] [\fB\-\-filter\-serial\fR] [\fB\-v\fR|\fB\-\-verbose\fR]... [\fB\-b\fR|\fB\-\-blocks\fR] [\fB\-\-bus\-blocks\fR] [\fB\-\-config\-blocks\fR] [\fB\-\-interface\-blocks\fR] [\fB\-\-endpoint\-blocks\fR] [\fB\-m\fR|\fB\-\-more\fR] [\fB\-\-sort\-devices\fR] [\fB\-\-sort\-buses\fR] [\fB\-\-group\-devices\fR] [\fB\-\-hide\-buses\fR] [\fB\-\-hide\-hubs\fR] [\fB\-\-decimal\fR] [\fB\-\-no\-padding\fR] [\fB\-\-no\-colour\fR] [\fB\-\-ascii\fR] [\fB\-\-headings\fR] [\fB\-\-json\fR] [\fB\-\-from\-json\fR] [\fB\-F\fR|\fB\-\-force\-libusb\fR] [\fB\-c\fR|\fB\-\-config\fR] [\fB\-z\fR|\fB\-\-debug\fR]... [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] .SH DESCRIPTION List system USB buses and devices; a modern and compatiable `lsusb` .SH OPTIONS @@ -254,7 +254,10 @@ Read from json output rather than profiling system \- must use \-\-tree json dum \fB\-F\fR, \fB\-\-force\-libusb\fR=\fIFORCE_LIBUSB\fR Force libusb profiler on macOS rather than using/combining system_profiler output .TP -\fB\-c\fR, \fB\-\-debug\fR=\fIDEBUG\fR +\fB\-c\fR, \fB\-\-config\fR=\fICONFIG\fR +Path to user config file to use for custom icons and colours +.TP +\fB\-z\fR, \fB\-\-debug\fR=\fIDEBUG\fR Turn debugging information on. Alternatively can use RUST_LOG env: INFO, DEBUG, TRACE .TP \fB\-h\fR, \fB\-\-help\fR @@ -263,6 +266,6 @@ Print help information (use `\-h` for a summary) \fB\-V\fR, \fB\-\-version\fR Print version information .SH VERSION -v0.8.5 +v0.9.0 .SH AUTHORS John Whittington diff --git a/doc/cyme.bash b/doc/cyme.bash index 605d2864..fa667515 100644 --- a/doc/cyme.bash +++ b/doc/cyme.bash @@ -19,7 +19,7 @@ _cyme() { case "${cmd}" in cyme) - opts="-l -t -d -s -D -v -b -m -F -c -h -V --lsusb --tree --vidpid --show --device --filter-name --filter-serial --verbose --blocks --bus-blocks --config-blocks --interface-blocks --endpoint-blocks --more --sort-devices --sort-buses --group-devices --hide-buses --hide-hubs --decimal --no-padding --no-colour --ascii --headings --json --from-json --force-libusb --debug --gen --help --version" + opts="-l -t -d -s -D -v -b -m -F -c -z -h -V --lsusb --tree --vidpid --show --device --filter-name --filter-serial --verbose --blocks --bus-blocks --config-blocks --interface-blocks --endpoint-blocks --more --sort-devices --sort-buses --group-devices --hide-buses --hide-hubs --decimal --no-padding --no-colour --ascii --headings --json --from-json --force-libusb --config --debug --gen --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -93,6 +93,14 @@ _cyme() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --config) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -c) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; diff --git a/doc/cyme.fish b/doc/cyme.fish index a901d308..deaa47bd 100644 --- a/doc/cyme.fish +++ b/doc/cyme.fish @@ -11,6 +11,7 @@ complete -c cyme -l endpoint-blocks -d 'Specify the blocks which will be display complete -c cyme -l sort-devices -d 'Sort devices by value' -r -f -a "{branch-position Sort by position in parent branch,device-number Sort by bus device number,no-sort No sorting; whatever order it was parsed}" complete -c cyme -l group-devices -d 'Group devices by value when listing' -r -f -a "{no-group No grouping,bus Group into buses with bus info as heading - like a flat tree}" complete -c cyme -l from-json -d 'Read from json output rather than profiling system - must use --tree json dump' -r +complete -c cyme -s c -l config -d 'Path to user config file to use for custom icons and colours' -r complete -c cyme -s l -l lsusb -d 'Attempt to maintain compatibility with lsusb output' complete -c cyme -s t -l tree -d 'Dump USB device hierarchy as a tree' complete -c cyme -s v -l verbose -d 'Verbosity level: 1 prints device configurations; 2 prints interfaces; 3 prints interface endpoints; 4 prints everything and all blocks' @@ -25,7 +26,7 @@ complete -c cyme -l ascii -d 'Disables icons and utf-8 charactors' complete -c cyme -l headings -d 'Show block headings' complete -c cyme -l json -d 'Output as json format after sorting, filters and tree settings are applied; without -tree will be flattened dump of devices' complete -c cyme -s F -l force-libusb -d 'Force libusb profiler on macOS rather than using/combining system_profiler output' -complete -c cyme -s c -l debug -d 'Turn debugging information on. Alternatively can use RUST_LOG env: INFO, DEBUG, TRACE' +complete -c cyme -s z -l debug -d 'Turn debugging information on. Alternatively can use RUST_LOG env: INFO, DEBUG, TRACE' complete -c cyme -l gen -d 'Generate cli completions and man page' complete -c cyme -s h -l help -d 'Print help information (use `--help` for more detail)' complete -c cyme -s V -l version -d 'Print version information' diff --git a/doc/cyme_example.json b/doc/cyme_example.json deleted file mode 100644 index 5643955d..00000000 --- a/doc/cyme_example.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "classifier#02": "", - "vid-pid-msb#0483:37": "", - "vid#2e8a": "", - "undefined-classifier": "☶", - "unknown-vendor": "", - "vid#05ac": "", - "vid-pid#1d50:6018": "", - "classifier-sub-protocol#15:01:01": "" -} \ No newline at end of file diff --git a/doc/cyme_example_config.json b/doc/cyme_example_config.json new file mode 100644 index 00000000..a1e21159 --- /dev/null +++ b/doc/cyme_example_config.json @@ -0,0 +1,41 @@ +{ + "icons": { + "icons": { + "vid#05ac": "", + "classifier-sub-protocol#15:01:01": "", + "unknown-vendor": "", + "classifier#02": "", + "undefined-classifier": "☶", + "vid-pid#1d50:6018": "", + "vid-pid-msb#0483:37": "", + "vid#2e8a": "" + }, + "tree": null + }, + "colours": { + "name": "bright blue", + "serial": "green", + "manufacturer": "blue", + "driver": "cyan", + "string": "blue", + "icon": null, + "location": "magenta", + "path": "cyan", + "number": "cyan", + "speed": "magenta", + "vid": "bright yellow", + "pid": "yellow", + "class_code": "bright yellow", + "sub_code": "yellow", + "protocol": "yellow", + "attributes": "magenta", + "power": "red", + "tree": "bright black", + "tree_bus_start": "bright black", + "tree_bus_terminator": "bright black", + "tree_configuration_terminator": "bright black", + "tree_interface_terminator": "bright black", + "tree_endpoint_in": "yellow", + "tree_endpoint_out": "magenta" + } +} diff --git a/src/colour.rs b/src/colour.rs index 3ca4f8f3..ab33afab 100644 --- a/src/colour.rs +++ b/src/colour.rs @@ -1,67 +1,115 @@ //! Colouring of cyme output use colored::*; use std::fmt; -// use serde::de::{self, Visitor, MapAccess, SeqAccess}; -// use serde::{Deserialize, Deserializer, Serialize}; -// use std::str::FromStr; +use serde::{Deserialize, Deserializer, Serialize}; +use serde::ser::SerializeSeq; /// Colours [`Block`] fields based on loose typing of field type /// /// Considered using HashMap with Colouring Enum like IconTheme but this seemed to suit better, it is less flexiable though... -#[derive(Debug)] +#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] pub struct ColourTheme { /// Colour to use for name from descriptor - // #[serde(default)] - // #[serde(deserialize_with = "deserialize_color")] + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub name: Option, /// Colour to use for serial from descriptor + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub serial: Option, /// Colour to use for manufacturer from descriptor + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub manufacturer: Option, /// Colour to use for driver from udev + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub driver: Option, /// Colour to use for general String data + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub string: Option, /// Colour to use for icons + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub icon: Option, /// Colour to use for location data + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub location: Option, /// Colour to use for path data + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub path: Option, /// Colour to use for general number values + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub number: Option, /// Colour to use for speed + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub speed: Option, /// Colour to use for Vendor ID + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub vid: Option, /// Colour to use for Product ID + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub pid: Option, /// Colour to use for generic ClassCode + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub class_code: Option, /// Colour to use for SubCodes + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub sub_code: Option, /// Colour to use for protocol + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub protocol: Option, /// Colour to use for info/enum type + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub attributes: Option, /// Colour to use for power information + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub power: Option, /// Tree colour + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub tree: Option, /// Colour at prepended before printing `USBBus` + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub tree_bus_start: Option, /// Colour printed at end of tree before printing `USBDevice` + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub tree_bus_terminator: Option, /// Colour printed at end of tree before printing configuration + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub tree_configuration_terminator: Option, /// Colour printed at end of tree before printing interface + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub tree_interface_terminator: Option, /// Colour for endpoint in before print + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub tree_endpoint_in: Option, /// Colour for endpoint out before print + #[serde(default, serialize_with = "color_serializer", deserialize_with = "deserialize_option_color_from_string")] pub tree_endpoint_out: Option, } +fn deserialize_option_color_from_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum NumericOrNull<'a> { + Str(&'a str), + #[serde(deserialize_with = "deserialize_color")] + FromStr(Color), + Null, + } + + match NumericOrNull::deserialize(deserializer)? { + NumericOrNull::Str(s) => match s { + "" => Ok(None), + _ => { + Color::try_from(s).map(Some).map_err(serde::de::Error::custom) + } + }, + NumericOrNull::FromStr(i) => Ok(Some(i)), + NumericOrNull::Null => Ok(None), + } +} + + // Custom color deserialize, adapted from: https://github.com/Peltoche/lsd/blob/master/src/theme/color.rs fn deserialize_color<'de, D>(deserializer: D) -> Result where @@ -123,6 +171,54 @@ where deserializer.deserialize_any(ColorVisitor) } +fn color_to_string(color: Color) -> String { + match color { + Color::Black => "black".into(), + Color::Red => "red".into(), + Color::Green => "green".into(), + Color::Yellow => "yellow".into(), + Color::Blue => "blue".into(), + Color::Magenta => "magenta".into(), + Color::Cyan => "cyan".into(), + Color::White => "white".into(), + Color::BrightBlack => "bright black".into(), + Color::BrightRed => "bright red".into(), + Color::BrightGreen => "bright green".into(), + Color::BrightYellow => "bright yellow".into(), + Color::BrightBlue => "bright blue".into(), + Color::BrightMagenta => "bright magenta".into(), + Color::BrightCyan => "bright cyan".into(), + Color::BrightWhite => "bright white".into(), + Color::TrueColor { r, g, b } => format!("[{}, {}, {}]", r, g ,b), + } +} + +/// Have to make this because external crate does not impl Display +fn color_serializer<'a, S>(color: &'a Option, s: S) -> Result +where + S: serde::ser::Serializer { + match color { + Some(c) => match c { + Color::TrueColor { r, g, b } => { + let mut seq = s.serialize_seq(Some(3))?; + seq.serialize_element(r)?; + seq.serialize_element(g)?; + seq.serialize_element(b)?; + seq.end() + }, + _ => s.serialize_str(&color_to_string(*c)), + } + None => s.serialize_none(), + } +} + +impl Default for ColourTheme { + fn default() -> Self { + ColourTheme::new() + } +} + + impl ColourTheme { /// New theme with defaults pub fn new() -> Self { @@ -155,13 +251,27 @@ impl ColourTheme { } } -// #[cfg(test)] -// mod tests { -// use super::*; +#[cfg(test)] +mod tests { + use super::*; -// #[test] -// fn test_serialize_color_value() { -// let color_value = ColorValue{ value: Color::Black }; -// println!("{}", serde_json::to_string_pretty(&color_value).unwrap()); -// } -// } + #[test] + fn test_serialize_color_theme() { + let ct: ColourTheme = ColourTheme::new(); + println!("{}", serde_json::to_string_pretty(&ct).unwrap()); + } + + #[test] + fn test_deserialize_color_theme() { + let ct: ColourTheme = serde_json::from_str(r#"{"name": "blue"}"#).unwrap(); + assert_eq!(ct.name, Some(Color::Blue)); + } + + #[test] + fn test_serialize_deserialize_color_theme() { + let ct: ColourTheme = ColourTheme::new(); + let ser = serde_json::to_string_pretty(&ct).unwrap(); + let ctrt: ColourTheme = serde_json::from_str(&ser).unwrap(); + assert_eq!(ct, ctrt); + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..b9f3e898 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,47 @@ +//! Config for cyme binary +use std::io; +use std::fs::File; +use std::io::{BufReader, Read}; +use serde::{Deserialize, Serialize}; + +use crate::icon; +use crate::colour; + +/// Allows user supplied icons to replace or add to `DEFAULT_ICONS` and `DEFAULT_TREE` +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct Config { + /// User supplied [`IconTheme`] - will merge with default + pub icons: icon::IconTheme, + /// User supplied [`ColourTheme`] - overrides default + pub colours: colour::ColourTheme, +} + +impl Config { + /// Default new + pub fn new() -> Config { + Config { + ..Default::default() + } + } + + /// Get example [`Config`] + pub fn example() -> Config { + Config { + icons: icon::example_theme(), + ..Default::default() + } + } + + /// Attempt to read from .json format confg at `file_path` + pub fn from_file(file_path: &str) -> Result { + let f = File::open(file_path)?; + let mut br = BufReader::new(f); + let mut data = String::new(); + + br.read_to_string(&mut data)?; + serde_json::from_str::(&data) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + } +} diff --git a/src/icon.rs b/src/icon.rs index 311555b5..06dc33b1 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -75,7 +75,6 @@ impl FromStr for Icon { .map(|vs| u32::from_str_radix(vs.trim_start_matches("0x"), 16)) .partition(Result::is_ok); let numbers: Vec = parse_ints.into_iter().map(|v| v.unwrap() as u16).collect(); - println!("{:?} {:?}", numbers, errors); if !errors.is_empty() { return Err(io::Error::new(io::ErrorKind::Other, "Invalid value in enum string after #")); @@ -172,10 +171,10 @@ lazy_static! { (Icon::TreeDeviceTerminator, "\u{25CB}".into()), // "○" (Icon::TreeConfigurationTerminiator, "\u{2022}".into()), // "•" (Icon::TreeInterfaceTerminiator, "\u{25E6}".into()), // "◦" - // (Icon::Endpoint(Direction::In), "\u{2192}".into()), // → - // (Icon::Endpoint(Direction::Out), "\u{2190}".into()), // ← - (Icon::Endpoint(Direction::In), ">".into()), // → - (Icon::Endpoint(Direction::Out), "<".into()), // ← + (Icon::Endpoint(Direction::In), "\u{2192}".into()), // → + (Icon::Endpoint(Direction::Out), "\u{2190}".into()), // ← + // (Icon::Endpoint(Direction::In), ">".into()), // → + // (Icon::Endpoint(Direction::Out), "<".into()), // ← ]) }; @@ -387,7 +386,7 @@ pub fn defaults() -> HashMap { } /// Returns example list of icons with all [`Icon`] types -pub fn example() -> HashMap { +pub fn example() -> HashMap { HashMap::from([ (Icon::UnknownVendor, "\u{f287}".into()), // usb plug default  (Icon::Vid(0x05ac), "\u{f179}".into()), // apple  @@ -400,6 +399,13 @@ pub fn example() -> HashMap { ]) } +/// Returns example theme with [`Icon`] types and default tree +pub fn example_theme() -> IconTheme { + // TODO + let tree = DEFAULT_TREE.clone(); + IconTheme { icons: Some(example()), tree: None } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index 815cbe91..34a23d93 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ extern crate lazy_static; pub mod display; pub mod icon; pub mod colour; +pub mod config; pub mod system_profiler; pub mod types; pub mod usb; diff --git a/src/main.rs b/src/main.rs index 61763cf0..7fd3840b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,15 +15,12 @@ use clap::CommandFactory; #[cfg(feature = "cli_generate")] use clap_complete::shells::*; #[cfg(feature = "cli_generate")] -use cyme::icon::example; -#[cfg(feature = "cli_generate")] use std::path::PathBuf; use cyme::display; -use cyme::icon::IconTheme; -use cyme::colour::ColourTheme; use cyme::system_profiler; use cyme::lsusb; +use cyme::config::Config; #[derive(Parser, Debug, Default, Serialize, Deserialize)] #[skip_serializing_none] @@ -137,8 +134,12 @@ struct Args { #[arg(short='F', long, default_value_t = false)] force_libusb: bool, + /// Path to user config file to use for custom icons, colours and default settings + #[arg(short='c', long)] + config: Option, + /// Turn debugging information on. Alternatively can use RUST_LOG env: INFO, DEBUG, TRACE - #[arg(short = 'c', long, action = clap::ArgAction::Count)] // short -d taken by lsusb compat vid:pid + #[arg(short = 'z', long, action = clap::ArgAction::Count)] // short -d taken by lsusb compat vid:pid debug: u8, /// Generate cli completions and man page @@ -330,8 +331,8 @@ fn print_man() -> Result<(), Error> { std::fs::write(PathBuf::from(&outdir).join("cyme.1"), buffer)?; - // TODO example config - std::fs::write(PathBuf::from(&outdir).join("cyme_example_config.json"), serde_json::to_string_pretty(&example()).unwrap())?; + // example config + std::fs::write(PathBuf::from(&outdir).join("cyme_example_config.json"), serde_json::to_string_pretty(&Config::example()).unwrap())?; Ok(()) } @@ -368,18 +369,31 @@ fn main() { )); }); + let config = if let Some(path) = args.config.as_ref() { + let config = Config::from_file(&path).unwrap_or_else(|e| { + eprintexit!(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Failed to parse user conifg at {}: Error({})", path, e) + )); + }); + log::info!("Using user config {:?}", config); + config + } else { + Config::new() + }; + let colours = if args.no_colour { // set env to be sure too env::set_var("NO_COLOR", "1"); None } else { - Some(ColourTheme::new()) + Some(config.colours) }; let icons = if args.ascii { None } else { - Some(IconTheme::new()) + Some(config.icons) }; let mut spusb = if let Some(file_path) = args.from_json {