mirror of
https://github.com/lelgenio/dhist.git
synced 2024-11-21 12:52:17 -03:00
init
This commit is contained in:
commit
3ecf8c981d
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
target
|
216
Cargo.lock
generated
Normal file
216
Cargo.lock
generated
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5f1fea81f183005ced9e59cdb01737ef2423956dac5a6d731b06b2ecfaa3467"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"bitflags",
|
||||||
|
"indexmap",
|
||||||
|
"lazy_static",
|
||||||
|
"os_str_bytes",
|
||||||
|
"strsim",
|
||||||
|
"termcolor",
|
||||||
|
"textwrap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dhist"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"dirs",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"redox_users",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.118"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"redox_syscall",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "dhist"
|
||||||
|
description = "Save and sort most often used dmenu-like input"
|
||||||
|
authors = ["Leonardo Eugênio <lelgeio@disroot.org>"]
|
||||||
|
license = "GPL-3.0"
|
||||||
|
version = "0.1.1"
|
||||||
|
edition = "2021"
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dirs = "4.0.0"
|
||||||
|
|
||||||
|
[dependencies.clap]
|
||||||
|
version = "3.0.0-beta.4"
|
||||||
|
features = ["color", "cargo"]
|
29
README.md
Normal file
29
README.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# dhist
|
||||||
|
|
||||||
|
Save and sort most often used dmenu-like input
|
||||||
|
|
||||||
|
```
|
||||||
|
USAGE:
|
||||||
|
dhist [OPTIONS] <SUBCOMMAND>
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-h, --help Print help information
|
||||||
|
|
||||||
|
SUBCOMMANDS:
|
||||||
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
increment Increase usage of input by 1
|
||||||
|
query Print history
|
||||||
|
sort Sort input by history frequency
|
||||||
|
wrap Wrap a command to sort before and increment after
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
# sort input of dmenu based on usage
|
||||||
|
printf "%s\n" hello world | dhist wrap -- dmenu
|
||||||
|
|
||||||
|
# same as above, but more verbose
|
||||||
|
# dhist increment also prints out it's input, so you can still use it for another program
|
||||||
|
printf "%s\n" hello world | dhist sort | dmenu | dhist increment
|
||||||
|
```
|
3
rust-toolchain.toml
Normal file
3
rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "stable-2022-01-20"
|
||||||
|
targets = [ "x86_64-unknown-linux-gnu", ]
|
65
src/cli.rs
Normal file
65
src/cli.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use clap::{
|
||||||
|
Command,
|
||||||
|
Arg,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn get_cli() -> clap::Command<'static> {
|
||||||
|
Command::new("dhist")
|
||||||
|
.about(clap::crate_description!())
|
||||||
|
.author(clap::crate_authors!())
|
||||||
|
.arg_required_else_help(true)
|
||||||
|
// .arg(
|
||||||
|
// Arg::new("histfile-path")
|
||||||
|
// .short('H')
|
||||||
|
// .long("history")
|
||||||
|
// .about("Path to history file"),
|
||||||
|
// )
|
||||||
|
.subcommand(Command::new(Mode::Query).about("Print history"))
|
||||||
|
.subcommand(
|
||||||
|
Command::new(Mode::Sort).about("Sort input by history frequency"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
Command::new(Mode::Increment).about("Increase usage of input by 1"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
Command::new(Mode::Wrap)
|
||||||
|
.about("Wrap a command to sort before and increment after")
|
||||||
|
.arg_required_else_help(true)
|
||||||
|
.arg(
|
||||||
|
Arg::new("command")
|
||||||
|
.required(true)
|
||||||
|
.multiple_values(true)
|
||||||
|
.last(true),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Mode {
|
||||||
|
Query,
|
||||||
|
Sort,
|
||||||
|
Increment,
|
||||||
|
Wrap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Mode> for String {
|
||||||
|
fn from(val: Mode) -> Self {
|
||||||
|
match val {
|
||||||
|
Mode::Query => "query".to_string(),
|
||||||
|
Mode::Sort => "sort".to_string(),
|
||||||
|
Mode::Increment => "increment".to_string(),
|
||||||
|
Mode::Wrap => "wrap".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Mode {
|
||||||
|
fn from(val: &str) -> Self {
|
||||||
|
match val {
|
||||||
|
"query" => Mode::Query,
|
||||||
|
"sort" => Mode::Sort,
|
||||||
|
"increment" => Mode::Increment,
|
||||||
|
"wrap" => Mode::Wrap,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
src/db.rs
Normal file
67
src/db.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{
|
||||||
|
BufRead,
|
||||||
|
BufReader,
|
||||||
|
BufWriter,
|
||||||
|
Write,
|
||||||
|
},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use dirs::data_dir;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
HistoryItem,
|
||||||
|
HistoryResults,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn get_db_path() -> PathBuf {
|
||||||
|
if let Ok(histfile_path) = std::env::var("DMENU_HISTORY_FILE") {
|
||||||
|
PathBuf::from(histfile_path)
|
||||||
|
} else {
|
||||||
|
data_dir()
|
||||||
|
.expect("Could not get a data dir")
|
||||||
|
.join("dmenu-history")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_history(file: &File) -> HistoryResults {
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
reader
|
||||||
|
.lines()
|
||||||
|
.flatten()
|
||||||
|
.filter_map(|line| -> Option<HistoryItem> {
|
||||||
|
let (count, value) = line.split_once(" ")?;
|
||||||
|
|
||||||
|
Some(HistoryItem {
|
||||||
|
count: count.parse::<i32>().ok()?,
|
||||||
|
value: value.to_string(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_history() -> HistoryResults {
|
||||||
|
let mut hist = match &File::open(get_db_path()) {
|
||||||
|
Ok(file) => read_history(file),
|
||||||
|
Err(_) => Vec::new().into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
hist.0.sort_by_key(|i| -i.count);
|
||||||
|
|
||||||
|
hist
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_history(res: HistoryResults) {
|
||||||
|
let file = File::create(get_db_path()).expect("Cannot write to data dir");
|
||||||
|
let mut history_writer = BufWriter::new(file);
|
||||||
|
for HistoryItem { count, value } in res.0 {
|
||||||
|
let newline = format!("{} {}\n", count, value);
|
||||||
|
history_writer
|
||||||
|
.write_all(newline.as_bytes())
|
||||||
|
.expect("Cannot write to history file");
|
||||||
|
}
|
||||||
|
}
|
118
src/increment.rs
Normal file
118
src/increment.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
use crate::{
|
||||||
|
HistoryItem,
|
||||||
|
HistoryResults,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn update_history(
|
||||||
|
input: Vec<String>,
|
||||||
|
history: &HistoryResults,
|
||||||
|
) -> HistoryResults {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
let input_lines = input
|
||||||
|
.into_iter()
|
||||||
|
// Here we copy stdin to stdout so other programs can use it
|
||||||
|
.inspect(|i| println!("{}", i))
|
||||||
|
.map(|k| HistoryItem { value: k, count: 1 })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut res: HashMap<String, i32> = HashMap::new();
|
||||||
|
|
||||||
|
history.0.iter().chain(input_lines.iter()).for_each(
|
||||||
|
|hist_item: &HistoryItem| {
|
||||||
|
let entry = res
|
||||||
|
.entry(hist_item.value.clone())
|
||||||
|
.or_insert(hist_item.count - 1);
|
||||||
|
*entry += 1;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
res.into_iter()
|
||||||
|
.map(|(k, v)| HistoryItem { value: k, count: v })
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(
|
||||||
|
history: HistoryResults,
|
||||||
|
input_lines: Vec<String>,
|
||||||
|
) -> HistoryResults {
|
||||||
|
// Read
|
||||||
|
|
||||||
|
// Proccess
|
||||||
|
update_history(input_lines, &history)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn increment_empty() {
|
||||||
|
let input = vec![].into();
|
||||||
|
let history = vec![].into();
|
||||||
|
|
||||||
|
update_history(input, &history);
|
||||||
|
|
||||||
|
assert_eq!(history, vec![].into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn increment_keeps_history() {
|
||||||
|
let input = vec![].into();
|
||||||
|
let history = vec![HistoryItem {
|
||||||
|
count: 1,
|
||||||
|
value: "one".to_string(),
|
||||||
|
}]
|
||||||
|
.into();
|
||||||
|
|
||||||
|
update_history(input, &history);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
history,
|
||||||
|
vec![HistoryItem {
|
||||||
|
count: 1,
|
||||||
|
value: "one".to_string(),
|
||||||
|
},]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn increment_empty_history() {
|
||||||
|
let input = vec!["one".to_string()].into();
|
||||||
|
let history = vec![].into();
|
||||||
|
|
||||||
|
let res = update_history(input, &history);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
vec![HistoryItem {
|
||||||
|
count: 1,
|
||||||
|
value: "one".to_string(),
|
||||||
|
},]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn increment_history() {
|
||||||
|
let input = vec!["one".to_string()].into();
|
||||||
|
let history = vec![HistoryItem {
|
||||||
|
count: 1,
|
||||||
|
value: "one".to_string(),
|
||||||
|
}]
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let res = update_history(input, &history);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
vec![HistoryItem {
|
||||||
|
count: 2,
|
||||||
|
value: "one".to_string(),
|
||||||
|
},]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
67
src/main.rs
Normal file
67
src/main.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
use cli::Mode;
|
||||||
|
|
||||||
|
mod cli;
|
||||||
|
mod db;
|
||||||
|
mod increment;
|
||||||
|
mod sort;
|
||||||
|
mod wrap;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||||
|
pub struct HistoryItem {
|
||||||
|
count: i32,
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct HistoryResults(Vec<HistoryItem>);
|
||||||
|
|
||||||
|
impl From<Vec<HistoryItem>> for HistoryResults {
|
||||||
|
fn from(val: Vec<HistoryItem>) -> Self {
|
||||||
|
HistoryResults(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_lines() -> Vec<String> {
|
||||||
|
use std::io::{
|
||||||
|
stdin,
|
||||||
|
BufRead,
|
||||||
|
};
|
||||||
|
stdin().lock().lines().flatten().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let matches = cli::get_cli().get_matches();
|
||||||
|
let history = db::get_history();
|
||||||
|
|
||||||
|
match matches.subcommand_name().unwrap().into() {
|
||||||
|
Mode::Query => {
|
||||||
|
let history = &history;
|
||||||
|
for line in &history.0 {
|
||||||
|
println!("{} {}", line.count, line.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Mode::Sort => {
|
||||||
|
let input_lines = read_lines();
|
||||||
|
let history = &sort::run(history, input_lines);
|
||||||
|
|
||||||
|
for line in &history.0 {
|
||||||
|
println!("{}", line.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Mode::Increment => {
|
||||||
|
let input_lines = read_lines();
|
||||||
|
db::put_history(increment::run(history, input_lines));
|
||||||
|
}
|
||||||
|
Mode::Wrap => {
|
||||||
|
let input_lines = read_lines();
|
||||||
|
|
||||||
|
let args = matches.subcommand().unwrap().1;
|
||||||
|
|
||||||
|
let cmd = args
|
||||||
|
.values_of("command")
|
||||||
|
.map(|x| x.collect::<Vec<_>>())
|
||||||
|
.unwrap();
|
||||||
|
db::put_history(wrap::run(history, input_lines, cmd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
162
src/sort.rs
Normal file
162
src/sort.rs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
use crate::{
|
||||||
|
HistoryItem,
|
||||||
|
HistoryResults,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn increment_with(main: &mut HistoryResults, rhs: &HistoryResults) {
|
||||||
|
for main_item in main.0.iter_mut() {
|
||||||
|
for rhs_item in rhs.0.iter() {
|
||||||
|
if rhs_item.value == main_item.value {
|
||||||
|
main_item.count += rhs_item.count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn results_from_vec(mut input: Vec<String>) -> HistoryResults {
|
||||||
|
input.dedup();
|
||||||
|
input
|
||||||
|
.into_iter()
|
||||||
|
.map(|item| HistoryItem {
|
||||||
|
count: 1,
|
||||||
|
value: item,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_lines(
|
||||||
|
history: HistoryResults,
|
||||||
|
mut input: HistoryResults,
|
||||||
|
) -> HistoryResults {
|
||||||
|
increment_with(&mut input, &history);
|
||||||
|
|
||||||
|
// Sort such that items with a bigger count are at the start
|
||||||
|
input.0.sort_by_key(|i| -i.count);
|
||||||
|
input
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(history: HistoryResults, stdin_read: Vec<String>) -> HistoryResults {
|
||||||
|
let input = results_from_vec(stdin_read);
|
||||||
|
sort_lines(history, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn history_stays_empty() {
|
||||||
|
let input = vec![].into();
|
||||||
|
let history = vec![].into();
|
||||||
|
|
||||||
|
let res = sort_lines(input, history);
|
||||||
|
|
||||||
|
assert_eq!(res, vec![].into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn history_stays_no_input() {
|
||||||
|
let input = vec![].into();
|
||||||
|
let history = vec![
|
||||||
|
HistoryItem {
|
||||||
|
count: 1,
|
||||||
|
value: "sample text".to_string(),
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
count: 2,
|
||||||
|
value: "sample text".to_string(),
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
count: 3,
|
||||||
|
value: "sample text".to_string(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let res = sort_lines(history, input);
|
||||||
|
|
||||||
|
assert_eq!(res, vec![].into());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn history_stays_no_history() {
|
||||||
|
let input = vec![
|
||||||
|
HistoryItem {
|
||||||
|
count: 1,
|
||||||
|
value: "sample text".to_string(),
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
count: 2,
|
||||||
|
value: "sample text".to_string(),
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
count: 3,
|
||||||
|
value: "sample text".to_string(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into();
|
||||||
|
let history = vec![].into();
|
||||||
|
|
||||||
|
let res = sort_lines(history, input);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
vec![
|
||||||
|
HistoryItem {
|
||||||
|
count: 3,
|
||||||
|
value: "sample text".to_string(),
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
count: 2,
|
||||||
|
value: "sample text".to_string(),
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
count: 1,
|
||||||
|
value: "sample text".to_string(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_occurances() {
|
||||||
|
let input =
|
||||||
|
vec!["one".to_string(), "two".to_string(), "three".to_string()];
|
||||||
|
let history = vec![
|
||||||
|
HistoryItem {
|
||||||
|
count: 1,
|
||||||
|
value: "one".to_string(),
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
count: 3,
|
||||||
|
value: "two".to_string(),
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
count: 1,
|
||||||
|
value: "three".to_string(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let res = sort_lines(history, results_from_vec(input));
|
||||||
|
|
||||||
|
let expected: HistoryResults = vec![
|
||||||
|
HistoryItem {
|
||||||
|
count: 4,
|
||||||
|
value: "two".to_string(),
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
count: 2,
|
||||||
|
value: "one".to_string(),
|
||||||
|
},
|
||||||
|
HistoryItem {
|
||||||
|
count: 2,
|
||||||
|
value: "three".to_string(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into();
|
||||||
|
|
||||||
|
assert_eq!(res, expected);
|
||||||
|
}
|
||||||
|
}
|
0
src/tests.rs
Normal file
0
src/tests.rs
Normal file
89
src/wrap.rs
Normal file
89
src/wrap.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use std::{
|
||||||
|
io::Write,
|
||||||
|
process::{
|
||||||
|
Command,
|
||||||
|
Stdio,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
increment,
|
||||||
|
sort,
|
||||||
|
HistoryResults,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn run(
|
||||||
|
history: HistoryResults,
|
||||||
|
input_lines: Vec<String>,
|
||||||
|
cmd: Vec<&str>,
|
||||||
|
) -> HistoryResults {
|
||||||
|
// let history = ;
|
||||||
|
|
||||||
|
let mut arguments = cmd.into_iter().map(|f| f.to_string());
|
||||||
|
let command = arguments.next().unwrap();
|
||||||
|
|
||||||
|
let command_input = sort::run(history.clone(), input_lines)
|
||||||
|
.0
|
||||||
|
.into_iter()
|
||||||
|
.map(|f| f.value)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let child_output =
|
||||||
|
shell_command(command, arguments.collect(), command_input);
|
||||||
|
|
||||||
|
increment::run(history, child_output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shell_command(
|
||||||
|
command: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
stdin: Vec<String>,
|
||||||
|
) -> Vec<String> {
|
||||||
|
let mut child = Command::new(&command)
|
||||||
|
.args(args)
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
panic!("failed to spawn program {}: {}", command, e)
|
||||||
|
});
|
||||||
|
{
|
||||||
|
let mut child_stdin = child
|
||||||
|
.stdin
|
||||||
|
.take()
|
||||||
|
.unwrap_or_else(|| panic!("failed to get stdin of {}", command));
|
||||||
|
for line in &stdin {
|
||||||
|
let text = format!("{}\n", line);
|
||||||
|
child_stdin.write_all(text.as_bytes()).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let child_output_bytes = child
|
||||||
|
.wait_with_output()
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
panic!("failed to get stdout of {}: {}", command, e)
|
||||||
|
})
|
||||||
|
.stdout;
|
||||||
|
let child_output = std::str::from_utf8(&child_output_bytes)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
panic!("failed to read output of {}: {}", command, e)
|
||||||
|
})
|
||||||
|
.lines()
|
||||||
|
.map(|i| i.to_string())
|
||||||
|
.collect();
|
||||||
|
child_output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_shell() {
|
||||||
|
let command = "cat".to_string();
|
||||||
|
let input = "hello\nworld!".lines().map(|x| x.to_string()).collect();
|
||||||
|
let expected_output: Vec<String> =
|
||||||
|
"hello\nworld!".lines().map(|x| x.to_string()).collect();
|
||||||
|
|
||||||
|
assert_eq!(shell_command(command, Vec::new(), input), expected_output)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue