use crate::{
config,
run,
run::{
Display,
TableDisplay,
},
store::Filter,
};
use clap::{
AppSettings::{
ColoredHelp,
GlobalVersion,
NextLineHelp,
},
Parser,
Subcommand,
};
use directories::{
BaseDirs,
ProjectDirs,
};
use log::error;
use regex::Regex;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("can not get base directories")]
BaseDirectory,
#[error("can not get project dirs")]
ProjectDirs,
}
fn project_dir() -> ProjectDirs {
ProjectDirs::from("com", "hstdb", "hstdb")
.ok_or(Error::ProjectDirs)
.expect("can not get project_dir")
}
fn base_directory() -> BaseDirs {
directories::BaseDirs::new()
.ok_or(Error::BaseDirectory)
.expect("can not get base directory")
}
fn default_data_dir() -> PathBuf {
let project_dir = project_dir();
let data_dir = project_dir.data_dir();
data_dir.to_owned()
}
fn default_cache_path() -> PathBuf {
let project_dir = project_dir();
let cache_path = project_dir.cache_dir().join("server");
cache_path
}
fn default_histdb_sqlite_path() -> PathBuf {
let base_dirs = base_directory();
let home = base_dirs.home_dir();
home.join(".histdb").join("zsh-history.db")
}
fn default_zsh_histfile_path() -> PathBuf {
let base_dirs = base_directory();
let home = base_dirs.home_dir();
home.join(".histfile")
}
fn default_socket_path() -> PathBuf {
let project_dir = project_dir();
let fallback_path = PathBuf::from("/tmp/hstdb/");
let socket_path = project_dir
.runtime_dir()
.unwrap_or(&fallback_path)
.join("server_socket");
socket_path
}
fn default_config_path() -> PathBuf {
let project_dir = project_dir();
let socket_path = project_dir.config_dir().join("config.toml");
socket_path
}
#[derive(Parser, Debug)]
struct ZSHAddHistory {
#[clap(flatten)]
socket_path: Socket,
#[clap(index = 1)]
command: String,
}
#[derive(Parser, Debug)]
struct Server {
#[clap(short, long, default_value_os_t = default_cache_path())]
cache_path: PathBuf,
#[clap(flatten)]
data_dir: DataDir,
#[clap(flatten)]
socket_path: Socket,
}
#[derive(Subcommand, Debug)]
enum Import {
#[cfg(feature = "histdb-import")]
Histdb(ImportHistdb),
Histfile(ImportHistfile),
}
#[derive(Parser, Debug)]
struct ImportHistdb {
#[clap(flatten)]
data_dir: DataDir,
#[clap(short, long, default_value_os_t = default_histdb_sqlite_path())]
import_file: PathBuf,
}
#[derive(Parser, Debug)]
struct ImportHistfile {
#[clap(flatten)]
data_dir: DataDir,
#[clap(short, long, default_value_os_t = default_zsh_histfile_path())]
import_file: PathBuf,
}
#[derive(Parser, Debug)]
struct Socket {
#[clap(short, long, env = "HISTDBRS_SOCKET_PATH", default_value_os_t = default_socket_path())]
socket_path: PathBuf,
}
#[derive(Parser, Debug)]
struct Config {
#[clap(long, env = "HISTDBRS_CONFIG_PATH", default_value_os_t = default_config_path())]
config_path: PathBuf,
}
#[derive(Parser, Debug)]
struct DataDir {
#[clap(
short,
long,
default_value_os_t = default_data_dir()
)]
data_dir: PathBuf,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Parser, Debug)]
struct DefaultArgs {
#[clap(flatten)]
data_dir: DataDir,
#[clap(short, long, default_value = "25")]
entries_count: usize,
#[clap(short, long)]
command: Option<String>,
#[clap(short = 't', long = "text")]
command_text: Option<Regex>,
#[clap(short = 'T', long = "text_excluded")]
command_text_excluded: Option<Regex>,
#[clap(short, long = "in", conflicts_with = "folder")]
in_current: bool,
#[clap(short, long)]
folder: Option<PathBuf>,
#[clap(long)]
no_subdirs: bool,
#[clap(long, conflicts_with = "all-hosts")]
hostname: Option<String>,
#[clap(long)]
session: Option<Regex>,
#[clap(long)]
all_hosts: bool,
#[clap(long)]
disable_formatting: bool,
#[clap(long)]
show_host: bool,
#[clap(long)]
show_status: bool,
#[clap(long)]
show_duration: bool,
#[clap(long)]
show_pwd: bool,
#[clap(long)]
show_session: bool,
#[clap(long)]
hide_header: bool,
#[clap(long)]
filter_failed: bool,
#[clap(long)]
find_status: Option<u16>,
#[clap(flatten)]
config: Config,
}
#[derive(Subcommand, Debug)]
enum SubCommand {
#[clap(name = "zshaddhistory")]
ZSHAddHistory(ZSHAddHistory),
#[clap(name = "server")]
Server(Server),
#[clap(name = "stop")]
Stop(Socket),
#[clap(name = "disable")]
Disable(Socket),
#[clap(name = "enable")]
Enable(Socket),
#[clap(name = "precmd")]
PreCmd(Socket),
#[clap(name = "session_id")]
SessionID,
#[clap(subcommand, name = "import")]
Import(Import),
#[clap(name = "init")]
Init,
#[clap(name = "bench")]
Bench(Socket),
}
#[derive(Parser, Debug)]
#[clap(
author, version, about, global_settings = &[ColoredHelp, NextLineHelp, GlobalVersion]
)]
pub struct Opt {
#[clap(flatten)]
default_args: DefaultArgs,
#[clap(subcommand)]
sub_command: Option<SubCommand>,
}
impl Opt {
pub fn run(self) -> Result<(), run::Error> {
let sub_command = self.sub_command;
let in_current = self.default_args.in_current;
let folder = self.default_args.folder;
let all_hosts = self.default_args.all_hosts;
let hostname = self.default_args.hostname;
let data_dir = self.default_args.data_dir.data_dir;
let entries_count = self.default_args.entries_count;
let command = self.default_args.command;
let session_filter = self.default_args.session;
let no_subdirs = self.default_args.no_subdirs;
let command_text = self.default_args.command_text;
let command_text_excluded = self.default_args.command_text_excluded;
let filter_failed = self.default_args.filter_failed;
let find_status = self.default_args.find_status;
let config = config::Config::open(self.default_args.config.config_path)
.map_err(run::Error::ReadConfig)?;
let format = !self.default_args.disable_formatting;
let duration = Display::should_show(self.default_args.show_duration);
let header = Display::should_hide(self.default_args.hide_header);
let host = Display::should_show(self.default_args.show_host);
let pwd = Display::should_show(self.default_args.show_pwd);
let session = Display::should_show(self.default_args.show_session);
let status = Display::should_show(self.default_args.show_status);
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", config.log_level.as_str());
}
pretty_env_logger::init();
sub_command.map_or_else(
|| {
let filter = Filter::default()
.directory(folder, in_current, no_subdirs)?
.hostname(hostname, all_hosts)?
.count(entries_count)
.command(command, command_text, command_text_excluded)
.session(session_filter)
.filter_failed(filter_failed)
.find_status(find_status);
let display = TableDisplay {
format,
duration,
header,
host,
pwd,
session,
status,
};
run::default(&filter, &display, data_dir)
},
|sub_command| match sub_command {
SubCommand::ZSHAddHistory(o) => {
run::zsh_add_history(&config, o.command, o.socket_path.socket_path)
}
SubCommand::Server(o) => {
run::server(o.cache_path, o.socket_path.socket_path, o.data_dir.data_dir)
}
SubCommand::Stop(o) => run::stop(o.socket_path),
SubCommand::Disable(o) => run::disable(o.socket_path),
SubCommand::Enable(o) => run::enable(o.socket_path),
SubCommand::PreCmd(o) => run::precmd(o.socket_path),
SubCommand::SessionID => {
run::session_id();
Ok(())
}
SubCommand::Import(s) => match s {
#[cfg(feature = "histdb-import")]
Import::Histdb(o) => run::import::histdb(&o.import_file, o.data_dir.data_dir)
.map_err(run::Error::Import),
Import::Histfile(o) => {
run::import::histfile(&o.import_file, o.data_dir.data_dir)
.map_err(run::Error::Import)
}
},
SubCommand::Init => {
run::init();
Ok(())
}
SubCommand::Bench(s) => run::bench(s.socket_path),
},
)
}
}