Skip to content

Commit

Permalink
new: added command-line option for config file path (#233)
Browse files Browse the repository at this point in the history
  • Loading branch information
pamburus authored May 2, 2024
1 parent 0952658 commit 90dbdf4
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 43 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ members = [".", "crate/encstr"]
[workspace.package]
repository = "https://github.com/pamburus/hl"
authors = ["Pavel Ivanov <[email protected]>"]
version = "0.29.0-alpha.4"
version = "0.29.0-alpha.5"
edition = "2021"
license = "MIT"

Expand Down Expand Up @@ -40,7 +40,7 @@ chrono = { version = "0.4", default-features = false, features = [
"std",
] }
chrono-tz = { version = "0", features = ["serde"] }
clap = { version = "4", features = ["wrap_help", "derive", "env"] }
clap = { version = "4", features = ["wrap_help", "derive", "env", "string"] }
clap_complete = "4"
closure = "0"
collection_macros = "0"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ Arguments:
[FILE]... Files to process

Options:
--config <FILE> Configuration file path [env: HL_CONFIG=] [default: ~/.config/hl/config.yaml]
-s, --sort Sort messages chronologically
-F, --follow Follow input streams and sort messages chronologically during time frame set by --sync-interval-ms option
--tail <N> Number of last messages to preload from each file in --follow mode [default: 10]
Expand Down
84 changes: 79 additions & 5 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use std::path::PathBuf;

// third-party imports
use clap::{value_parser, ArgAction, Parser, ValueEnum};
use clap::{value_parser, ArgAction, Args, Parser, ValueEnum};
use clap_complete::Shell;
use std::num::NonZeroUsize;

Expand All @@ -16,10 +16,76 @@ use crate::{

// ---

#[derive(Args)]
pub struct BootstrapArgs {
/// Configuration file path.
#[arg(long, overrides_with = "config", value_name = "FILE", env = "HL_CONFIG", default_value = default_config_path(), num_args=1)]
pub config: String,
}

/// JSON and logfmt log converter to human readable representation.
#[derive(Parser)]
#[clap(version, disable_help_flag = true)]
pub struct BootstrapOpt {
#[command(flatten)]
pub args: BootstrapArgs,
}

impl BootstrapOpt {
pub fn parse() -> Self {
Self::parse_from(Self::args())
}

pub fn args() -> Vec<String> {
let mut args = std::env::args();
let Some(first) = args.next() else {
return vec![];
};

let mut result = vec![first];
let mut follow_up = false;

while let Some(arg) = args.next() {
match (arg.as_bytes(), follow_up) {
(b"--", _) => {
break;
}
([b'-', b'-', b'c', b'o', b'n', b'f', b'i', b'g', b'=', ..], _) => {
result.push(arg);
follow_up = false;
}
(b"--config", _) => {
result.push(arg);
follow_up = true;
}
([b'-'], true) => {
result.push(arg);
follow_up = false;
}
([b'-', ..], true) => {
follow_up = false;
}
(_, true) => {
result.push(arg);
follow_up = false;
}
_ => {}
}
}

result
}
}

// ---

/// JSON and logfmt log converter to human readable representation.
#[derive(Parser)]
#[clap(version, disable_help_flag = true)]
pub struct Opt {
#[command(flatten)]
pub bootstrap: BootstrapArgs,

/// Sort messages chronologically.
#[arg(long, short = 's', overrides_with = "sort")]
pub sort: bool,
Expand Down Expand Up @@ -125,7 +191,7 @@ pub struct Opt {
/// Color theme.
#[arg(
long,
default_value_t = config::get().theme.clone(),
default_value_t = config::global::get().theme.clone(),
env = "HL_THEME",
overrides_with="theme",
help_heading = heading::OUTPUT
Expand Down Expand Up @@ -160,7 +226,7 @@ pub struct Opt {
env = "HL_FLATTEN",
value_name = "WHEN",
value_enum,
default_value_t = config::get().formatting.flatten.as_ref().map(|x| match x{
default_value_t = config::global::get().formatting.flatten.as_ref().map(|x| match x{
settings::FlattenOption::Never => FlattenOption::Never,
settings::FlattenOption::Always => FlattenOption::Always,
}).unwrap_or(FlattenOption::Always),
Expand All @@ -174,7 +240,7 @@ pub struct Opt {
short,
long,
env="HL_TIME_FORMAT",
default_value_t = config::get().time_format.clone(),
default_value_t = config::global::get().time_format.clone(),
overrides_with = "time_format",
value_name = "FORMAT",
help_heading = heading::OUTPUT
Expand All @@ -186,7 +252,7 @@ pub struct Opt {
long,
short = 'Z',
env="HL_TIME_ZONE",
default_value = config::get().time_zone.name(),
default_value = config::global::get().time_zone.name(),
overrides_with="time_zone",
value_name = "TZ",
help_heading = heading::OUTPUT
Expand Down Expand Up @@ -408,3 +474,11 @@ fn parse_non_zero_size(s: &str) -> std::result::Result<NonZeroUsize, NonZeroSize
Err(NonZeroSizeParseError::ZeroSize)
}
}

fn default_config_path() -> clap::builder::OsStr {
if let Some(dirs) = config::app_dirs() {
dirs.config_dir.join("config.yaml").into_os_string().into()
} else {
"".into()
}
}
46 changes: 33 additions & 13 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,51 @@
// std imports
use std::sync::Mutex;

// third-party imports
use once_cell::sync::Lazy;
use platform_dirs::AppDirs;

// local imports
use crate::settings::Settings;
use crate::{error::Result, settings::Settings};

// ---

pub const APP_NAME: &str = "hl";

static CONFIG: Lazy<Settings> = Lazy::new(load);
static DEFAULT: Lazy<Settings> = Lazy::new(Settings::default);

pub fn get() -> &'static Settings {
&CONFIG
/// Get the default settings.
pub fn default() -> &'static Settings {
Default::default()
}

pub fn default() -> &'static Settings {
&DEFAULT
/// Load settings from the given file or the default configuration file per platform.
pub fn load(path: String) -> Result<Settings> {
if path.is_empty() {
return Ok(Default::default());
}

Settings::load(&path)
}

pub fn app_dirs() -> AppDirs {
AppDirs::new(Some(APP_NAME), true).unwrap()
/// Get the application platform-specific directories.
pub fn app_dirs() -> Option<AppDirs> {
AppDirs::new(Some(APP_NAME), true)
}

// ---
pub mod global {
use super::*;

static PENDING: Mutex<Option<Settings>> = Mutex::new(None);
static RESOLVED: Lazy<Settings> = Lazy::new(|| PENDING.lock().unwrap().take().unwrap_or_default());

/// Call initialize before any calls to get otherwise it will have no effect.
pub fn initialize(settings: Settings) {
*PENDING.lock().unwrap() = Some(settings);
}

fn load() -> Settings {
Settings::load(&app_dirs()).unwrap()
/// Get the resolved settings.
/// If initialized was called before, then a clone of those settings will be returned.
/// Otherwise, the default settings will be returned.
pub fn get() -> &'static Settings {
&RESOLVED
}
}
19 changes: 19 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ pub enum Error {
ParseFloatError(#[from] ParseFloatError),
#[error(transparent)]
ParseIntError(#[from] ParseIntError),
#[error("failed to detect application directories")]
AppDirs,
}

impl Error {
pub fn log(&self) {
eprintln!("{} {}", Color::LightRed.bold().paint("error:"), self);
}
}

/// SizeParseError is an error which may occur when parsing size.
Expand Down Expand Up @@ -134,3 +142,14 @@ pub struct InvalidLevelError {
pub type Result<T> = std::result::Result<T, Error>;

pub const HILITE: Color = Color::Yellow;

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_log() {
let err = Error::Io(std::io::Error::new(std::io::ErrorKind::Other, "test"));
err.log();
}
}
13 changes: 7 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use std::{
use chrono::Utc;
use clap::{CommandFactory, Parser};
use itertools::Itertools;
use nu_ansi_term::Color;

// local imports
use hl::{
Expand All @@ -32,10 +31,10 @@ use hl::{
// ---

fn run() -> Result<()> {
let app_dirs = config::app_dirs();
let settings = config::get();
let opt = cli::Opt::parse();
let settings = config::load(cli::BootstrapOpt::parse().args.config)?;
config::global::initialize(settings.clone());

let opt = cli::Opt::parse();
if opt.help {
return cli::Opt::command().print_help().map_err(Error::Io);
}
Expand Down Expand Up @@ -69,6 +68,8 @@ fn run() -> Result<()> {
cli::ColorOption::Always => true,
cli::ColorOption::Never => false,
};

let app_dirs = config::app_dirs().ok_or(Error::AppDirs)?;
let theme = if use_colors {
let theme = &opt.theme;
Theme::load(&app_dirs, theme)?
Expand Down Expand Up @@ -130,7 +131,7 @@ fn run() -> Result<()> {
let all = || IncludeExcludeKeyFilter::new(KeyMatchOptions::default());
let none = || all().excluded();
let mut fields = all();
for (i, key) in config::get().fields.hide.iter().chain(&opt.hide).enumerate() {
for (i, key) in settings.fields.hide.iter().chain(&opt.hide).enumerate() {
if key == "*" {
fields = none();
} else if key == "!*" {
Expand Down Expand Up @@ -310,7 +311,7 @@ fn run() -> Result<()> {

fn main() {
if let Err(err) = run() {
eprintln!("{}: {}", Color::Red.paint("error"), err);
err.log();
process::exit(1);
}
}
Loading

0 comments on commit 90dbdf4

Please sign in to comment.