mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 12:05:34 +00:00
refactor: simplify configuration and build code + leaner crate (#308)
This commit is contained in:
parent
c7109044f0
commit
1e8c8dbc96
1520
Cargo.lock
generated
1520
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,6 @@ edition = "2021"
|
|||||||
description = "The revolution will be televised."
|
description = "The revolution will be televised."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Alexandre Pasmantier <alex.pasmant@gmail.com>"]
|
authors = ["Alexandre Pasmantier <alex.pasmant@gmail.com>"]
|
||||||
build = "build.rs"
|
|
||||||
repository = "https://github.com/alexpasmantier/television"
|
repository = "https://github.com/alexpasmantier/television"
|
||||||
homepage = "https://github.com/alexpasmantier/television"
|
homepage = "https://github.com/alexpasmantier/television"
|
||||||
keywords = ["search", "fuzzy", "preview", "tui", "terminal"]
|
keywords = ["search", "fuzzy", "preview", "tui", "terminal"]
|
||||||
@ -20,7 +19,6 @@ include = [
|
|||||||
"README.md",
|
"README.md",
|
||||||
"themes/**/*.toml",
|
"themes/**/*.toml",
|
||||||
"television/**",
|
"television/**",
|
||||||
"build.rs",
|
|
||||||
".config/config.toml",
|
".config/config.toml",
|
||||||
"cable",
|
"cable",
|
||||||
]
|
]
|
||||||
@ -51,7 +49,6 @@ clap = { version = "4.5", default-features = false, features = [
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
ratatui = { version = "0.29", features = ["serde", "macros"] }
|
ratatui = { version = "0.29", features = ["serde", "macros"] }
|
||||||
better-panic = "0.3"
|
better-panic = "0.3"
|
||||||
config = "0.14"
|
|
||||||
signal-hook = "0.3"
|
signal-hook = "0.3"
|
||||||
human-panic = "2.0"
|
human-panic = "2.0"
|
||||||
copypasta = "0.10"
|
copypasta = "0.10"
|
||||||
@ -88,9 +85,6 @@ name = "tv"
|
|||||||
name = "results_list_benchmark"
|
name = "results_list_benchmark"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
vergen-gix = { version = "1.0", features = ["build", "cargo", "rustc"] }
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
crossterm = { version = "0.28.1", features = ["serde", "use-dev-tty"] }
|
crossterm = { version = "0.28.1", features = ["serde", "use-dev-tty"] }
|
||||||
|
|
||||||
|
14
build.rs
14
build.rs
@ -1,14 +0,0 @@
|
|||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use vergen_gix::{BuildBuilder, CargoBuilder, Emitter, RustcBuilder};
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
let build = BuildBuilder::default().build_date(true).build()?;
|
|
||||||
let cargo = CargoBuilder::default().target_triple(true).build()?;
|
|
||||||
let rustc = RustcBuilder::default().semver(true).build()?;
|
|
||||||
Ok(Emitter::default()
|
|
||||||
.add_instructions(&build)?
|
|
||||||
.add_instructions(&cargo)?
|
|
||||||
.add_instructions(&rustc)?
|
|
||||||
.emit()?)
|
|
||||||
}
|
|
@ -292,16 +292,7 @@ fn delimiter_parser(s: &str) -> Result<String, String> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION_MESSAGE: &str = concat!(
|
const VERSION_MESSAGE: &str = env!("CARGO_PKG_VERSION");
|
||||||
env!("CARGO_PKG_VERSION"),
|
|
||||||
"\ntarget triple: ",
|
|
||||||
env!("VERGEN_CARGO_TARGET_TRIPLE"),
|
|
||||||
"\nbuild: ",
|
|
||||||
env!("VERGEN_RUSTC_SEMVER"),
|
|
||||||
" (",
|
|
||||||
env!("VERGEN_BUILD_DATE"),
|
|
||||||
")"
|
|
||||||
);
|
|
||||||
|
|
||||||
pub fn version() -> String {
|
pub fn version() -> String {
|
||||||
let author = clap::crate_authors!();
|
let author = clap::crate_authors!();
|
||||||
|
@ -29,10 +29,10 @@ impl Display for Binding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct KeyBindings(pub config::Map<Mode, config::Map<Action, Binding>>);
|
pub struct KeyBindings(pub FxHashMap<Mode, FxHashMap<Action, Binding>>);
|
||||||
|
|
||||||
impl Deref for KeyBindings {
|
impl Deref for KeyBindings {
|
||||||
type Target = config::Map<Mode, config::Map<Action, Binding>>;
|
type Target = FxHashMap<Mode, FxHashMap<Action, Binding>>;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
@ -44,6 +44,31 @@ impl DerefMut for KeyBindings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn merge_keybindings(
|
||||||
|
mut keybindings: KeyBindings,
|
||||||
|
new_keybindings: &KeyBindings,
|
||||||
|
) -> KeyBindings {
|
||||||
|
for (mode, bindings) in new_keybindings.iter() {
|
||||||
|
for (action, binding) in bindings {
|
||||||
|
match keybindings.get_mut(mode) {
|
||||||
|
Some(mode_bindings) => {
|
||||||
|
mode_bindings.insert(action.clone(), binding.clone());
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
keybindings.insert(
|
||||||
|
*mode,
|
||||||
|
[(action.clone(), binding.clone())]
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keybindings
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum SerializedBinding {
|
pub enum SerializedBinding {
|
||||||
@ -61,7 +86,8 @@ impl<'de> Deserialize<'de> for KeyBindings {
|
|||||||
FxHashMap<Action, SerializedBinding>,
|
FxHashMap<Action, SerializedBinding>,
|
||||||
>::deserialize(deserializer)?;
|
>::deserialize(deserializer)?;
|
||||||
|
|
||||||
let keybindings = parsed_map
|
let keybindings: FxHashMap<Mode, FxHashMap<Action, Binding>> =
|
||||||
|
parsed_map
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(mode, inner_map)| {
|
.map(|(mode, inner_map)| {
|
||||||
let converted_inner_map = inner_map
|
let converted_inner_map = inner_map
|
||||||
@ -75,16 +101,16 @@ impl<'de> Deserialize<'de> for KeyBindings {
|
|||||||
parse_key(&key_str).unwrap(),
|
parse_key(&key_str).unwrap(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SerializedBinding::MultipleKeys(keys_str) => {
|
SerializedBinding::MultipleKeys(
|
||||||
Binding::MultipleKeys(
|
keys_str,
|
||||||
|
) => Binding::MultipleKeys(
|
||||||
keys_str
|
keys_str
|
||||||
.iter()
|
.iter()
|
||||||
.map(|key_str| {
|
.map(|key_str| {
|
||||||
parse_key(key_str).unwrap()
|
parse_key(key_str).unwrap()
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
#![allow(clippy::module_name_repetitions)]
|
#![allow(clippy::module_name_repetitions)]
|
||||||
use std::{env, path::PathBuf};
|
use std::{env, path::PathBuf};
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::{eyre::Context, Result};
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
|
use keybindings::merge_keybindings;
|
||||||
pub use keybindings::{parse_key, Binding, KeyBindings};
|
pub use keybindings::{parse_key, Binding, KeyBindings};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use previewers::PreviewersConfig;
|
use previewers::PreviewersConfig;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use shell_integration::ShellIntegrationConfig;
|
use shell_integration::ShellIntegrationConfig;
|
||||||
use styles::Styles;
|
|
||||||
pub use themes::Theme;
|
pub use themes::Theme;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
use ui::UiConfig;
|
use ui::UiConfig;
|
||||||
@ -16,18 +16,18 @@ use ui::UiConfig;
|
|||||||
mod keybindings;
|
mod keybindings;
|
||||||
mod previewers;
|
mod previewers;
|
||||||
mod shell_integration;
|
mod shell_integration;
|
||||||
mod styles;
|
|
||||||
mod themes;
|
mod themes;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
const CONFIG: &str = include_str!("../.config/config.toml");
|
const DEFAULT_CONFIG: &str = include_str!("../../.config/config.toml");
|
||||||
|
|
||||||
#[allow(dead_code, clippy::module_name_repetitions)]
|
#[allow(dead_code, clippy::module_name_repetitions)]
|
||||||
#[derive(Clone, Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
#[serde(default)]
|
#[serde(default = "get_data_dir")]
|
||||||
pub data_dir: PathBuf,
|
pub data_dir: PathBuf,
|
||||||
#[serde(default)]
|
#[serde(default = "get_config_dir")]
|
||||||
pub config_dir: PathBuf,
|
pub config_dir: PathBuf,
|
||||||
#[serde(default = "default_frame_rate")]
|
#[serde(default = "default_frame_rate")]
|
||||||
pub frame_rate: f64,
|
pub frame_rate: f64,
|
||||||
@ -36,19 +36,23 @@ pub struct AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Clone, Debug, Default, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
/// General application configuration
|
||||||
#[allow(clippy::struct_field_names)]
|
#[allow(clippy::struct_field_names)]
|
||||||
#[serde(default, flatten)]
|
#[serde(default, flatten)]
|
||||||
pub config: AppConfig,
|
pub config: AppConfig,
|
||||||
|
/// Keybindings configuration
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub keybindings: KeyBindings,
|
pub keybindings: KeyBindings,
|
||||||
#[serde(default)]
|
/// UI configuration
|
||||||
pub styles: Styles,
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ui: UiConfig,
|
pub ui: UiConfig,
|
||||||
|
/// Previewers configuration
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub previewers: PreviewersConfig,
|
pub previewers: PreviewersConfig,
|
||||||
|
/// Shell integration configuration
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub shell_integration: ShellIntegrationConfig,
|
pub shell_integration: ShellIntegrationConfig,
|
||||||
}
|
}
|
||||||
@ -77,68 +81,40 @@ impl Config {
|
|||||||
#[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]
|
#[allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
// Load the default_config values as base defaults
|
// Load the default_config values as base defaults
|
||||||
let default_config: Config =
|
let default_config: Config = toml::from_str(DEFAULT_CONFIG)
|
||||||
toml::from_str(CONFIG).expect("default config should be valid");
|
.wrap_err("Error parsing default config")?;
|
||||||
|
|
||||||
// initialize the config builder
|
// initialize the config builder
|
||||||
let data_dir = get_data_dir();
|
let data_dir = get_data_dir();
|
||||||
let config_dir = get_config_dir();
|
let config_dir = get_config_dir();
|
||||||
|
|
||||||
std::fs::create_dir_all(&config_dir)
|
std::fs::create_dir_all(&config_dir)
|
||||||
.expect("Failed creating configuration directory");
|
.expect("Failed creating configuration directory");
|
||||||
std::fs::create_dir_all(&data_dir)
|
std::fs::create_dir_all(&data_dir)
|
||||||
.expect("Failed creating data directory");
|
.expect("Failed creating data directory");
|
||||||
|
|
||||||
let mut builder = config::Config::builder()
|
|
||||||
.set_default("data_dir", data_dir.to_str().unwrap())?
|
|
||||||
.set_default("config_dir", config_dir.to_str().unwrap())?
|
|
||||||
.set_default("frame_rate", default_config.config.frame_rate)?
|
|
||||||
.set_default("tick_rate", default_config.config.tick_rate)?
|
|
||||||
.set_default("ui", default_config.ui.clone())?
|
|
||||||
.set_default("previewers", default_config.previewers.clone())?
|
|
||||||
.set_default("theme", default_config.ui.theme.clone())?
|
|
||||||
.set_default(
|
|
||||||
"shell_integration",
|
|
||||||
default_config.shell_integration.clone(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Load the user's config file
|
|
||||||
let source = config::File::from(config_dir.join(CONFIG_FILE_NAME))
|
|
||||||
.format(config::FileFormat::Toml)
|
|
||||||
.required(false);
|
|
||||||
builder = builder.add_source(source);
|
|
||||||
|
|
||||||
if config_dir.join(CONFIG_FILE_NAME).is_file() {
|
if config_dir.join(CONFIG_FILE_NAME).is_file() {
|
||||||
debug!("Found config file at {:?}", config_dir);
|
debug!("Found config file at {:?}", config_dir);
|
||||||
let mut cfg: Self = builder.build()?.try_deserialize().unwrap();
|
|
||||||
//.with_context(|| {
|
|
||||||
// format!(
|
|
||||||
// "Error parsing config file at {:?}",
|
|
||||||
// config_dir.join(CONFIG_FILE_NAME)
|
|
||||||
// )
|
|
||||||
//})?;
|
|
||||||
|
|
||||||
for (mode, default_bindings) in default_config.keybindings.iter() {
|
let path = config_dir.join(CONFIG_FILE_NAME);
|
||||||
let user_bindings = cfg.keybindings.entry(*mode).or_default();
|
let contents = std::fs::read_to_string(&path)?;
|
||||||
for (command, key) in default_bindings {
|
|
||||||
user_bindings
|
|
||||||
.entry(command.clone())
|
|
||||||
.or_insert_with(|| key.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (mode, default_styles) in default_config.styles.iter() {
|
let cfg: Config = toml::from_str(&contents)
|
||||||
let user_styles = cfg.styles.entry(*mode).or_default();
|
.wrap_err(format!("error parsing config: {path:?}"))?;
|
||||||
for (style_key, style) in default_styles {
|
|
||||||
user_styles.entry(style_key.clone()).or_insert(*style);
|
// merge keybindings with default keybindings
|
||||||
}
|
let keybindings = merge_keybindings(
|
||||||
}
|
default_config.keybindings,
|
||||||
|
&cfg.keybindings,
|
||||||
|
);
|
||||||
|
let cfg = Config { keybindings, ..cfg };
|
||||||
|
|
||||||
debug!("Config: {:?}", cfg);
|
debug!("Config: {:?}", cfg);
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
} else {
|
} else {
|
||||||
warn!("No config file found at {:?}, creating default configuration file at that location.", config_dir);
|
warn!("No config file found at {:?}, creating default configuration file at that location.", config_dir);
|
||||||
// create the default configuration file in the user's config directory
|
// create the default configuration file in the user's config directory
|
||||||
std::fs::write(config_dir.join(CONFIG_FILE_NAME), CONFIG)?;
|
std::fs::write(config_dir.join(CONFIG_FILE_NAME), DEFAULT_CONFIG)?;
|
||||||
Ok(default_config)
|
Ok(default_config)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::preview::{previewers, PreviewerConfig};
|
use crate::preview::{previewers, PreviewerConfig};
|
||||||
use config::ValueKind;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
@ -20,26 +17,11 @@ impl From<PreviewersConfig> for PreviewerConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PreviewersConfig> for ValueKind {
|
|
||||||
fn from(val: PreviewersConfig) -> Self {
|
|
||||||
let mut m = HashMap::new();
|
|
||||||
m.insert(String::from("basic"), val.basic.into());
|
|
||||||
m.insert(String::from("file"), val.file.into());
|
|
||||||
m.insert(String::from("env_var"), val.env_var.into());
|
|
||||||
ValueKind::Table(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
pub struct BasicPreviewerConfig {}
|
pub struct BasicPreviewerConfig {}
|
||||||
|
|
||||||
impl From<BasicPreviewerConfig> for ValueKind {
|
|
||||||
fn from(_val: BasicPreviewerConfig) -> Self {
|
|
||||||
ValueKind::Table(HashMap::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct FilePreviewerConfig {
|
pub struct FilePreviewerConfig {
|
||||||
//pub max_file_size: u64,
|
//pub max_file_size: u64,
|
||||||
pub theme: String,
|
pub theme: String,
|
||||||
@ -54,19 +36,5 @@ impl Default for FilePreviewerConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FilePreviewerConfig> for ValueKind {
|
|
||||||
fn from(val: FilePreviewerConfig) -> Self {
|
|
||||||
let mut m = HashMap::new();
|
|
||||||
m.insert(String::from("theme"), ValueKind::String(val.theme).into());
|
|
||||||
ValueKind::Table(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
pub struct EnvVarPreviewerConfig {}
|
pub struct EnvVarPreviewerConfig {}
|
||||||
|
|
||||||
impl From<EnvVarPreviewerConfig> for ValueKind {
|
|
||||||
fn from(_val: EnvVarPreviewerConfig) -> Self {
|
|
||||||
ValueKind::Table(HashMap::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
@ -7,20 +5,3 @@ use serde::Deserialize;
|
|||||||
pub struct ShellIntegrationConfig {
|
pub struct ShellIntegrationConfig {
|
||||||
pub commands: FxHashMap<String, String>,
|
pub commands: FxHashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ShellIntegrationConfig> for config::ValueKind {
|
|
||||||
fn from(val: ShellIntegrationConfig) -> Self {
|
|
||||||
let mut m = HashMap::new();
|
|
||||||
m.insert(
|
|
||||||
String::from("commands"),
|
|
||||||
config::ValueKind::Table(
|
|
||||||
val.commands
|
|
||||||
.into_iter()
|
|
||||||
.map(|(k, v)| (k, config::ValueKind::String(v).into()))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
config::ValueKind::Table(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,207 +0,0 @@
|
|||||||
use crate::screen::mode::Mode;
|
|
||||||
use ratatui::prelude::{Color, Modifier, Style};
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use serde::{Deserialize, Deserializer};
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct Styles(pub FxHashMap<Mode, FxHashMap<String, Style>>);
|
|
||||||
|
|
||||||
impl Deref for Styles {
|
|
||||||
type Target = FxHashMap<Mode, FxHashMap<String, Style>>;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Styles {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Styles {
|
|
||||||
fn deserialize<D>(deserializer: D) -> color_eyre::Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let parsed_map =
|
|
||||||
FxHashMap::<Mode, FxHashMap<String, String>>::deserialize(
|
|
||||||
deserializer,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let styles = parsed_map
|
|
||||||
.into_iter()
|
|
||||||
.map(|(mode, inner_map)| {
|
|
||||||
let converted_inner_map = inner_map
|
|
||||||
.into_iter()
|
|
||||||
.map(|(str, style)| (str, parse_style(&style)))
|
|
||||||
.collect();
|
|
||||||
(mode, converted_inner_map)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(Styles(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_style(line: &str) -> Style {
|
|
||||||
let (foreground, background) =
|
|
||||||
line.split_at(line.to_lowercase().find("on ").unwrap_or(line.len()));
|
|
||||||
let foreground = process_color_string(foreground);
|
|
||||||
let background = process_color_string(&background.replace("on ", ""));
|
|
||||||
|
|
||||||
let mut style = Style::default();
|
|
||||||
if let Some(fg) = parse_color(&foreground.0) {
|
|
||||||
style = style.fg(fg);
|
|
||||||
}
|
|
||||||
if let Some(bg) = parse_color(&background.0) {
|
|
||||||
style = style.bg(bg);
|
|
||||||
}
|
|
||||||
style = style.add_modifier(foreground.1 | background.1);
|
|
||||||
style
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_color_string(color_str: &str) -> (String, Modifier) {
|
|
||||||
let color = color_str
|
|
||||||
.replace("grey", "gray")
|
|
||||||
.replace("bright ", "")
|
|
||||||
.replace("bold ", "")
|
|
||||||
.replace("underline ", "")
|
|
||||||
.replace("inverse ", "");
|
|
||||||
|
|
||||||
let mut modifiers = Modifier::empty();
|
|
||||||
if color_str.contains("underline") {
|
|
||||||
modifiers |= Modifier::UNDERLINED;
|
|
||||||
}
|
|
||||||
if color_str.contains("bold") {
|
|
||||||
modifiers |= Modifier::BOLD;
|
|
||||||
}
|
|
||||||
if color_str.contains("inverse") {
|
|
||||||
modifiers |= Modifier::REVERSED;
|
|
||||||
}
|
|
||||||
|
|
||||||
(color, modifiers)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
pub fn parse_color(s: &str) -> Option<Color> {
|
|
||||||
let s = s.trim_start();
|
|
||||||
let s = s.trim_end();
|
|
||||||
if s.contains("bright color") {
|
|
||||||
let s = s.trim_start_matches("bright ");
|
|
||||||
let c = s
|
|
||||||
.trim_start_matches("color")
|
|
||||||
.parse::<u8>()
|
|
||||||
.unwrap_or_default();
|
|
||||||
Some(Color::Indexed(c.wrapping_shl(8)))
|
|
||||||
} else if s.contains("color") {
|
|
||||||
let c = s
|
|
||||||
.trim_start_matches("color")
|
|
||||||
.parse::<u8>()
|
|
||||||
.unwrap_or_default();
|
|
||||||
Some(Color::Indexed(c))
|
|
||||||
} else if s.contains("gray") {
|
|
||||||
let c = 232
|
|
||||||
+ s.trim_start_matches("gray")
|
|
||||||
.parse::<u8>()
|
|
||||||
.unwrap_or_default();
|
|
||||||
Some(Color::Indexed(c))
|
|
||||||
} else if s.contains("rgb") {
|
|
||||||
let red =
|
|
||||||
(s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8;
|
|
||||||
let green =
|
|
||||||
(s.as_bytes()[4] as char).to_digit(10).unwrap_or_default() as u8;
|
|
||||||
let blue =
|
|
||||||
(s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8;
|
|
||||||
let c = 16 + red * 36 + green * 6 + blue;
|
|
||||||
Some(Color::Indexed(c))
|
|
||||||
} else if s == "bold black" {
|
|
||||||
Some(Color::Indexed(8))
|
|
||||||
} else if s == "bold red" {
|
|
||||||
Some(Color::Indexed(9))
|
|
||||||
} else if s == "bold green" {
|
|
||||||
Some(Color::Indexed(10))
|
|
||||||
} else if s == "bold yellow" {
|
|
||||||
Some(Color::Indexed(11))
|
|
||||||
} else if s == "bold blue" {
|
|
||||||
Some(Color::Indexed(12))
|
|
||||||
} else if s == "bold magenta" {
|
|
||||||
Some(Color::Indexed(13))
|
|
||||||
} else if s == "bold cyan" {
|
|
||||||
Some(Color::Indexed(14))
|
|
||||||
} else if s == "bold white" {
|
|
||||||
Some(Color::Indexed(15))
|
|
||||||
} else if s == "black" {
|
|
||||||
Some(Color::Indexed(0))
|
|
||||||
} else if s == "red" {
|
|
||||||
Some(Color::Indexed(1))
|
|
||||||
} else if s == "green" {
|
|
||||||
Some(Color::Indexed(2))
|
|
||||||
} else if s == "yellow" {
|
|
||||||
Some(Color::Indexed(3))
|
|
||||||
} else if s == "blue" {
|
|
||||||
Some(Color::Indexed(4))
|
|
||||||
} else if s == "magenta" {
|
|
||||||
Some(Color::Indexed(5))
|
|
||||||
} else if s == "cyan" {
|
|
||||||
Some(Color::Indexed(6))
|
|
||||||
} else if s == "white" {
|
|
||||||
Some(Color::Indexed(7))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_style_default() {
|
|
||||||
let style = parse_style("");
|
|
||||||
assert_eq!(style, Style::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_style_foreground() {
|
|
||||||
let style = parse_style("red");
|
|
||||||
assert_eq!(style.fg, Some(Color::Indexed(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_style_background() {
|
|
||||||
let style = parse_style("on blue");
|
|
||||||
assert_eq!(style.bg, Some(Color::Indexed(4)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_style_modifiers() {
|
|
||||||
let style = parse_style("underline red on blue");
|
|
||||||
assert_eq!(style.fg, Some(Color::Indexed(1)));
|
|
||||||
assert_eq!(style.bg, Some(Color::Indexed(4)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_process_color_string() {
|
|
||||||
let (color, modifiers) =
|
|
||||||
process_color_string("underline bold inverse gray");
|
|
||||||
assert_eq!(color, "gray");
|
|
||||||
assert!(modifiers.contains(Modifier::UNDERLINED));
|
|
||||||
assert!(modifiers.contains(Modifier::BOLD));
|
|
||||||
assert!(modifiers.contains(Modifier::REVERSED));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_color_rgb() {
|
|
||||||
let color = parse_color("rgb123");
|
|
||||||
let expected = 16 + 36 + 2 * 6 + 3;
|
|
||||||
assert_eq!(color, Some(Color::Indexed(expected)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_color_unknown() {
|
|
||||||
let color = parse_color("unknown");
|
|
||||||
assert_eq!(color, None);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use config::ValueKind;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::screen::layout::{InputPosition, PreviewTitlePosition};
|
use crate::screen::layout::{InputPosition, PreviewTitlePosition};
|
||||||
@ -10,6 +7,7 @@ use super::themes::DEFAULT_THEME;
|
|||||||
const DEFAULT_UI_SCALE: u16 = 100;
|
const DEFAULT_UI_SCALE: u16 = 100;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct UiConfig {
|
pub struct UiConfig {
|
||||||
pub use_nerd_font_icons: bool,
|
pub use_nerd_font_icons: bool,
|
||||||
pub ui_scale: u16,
|
pub ui_scale: u16,
|
||||||
@ -34,39 +32,3 @@ impl Default for UiConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<UiConfig> for ValueKind {
|
|
||||||
fn from(val: UiConfig) -> Self {
|
|
||||||
let mut m = HashMap::new();
|
|
||||||
m.insert(
|
|
||||||
String::from("use_nerd_font_icons"),
|
|
||||||
ValueKind::Boolean(val.use_nerd_font_icons).into(),
|
|
||||||
);
|
|
||||||
m.insert(
|
|
||||||
String::from("ui_scale"),
|
|
||||||
ValueKind::U64(val.ui_scale.into()).into(),
|
|
||||||
);
|
|
||||||
m.insert(
|
|
||||||
String::from("show_help_bar"),
|
|
||||||
ValueKind::Boolean(val.show_help_bar).into(),
|
|
||||||
);
|
|
||||||
m.insert(
|
|
||||||
String::from("show_preview_panel"),
|
|
||||||
ValueKind::Boolean(val.show_preview_panel).into(),
|
|
||||||
);
|
|
||||||
m.insert(
|
|
||||||
String::from("input_position"),
|
|
||||||
ValueKind::String(val.input_bar_position.to_string()).into(),
|
|
||||||
);
|
|
||||||
m.insert(
|
|
||||||
String::from("preview_title_position"),
|
|
||||||
match val.preview_title_position {
|
|
||||||
Some(pos) => ValueKind::String(pos.to_string()),
|
|
||||||
None => ValueKind::Nil,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
m.insert(String::from("theme"), ValueKind::String(val.theme).into());
|
|
||||||
ValueKind::Table(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||||
|
|
||||||
use crate::config;
|
use crate::config::get_data_dir;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
|
pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() -> Result<()> {
|
pub fn init() -> Result<()> {
|
||||||
let directory = config::get_data_dir();
|
let directory = get_data_dir();
|
||||||
std::fs::create_dir_all(directory.clone())?;
|
std::fs::create_dir_all(directory.clone())?;
|
||||||
let log_path = directory.join(LOG_FILE.clone());
|
let log_path = directory.join(LOG_FILE.clone());
|
||||||
let log_file = std::fs::File::create(log_path)?;
|
let log_file = std::fs::File::create(log_path)?;
|
||||||
|
@ -40,40 +40,6 @@ pub fn build_metadata_table<'a>(
|
|||||||
)),
|
)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let target_triple_row = Row::new(vec![
|
|
||||||
Cell::from(Span::styled(
|
|
||||||
"target triple: ",
|
|
||||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
|
||||||
)),
|
|
||||||
Cell::from(Span::styled(
|
|
||||||
&app_metadata.build.target_triple,
|
|
||||||
Style::default().fg(colorscheme.help.metadata_field_value_fg),
|
|
||||||
)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let build_row = Row::new(vec![
|
|
||||||
Cell::from(Span::styled(
|
|
||||||
"build: ",
|
|
||||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
|
||||||
)),
|
|
||||||
Cell::from(Span::styled(
|
|
||||||
&app_metadata.build.rustc_version,
|
|
||||||
Style::default().fg(colorscheme.help.metadata_field_value_fg),
|
|
||||||
)),
|
|
||||||
Cell::from(Span::styled(
|
|
||||||
" (",
|
|
||||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
|
||||||
)),
|
|
||||||
Cell::from(Span::styled(
|
|
||||||
&app_metadata.build.build_date,
|
|
||||||
Style::default().fg(colorscheme.help.metadata_field_value_fg),
|
|
||||||
)),
|
|
||||||
Cell::from(Span::styled(
|
|
||||||
")",
|
|
||||||
Style::default().fg(colorscheme.help.metadata_field_name_fg),
|
|
||||||
)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let current_dir_row = Row::new(vec![
|
let current_dir_row = Row::new(vec![
|
||||||
Cell::from(Span::styled(
|
Cell::from(Span::styled(
|
||||||
"current directory: ",
|
"current directory: ",
|
||||||
@ -115,8 +81,6 @@ pub fn build_metadata_table<'a>(
|
|||||||
Table::new(
|
Table::new(
|
||||||
vec![
|
vec![
|
||||||
version_row,
|
version_row,
|
||||||
target_triple_row,
|
|
||||||
build_row,
|
|
||||||
current_dir_row,
|
current_dir_row,
|
||||||
current_channel_row,
|
current_channel_row,
|
||||||
current_mode_row,
|
current_mode_row,
|
||||||
|
@ -21,7 +21,7 @@ use crate::screen::preview::draw_preview_content_block;
|
|||||||
use crate::screen::remote_control::draw_remote_control;
|
use crate::screen::remote_control::draw_remote_control;
|
||||||
use crate::screen::results::draw_results_list;
|
use crate::screen::results::draw_results_list;
|
||||||
use crate::screen::spinner::{Spinner, SpinnerState};
|
use crate::screen::spinner::{Spinner, SpinnerState};
|
||||||
use crate::utils::metadata::{AppMetadata, BuildMetadata};
|
use crate::utils::metadata::AppMetadata;
|
||||||
use crate::utils::strings::EMPTY_STRING;
|
use crate::utils::strings::EMPTY_STRING;
|
||||||
use crate::{cable::load_cable_channels, keymap::Keymap};
|
use crate::{cable::load_cable_channels, keymap::Keymap};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
@ -75,11 +75,6 @@ impl Television {
|
|||||||
|
|
||||||
let app_metadata = AppMetadata::new(
|
let app_metadata = AppMetadata::new(
|
||||||
env!("CARGO_PKG_VERSION").to_string(),
|
env!("CARGO_PKG_VERSION").to_string(),
|
||||||
BuildMetadata::new(
|
|
||||||
env!("VERGEN_RUSTC_SEMVER").to_string(),
|
|
||||||
env!("VERGEN_BUILD_DATE").to_string(),
|
|
||||||
env!("VERGEN_CARGO_TARGET_TRIPLE").to_string(),
|
|
||||||
),
|
|
||||||
std::env::current_dir()
|
std::env::current_dir()
|
||||||
.expect("Could not get current directory")
|
.expect("Could not get current directory")
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
|
@ -1,38 +1,12 @@
|
|||||||
pub struct BuildMetadata {
|
|
||||||
pub rustc_version: String,
|
|
||||||
pub build_date: String,
|
|
||||||
pub target_triple: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BuildMetadata {
|
|
||||||
pub fn new(
|
|
||||||
rustc_version: String,
|
|
||||||
build_date: String,
|
|
||||||
target_triple: String,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
rustc_version,
|
|
||||||
build_date,
|
|
||||||
target_triple,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AppMetadata {
|
pub struct AppMetadata {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub build: BuildMetadata,
|
|
||||||
pub current_directory: String,
|
pub current_directory: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppMetadata {
|
impl AppMetadata {
|
||||||
pub fn new(
|
pub fn new(version: String, current_directory: String) -> Self {
|
||||||
version: String,
|
|
||||||
build: BuildMetadata,
|
|
||||||
current_directory: String,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
version,
|
version,
|
||||||
build,
|
|
||||||
current_directory,
|
current_directory,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user