add initial parse phase for add subcommand (keybind), improve key parsing
This commit is contained in:
parent
669441bf4c
commit
7498338b9e
|
@ -257,6 +257,15 @@ version = "0.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12f9a0700e0127ba15d1d52dd742097f821cd9c65939303a44d970465040a297"
|
||||
dependencies = [
|
||||
"error-code",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cnx"
|
||||
version = "0.3.0"
|
||||
|
@ -309,6 +318,12 @@ version = "1.10.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
||||
|
||||
[[package]]
|
||||
name = "endian-type"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.2"
|
||||
|
@ -338,6 +353,23 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-code"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b"
|
||||
|
||||
[[package]]
|
||||
name = "fd-lock"
|
||||
version = "4.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.30"
|
||||
|
@ -553,6 +585,7 @@ dependencies = [
|
|||
"paste",
|
||||
"pretty_assertions",
|
||||
"pretty_env_logger",
|
||||
"rustyline",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
|
@ -685,6 +718,26 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nibble_vec"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.18"
|
||||
|
@ -877,6 +930,16 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radix_trie"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
|
||||
dependencies = [
|
||||
"endian-type",
|
||||
"nibble_vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.3"
|
||||
|
@ -931,6 +994,28 @@ version = "1.0.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "13.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
"clipboard-win",
|
||||
"fd-lock",
|
||||
"home",
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"nix",
|
||||
"radix_trie",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"utf8parse",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.17"
|
||||
|
@ -1189,6 +1274,18 @@ version = "1.0.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
|
|
|
@ -18,6 +18,7 @@ which = "6.0.0"
|
|||
log = "0.4"
|
||||
pretty_env_logger = "0.5"
|
||||
cnx = { git = "https://github.com/mjkillough/cnx.git", rev = "7845d99baa296901171c083db61588a62f9a8b34" }
|
||||
rustyline = "13.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
use clap::Subcommand;
|
||||
use rustyline::{error::ReadlineError, DefaultEditor};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::hlwm::{
|
||||
command::{CommandParseError, HlwmCommand},
|
||||
key::{Key, KeyParseError},
|
||||
};
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum Add {
|
||||
/// Add a new keybind to the existing hlctl config
|
||||
#[clap(arg_required_else_help = true)]
|
||||
Keybind {
|
||||
#[arg(short, long)]
|
||||
interactive: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum Error {
|
||||
#[error("readline error: [{0}]")]
|
||||
ReadlineError(String),
|
||||
#[error("key parse error: [{0}]")]
|
||||
KeyParseError(#[from] KeyParseError),
|
||||
#[error("could not parse command: [{0}]")]
|
||||
CommandParseError(#[from] CommandParseError),
|
||||
}
|
||||
|
||||
impl From<ReadlineError> for Error {
|
||||
fn from(value: ReadlineError) -> Self {
|
||||
Self::ReadlineError(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Add {
|
||||
pub fn run(self) -> Result<(), Error> {
|
||||
match self {
|
||||
Add::Keybind { interactive } => {
|
||||
if interactive {
|
||||
keybind_interactive()
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn keybind_interactive() -> Result<(), Error> {
|
||||
let mut read = DefaultEditor::new()?;
|
||||
let keys = read
|
||||
.readline("Keys (Separated by spaces, tabs, +, or -): ")?
|
||||
.trim()
|
||||
.split([' ', '\t', '+', '-'])
|
||||
.map(|l| l.trim())
|
||||
.filter(|l| !l.is_empty())
|
||||
.map(|k| Key::from_str_case_insensitive(k))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let command = {
|
||||
let read = read.readline("Command (Arguments separated by tabs or spaces): ")?;
|
||||
let mut args = read
|
||||
.trim()
|
||||
.split([' ', '\t'])
|
||||
.map(|a| a.trim())
|
||||
.filter(|a| !a.is_empty())
|
||||
.map(|a| a.to_string());
|
||||
match args.next() {
|
||||
Some(cmd) => HlwmCommand::from_raw_parts(&cmd, args.collect())?,
|
||||
None => {
|
||||
println!("No command provided");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
println!("{keys:?} -> {command:?}");
|
||||
Ok(())
|
||||
}
|
|
@ -960,6 +960,9 @@ mod test {
|
|||
value: Attribute::Color(Color::X11(X11Color::NavajoWhite3)),
|
||||
});
|
||||
let cfg_serialized = cfg.serialize().unwrap();
|
||||
println!("--> Config <--");
|
||||
println!("{cfg_serialized}");
|
||||
println!("--> Config <--");
|
||||
let parsed_cfg = Config::deserialize(&cfg_serialized).unwrap();
|
||||
|
||||
assert_eq!(cfg, parsed_cfg,)
|
||||
|
|
|
@ -272,7 +272,7 @@ impl Default for HlwmCommand {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum CommandParseError {
|
||||
#[error("unknown command [{0}]")]
|
||||
UnknownCommand(String),
|
||||
|
@ -287,11 +287,17 @@ pub enum CommandParseError {
|
|||
#[error("command execution error: [{0}]")]
|
||||
CommandError(#[from] CommandError),
|
||||
#[error("string utf8 error")]
|
||||
StringUtf8Error(#[from] FromUtf8Error),
|
||||
StringUtf8Error(String),
|
||||
#[error("parsing string value error: [{0}]")]
|
||||
StringParseError(#[from] StringParseError),
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for CommandParseError {
|
||||
fn from(value: FromUtf8Error) -> Self {
|
||||
Self::StringUtf8Error(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::de::Expected for CommandParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "expected valid command string. Got error: {self}")
|
||||
|
|
216
src/hlwm/key.rs
216
src/hlwm/key.rs
|
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
|||
use strum::IntoEnumIterator;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::split;
|
||||
use crate::{logerr::UnwrapLog, split};
|
||||
|
||||
use super::{
|
||||
command::{CommandParseError, HlwmCommand},
|
||||
|
@ -43,6 +43,21 @@ pub enum Key {
|
|||
Mouse(MouseButton),
|
||||
}
|
||||
|
||||
fn title_case(s: &str) -> String {
|
||||
let mut chars = s.chars();
|
||||
let mut out_chars = Vec::with_capacity(s.len());
|
||||
|
||||
if let Some(first) = chars.next() {
|
||||
out_chars.push(first.to_ascii_uppercase());
|
||||
}
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
out_chars.push(c.to_ascii_lowercase());
|
||||
}
|
||||
|
||||
out_chars.into_iter().collect()
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub fn is_standard(&self) -> bool {
|
||||
match self {
|
||||
|
@ -62,6 +77,88 @@ impl Key {
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str_case_insensitive(s: &str) -> Result<Self, KeyParseError> {
|
||||
Key::from_str(&title_case(s))
|
||||
}
|
||||
|
||||
/// Returns the name for this key as it should appear in the config
|
||||
pub fn config_name(&self) -> String {
|
||||
self.aliases()
|
||||
.first()
|
||||
.expect_log("all keys should have at least one alias")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Returns a list of aliases for the key
|
||||
pub fn aliases(&self) -> Vec<String> {
|
||||
match self {
|
||||
Key::Mod1Alt => vec!["Alt".into(), "Mod1".into()],
|
||||
Key::Mod4Super => vec!["Super".into(), "Mod4".into()],
|
||||
Key::Return => vec!["Return".into(), "Enter".into()],
|
||||
Key::Shift => vec!["Shift".into()],
|
||||
Key::Tab => vec!["Tab".into()],
|
||||
Key::Left => vec!["Left".into()],
|
||||
Key::Right => vec!["Right".into()],
|
||||
Key::Up => vec!["Up".into()],
|
||||
Key::Down => vec!["Down".into()],
|
||||
Key::Space => vec!["Space".into()],
|
||||
Key::Control => vec!["Ctrl".into(), "Ctl".into(), "Control".into()],
|
||||
Key::Backtick => vec!["Backtick".into(), "Grave".into()],
|
||||
Key::F1 => vec!["F1".into()],
|
||||
Key::F2 => vec!["F2".into()],
|
||||
Key::F3 => vec!["F3".into()],
|
||||
Key::F4 => vec!["F4".into()],
|
||||
Key::F5 => vec!["F5".into()],
|
||||
Key::F6 => vec!["F6".into()],
|
||||
Key::F7 => vec!["F7".into()],
|
||||
Key::F8 => vec!["F8".into()],
|
||||
Key::F9 => vec!["F9".into()],
|
||||
Key::F10 => vec!["F10".into()],
|
||||
Key::F11 => vec!["F11".into()],
|
||||
Key::F12 => vec!["F12".into()],
|
||||
Key::Home => vec!["Home".into()],
|
||||
Key::Delete => vec!["Delete".into(), "Del".into()],
|
||||
Key::Char(c) => vec![c.to_ascii_lowercase().to_string()],
|
||||
Key::Mouse(m) => vec![m.to_string()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Key {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let data = match self {
|
||||
Key::Return => "Return".to_string(),
|
||||
Key::Shift => "Shift".to_string(),
|
||||
Key::Tab => "Tab".to_string(),
|
||||
Key::Left => "Left".to_string(),
|
||||
Key::Right => "Right".to_string(),
|
||||
Key::Up => "Up".to_string(),
|
||||
Key::Down => "Down".to_string(),
|
||||
Key::Space => "space".to_string(),
|
||||
Key::Control => "Control".to_string(),
|
||||
Key::Backtick => "grave".to_string(),
|
||||
Key::Char(c) => c.to_string(),
|
||||
Key::Mouse(m) => m.to_string(),
|
||||
Key::F1 => "F1".to_string(),
|
||||
Key::F2 => "F2".to_string(),
|
||||
Key::F3 => "F3".to_string(),
|
||||
Key::F4 => "F4".to_string(),
|
||||
Key::F5 => "F5".to_string(),
|
||||
Key::F6 => "F6".to_string(),
|
||||
Key::F7 => "F7".to_string(),
|
||||
Key::F8 => "F8".to_string(),
|
||||
Key::F9 => "F9".to_string(),
|
||||
Key::F10 => "F10".to_string(),
|
||||
Key::F11 => "F11".to_string(),
|
||||
Key::F12 => "F12".to_string(),
|
||||
Key::Home => "Home".to_string(),
|
||||
Key::Delete => "Delete".to_string(),
|
||||
Key::Mod1Alt => "Mod1".to_string(),
|
||||
Key::Mod4Super => "Mod4".to_string(),
|
||||
};
|
||||
f.write_str(&data)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MouseButton> for Key {
|
||||
|
@ -81,7 +178,7 @@ impl Serialize for Key {
|
|||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
serializer.serialize_str(&self.config_name())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,7 +194,7 @@ impl<'de> Deserialize<'de> for Key {
|
|||
}
|
||||
}
|
||||
let str_val: String = Deserialize::deserialize(deserializer)?;
|
||||
Ok(Self::from_str(&str_val).map_err(|_| {
|
||||
Ok(Self::from_str_case_insensitive(&str_val).map_err(|_| {
|
||||
serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &ExpectedKey)
|
||||
})?)
|
||||
}
|
||||
|
@ -112,14 +209,16 @@ impl FromStr for Key {
|
|||
Ok(Self::Mouse(MouseButton::from_str(s)?))
|
||||
}
|
||||
_ => {
|
||||
if let Some(key) = Self::iter().into_iter().find(|key| key.to_string() == s) {
|
||||
match key {
|
||||
Key::Char(_) | Key::Mouse(_) => (),
|
||||
_ => return Ok(key),
|
||||
}
|
||||
if let Some(key) = Self::iter().into_iter().find(|key| match key {
|
||||
Key::Char(_) | Key::Mouse(_) => false,
|
||||
_ => key.aliases().contains(&s.to_string()),
|
||||
}) {
|
||||
return Ok(key);
|
||||
}
|
||||
if s.len() == 1 {
|
||||
Ok(Self::Char(s.chars().next().unwrap()))
|
||||
Ok(Self::Char(
|
||||
s.chars().next().unwrap_log().to_ascii_lowercase(),
|
||||
))
|
||||
} else {
|
||||
Err(KeyParseError::ExpectedCharKey(s.to_string()))
|
||||
}
|
||||
|
@ -170,42 +269,6 @@ impl Display for MouseButton {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for Key {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let data = match self {
|
||||
Key::Return => "Return".to_string(),
|
||||
Key::Shift => "Shift".to_string(),
|
||||
Key::Tab => "Tab".to_string(),
|
||||
Key::Left => "Left".to_string(),
|
||||
Key::Right => "Right".to_string(),
|
||||
Key::Up => "Up".to_string(),
|
||||
Key::Down => "Down".to_string(),
|
||||
Key::Space => "space".to_string(),
|
||||
Key::Control => "Control".to_string(),
|
||||
Key::Backtick => "grave".to_string(),
|
||||
Key::Char(c) => c.to_string(),
|
||||
Key::Mouse(m) => m.to_string(),
|
||||
Key::F1 => "F1".to_string(),
|
||||
Key::F2 => "F2".to_string(),
|
||||
Key::F3 => "F3".to_string(),
|
||||
Key::F4 => "F4".to_string(),
|
||||
Key::F5 => "F5".to_string(),
|
||||
Key::F6 => "F6".to_string(),
|
||||
Key::F7 => "F7".to_string(),
|
||||
Key::F8 => "F8".to_string(),
|
||||
Key::F9 => "F9".to_string(),
|
||||
Key::F10 => "F10".to_string(),
|
||||
Key::F11 => "F11".to_string(),
|
||||
Key::F12 => "F12".to_string(),
|
||||
Key::Home => "Home".to_string(),
|
||||
Key::Delete => "Delete".to_string(),
|
||||
Key::Mod1Alt => "Mod1".to_string(),
|
||||
Key::Mod4Super => "Mod4".to_string(),
|
||||
};
|
||||
f.write_str(&data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum MousebindAction {
|
||||
|
@ -489,9 +552,10 @@ mod test {
|
|||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::hlwm::command::HlwmCommand;
|
||||
use crate::{hlwm::command::HlwmCommand, logerr::UnwrapLog};
|
||||
|
||||
use super::{Key, MousebindAction};
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct KeyWrapper {
|
||||
|
@ -501,8 +565,8 @@ mod test {
|
|||
#[test]
|
||||
fn key_serialize_deserialize() {
|
||||
for (original_key, wrapper) in Key::iter().map(|key| (key, KeyWrapper { key })) {
|
||||
let wrapper_str = toml::to_string_pretty(&wrapper).unwrap();
|
||||
let parsed: KeyWrapper = toml::from_str(&wrapper_str).unwrap();
|
||||
let wrapper_str = toml::to_string_pretty(&wrapper).expect_log("serialize");
|
||||
let parsed: KeyWrapper = toml::from_str(&wrapper_str).expect_log("deserialize");
|
||||
assert_eq!(original_key, parsed.key);
|
||||
}
|
||||
}
|
||||
|
@ -527,4 +591,60 @@ mod test {
|
|||
assert_eq!(original_key, parsed.action);
|
||||
}
|
||||
}
|
||||
|
||||
fn case_random<S: Into<String>>(s: S) -> Vec<String> {
|
||||
let s = s.into();
|
||||
let counts: usize = s.chars().map(|c| c.is_ascii_alphabetic() as usize).sum();
|
||||
if counts < 2 {
|
||||
return vec![s.to_string()];
|
||||
}
|
||||
let mut variants: Vec<String> = Vec::with_capacity(counts + 2);
|
||||
let mut working = Vec::new();
|
||||
|
||||
for idx in 0..counts {
|
||||
let mut c_idx = 0;
|
||||
for c in s.chars() {
|
||||
if !c.is_ascii_alphabetic() {
|
||||
working.push(c);
|
||||
continue;
|
||||
}
|
||||
if c_idx == idx {
|
||||
working.push(c.to_ascii_uppercase());
|
||||
} else {
|
||||
working.push(c.to_ascii_lowercase());
|
||||
}
|
||||
c_idx += 1;
|
||||
}
|
||||
|
||||
variants.push(working.into_iter().collect());
|
||||
working = Vec::new();
|
||||
}
|
||||
variants.push(s.to_ascii_lowercase());
|
||||
variants.push(s.to_ascii_uppercase());
|
||||
|
||||
variants
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_from_case_insensitive() {
|
||||
let test_cases = Key::iter().map(|key| {
|
||||
let strings = key
|
||||
.aliases()
|
||||
.into_iter()
|
||||
.map(|a| case_random(a))
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
(strings, key)
|
||||
});
|
||||
|
||||
for (strings, key) in test_cases {
|
||||
for case in strings {
|
||||
println!("comparing str: [{case}] with key: [{key}]");
|
||||
let parsed = Key::from_str_case_insensitive(&case).expect(&format!(
|
||||
"key [{key}] parse from insensitive string [{case}] failed"
|
||||
));
|
||||
assert_eq!(key, parsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ pub trait UnwrapLog {
|
|||
type Target;
|
||||
|
||||
fn unwrap_or_log(self, default: Self::Target) -> Self::Target;
|
||||
fn unwrap_log(self) -> Self::Target;
|
||||
fn expect_log(self, expect: &str) -> Self::Target;
|
||||
}
|
||||
|
||||
impl<T, E> UnwrapLog for Result<T, E>
|
||||
|
@ -28,6 +30,26 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_log(self) -> Self::Target {
|
||||
match self {
|
||||
Ok(target) => target,
|
||||
Err(err) => {
|
||||
error!("called `Result::unwrap_log()` on an `Err` value: {err}");
|
||||
panic!("called `Result::unwrap_log()` on an `Err` value: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_log(self, expect: &str) -> Self::Target {
|
||||
match self {
|
||||
Ok(target) => target,
|
||||
Err(err) => {
|
||||
error!("[{expect}] called `Result::expect_log()` on an `Err` value: {err}");
|
||||
panic!("[{expect}] called `Result::expect_log()` on an `Err` value: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> UnwrapLog for Option<T>
|
||||
|
@ -49,4 +71,24 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_log(self) -> Self::Target {
|
||||
match self {
|
||||
Some(target) => target,
|
||||
None => {
|
||||
error!("called `Option::unwrap_log()` on a `None` value");
|
||||
panic!("called `Option::unwrap_log()` on a `None` value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_log(self, expect: &str) -> Self::Target {
|
||||
match self {
|
||||
Some(target) => target,
|
||||
None => {
|
||||
error!("[{expect}] called `Option::expect_log()` on an `None` value");
|
||||
panic!("[{expect}] called `Option::expect_log()` on an `None` value");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -1,10 +1,12 @@
|
|||
#![feature(macro_metavar_expr)]
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use add::Add;
|
||||
use clap::{Parser, Subcommand};
|
||||
use config::Config;
|
||||
use log::{error, info};
|
||||
use log::{error, info, LevelFilter};
|
||||
|
||||
mod add;
|
||||
pub mod cmd;
|
||||
mod config;
|
||||
pub mod environ;
|
||||
|
@ -41,10 +43,16 @@ enum HlctlCommand {
|
|||
},
|
||||
/// Start the top panel
|
||||
Panel,
|
||||
/// Add an element to the existing hlctl config
|
||||
#[command(subcommand)]
|
||||
Add(Add),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pretty_env_logger::init();
|
||||
pretty_env_logger::formatted_builder()
|
||||
.filter_module("rustyline", LevelFilter::Error)
|
||||
.parse_default_env()
|
||||
.init();
|
||||
|
||||
let args = Args::parse();
|
||||
match args.command {
|
||||
|
@ -56,6 +64,11 @@ fn main() {
|
|||
error!("panel: {err}");
|
||||
}
|
||||
}
|
||||
HlctlCommand::Add(add) => {
|
||||
if let Err(err) = add.run() {
|
||||
error!("add: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue