Added autcompletions.

Description:
- Added autocompletions for some shells.
- Fixed styles.
- Fixed possible bugs with clippy.

Signed-off-by: Pavel Kirilin <win10@list.ru>
This commit is contained in:
2020-05-06 03:40:08 +04:00
parent 496a65f60a
commit 28902116a5
9 changed files with 315 additions and 94 deletions

115
Cargo.lock generated
View File

@ -24,6 +24,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]]
name = "atty"
version = "0.2.14"
@ -35,12 +47,19 @@ dependencies = [
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "awatch"
version = "0.3.1"
dependencies = [
"alphanumeric-sort",
"colored",
"dirs",
"failure",
"failure_derive",
"lazy_static",
@ -75,12 +94,29 @@ dependencies = [
"libc",
]
[[package]]
name = "base64"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "blake2b_simd"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
dependencies = [
"arrayref",
"arrayvec",
"constant_time_eq",
]
[[package]]
name = "cc"
version = "1.0.50"
@ -119,6 +155,45 @@ dependencies = [
"winapi",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg",
"cfg-if",
"lazy_static",
]
[[package]]
name = "dirs"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
dependencies = [
"cfg-if",
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
dependencies = [
"cfg-if",
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "failure"
version = "0.1.7"
@ -141,6 +216,17 @@ dependencies = [
"synstructure",
]
[[package]]
name = "getrandom"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "heck"
version = "0.3.1"
@ -248,6 +334,17 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "redox_users"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431"
dependencies = [
"getrandom",
"redox_syscall",
"rust-argon2",
]
[[package]]
name = "regex"
version = "1.3.5"
@ -266,6 +363,18 @@ version = "0.6.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
[[package]]
name = "rust-argon2"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
dependencies = [
"base64",
"blake2b_simd",
"constant_time_eq",
"crossbeam-utils",
]
[[package]]
name = "rustc-demangle"
version = "0.1.16"
@ -439,6 +548,12 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "winapi"
version = "0.3.8"

View File

@ -11,6 +11,7 @@ structopt = "0.3" # Used to build CLI.
serde = "1.0" # A generic serialization/deserialization framework.
serde_derive = "1.0.105" # Used to configure json config stucture.
serde_json = "1.0" # Used to store config.
dirs = "2.0.2" # Dirs for locating home directory.
failure = "0.1.7" # Experimental error handling abstraction.
failure_derive = "0.1.7" # Used to create new error type.
lazy_static = "1.4" # Define lazy static vars.

View File

@ -8,6 +8,35 @@ pub struct Opt {
pub mode: Option<RunMode>
}
#[derive(StructOpt, Debug)]
pub enum ShellType {
#[structopt(
about = "Generates a .bash completion file for the Bourne Again SHell (BASH)",
name = "bash"
)]
Bash,
#[structopt(
about = "Generates a .fish completion file for the Friendly Interactive SHell (fish)",
name = "fish"
)]
Fish,
#[structopt(
about = "Generates a completion file for the Z SHell (ZSH)",
name = "zsh"
)]
Zsh,
#[structopt(
about = "Generates a completion file for PowerShell",
name = "ps"
)]
PowerShell,
#[structopt(
about = "Generates a completion file for Elvish",
name = "elvish"
)]
Elvish,
}
#[derive(StructOpt, Debug)]
pub enum RunMode {
#[structopt(name = "init", about = "Initialize config")]
@ -22,4 +51,76 @@ pub enum RunMode {
Update,
#[structopt(name = "reset", about = "Set episode to 0")]
Reset,
#[structopt(name = "completion", about = "Generate autocompletion \
for your shell. If no shell was specified, then it will try \
to recognize it automatically.")]
Completion {
#[structopt(subcommand)]
shell: Option<ShellType>
},
}
impl From<ShellType> for Shell {
fn from(shell: ShellType) -> Self {
match shell {
ShellType::Bash => { Shell::Bash }
ShellType::Fish => { Shell::Fish }
ShellType::Zsh => { Shell::Zsh }
ShellType::PowerShell => { Shell::PowerShell }
ShellType::Elvish => { Shell::Elvish }
}
}
}
fn recognize_shell() -> AppResult<Shell> {
println!("Started auto shell recognition.");
let shell_var = std::env::var("SHELL").map_err(|_| {
AppError::RuntimeError(String::from(
"$SHELL env variable not found please specify shell by yourself",
))
})?;
let shell_split: Vec<_> = shell_var.split('/').collect();
if shell_split.is_empty() {
return Err(AppError::RuntimeError(String::from(
"Can't recognize shell please specify it by yourself.",
)));
}
let shell_name = shell_split.last().unwrap();
Shell::from_str(shell_name).map_err(|_| AppError::RuntimeError(String::from("Unknown shell")))
}
pub fn generate_completion(shell: Option<ShellType>) -> AppResult<()> {
let shell = if let Some(shell_val) = shell {
Shell::from(shell_val)
} else {
let current_shell = recognize_shell()?;
println!("Recognized shell: {}", current_shell.to_string());
current_shell
};
let home_dir = dirs::home_dir();
let mut target_dir = String::from(".");
if let Some(home_dir) = home_dir {
target_dir = match shell {
Shell::Bash => {
String::from("/etc/bash_completion.d")
}
Shell::Fish => {
let fish_dir = format!("{}/.config/fish/completions/", home_dir.display());
fish_dir
}
Shell::Zsh => {
let zsh_dir = std::env::var("ZSH")
.map_err(|_| AppError::RuntimeError(String::from(
"Please install oh-my-zsh for rich autocompletions."
)))?;
format!("{}/completions", zsh_dir)
}
Shell::PowerShell => { String::from(".") }
Shell::Elvish => { String::from(".") }
};
}
std::fs::create_dir_all(target_dir.as_str()).ok();
Opt::clap().gen_completions(env!("CARGO_PKG_NAME"), shell, target_dir.as_str());
println!("Completion file saved at {}", target_dir);
Ok(())
}

View File

@ -1,7 +1,7 @@
use crate::result::{AppResult, AppError};
use crate::result::{AppError, AppResult};
use crate::tty_stuff::{choose_command, choose_episode, choose_pattern};
use crate::CONFIG_PATH;
use std::io::{Write, Read};
use crate::tty_stuff::{choose_pattern, choose_command, choose_episode};
use std::io::{Read, Write};
#[derive(Serialize, Default, Clone, Deserialize)]
pub struct Config {
@ -19,9 +19,9 @@ impl Config {
pub fn new(pattern: String, maybe_command: String) -> AppResult<Self> {
let mut command = maybe_command;
if pattern.is_empty() {
return Err(AppError::RuntimeError(
String::from("Pattern can't be empty")
));
return Err(AppError::RuntimeError(String::from(
"Pattern can't be empty",
)));
}
if command.is_empty() {
command = default_command();
@ -43,9 +43,7 @@ impl Config {
pub fn read() -> AppResult<Self> {
let config_path = std::path::Path::new(CONFIG_PATH.as_str());
if !config_path.exists() {
return Err(
AppError::StdErr(String::from("Run 'awatch init' first."))
);
return Err(AppError::StdErr(String::from("Run 'awatch init' first.")));
}
let mut file = std::fs::File::open(CONFIG_PATH.as_str())?;
let mut buffer = String::new();
@ -100,4 +98,4 @@ pub fn get_matched_files(pattern: String) -> AppResult<Vec<String>> {
}
alphanumeric_sort::sort_str_slice(names.as_mut_slice());
Ok(names)
}
}

View File

@ -1,11 +1,13 @@
use crate::result::AppResult;
use crate::tty_stuff::{choose_pattern, choose_command};
use crate::config::Config;
use crate::result::AppResult;
use crate::tty_stuff::{choose_command, choose_pattern};
pub fn init_config() -> AppResult<()> {
let pattern = choose_pattern(String::new())?;
let command = choose_command(String::from("mpv --fullscreen \"{}\""))?.trim().to_string();
let command = choose_command(String::from("mpv --fullscreen \"{}\""))?
.trim()
.to_string();
let config = Config::new(pattern, command)?;
config.save()?;
Ok(())
}
}

View File

@ -13,24 +13,27 @@ lazy_static! {
use structopt::StructOpt;
use crate::result::{AppError, AppResult};
use crate::run_modes::run;
use crate::result::AppResult;
use colored::{Colorize, Color};
use colored::{Color, Colorize};
pub mod result;
pub mod config;
pub mod initialization;
pub mod result;
pub mod run_modes;
pub mod tty_stuff;
pub mod initialization;
use std::str::FromStr;
use structopt::clap::Shell;
include!("cli.rs");
fn main() -> AppResult<()> {
let opt: Opt = Opt::from_args();
if let Err(error) = run(opt) {
println!("{dashes} {title} {dashes}",
dashes = "#######".color(Color::BrightRed),
title= "Error".color(Color::BrightRed)
println!(
"{dashes} {title} {dashes}",
dashes = "#######".color(Color::BrightRed),
title = "Error".color(Color::BrightRed)
);
println!("{}", error);
}

View File

@ -26,4 +26,4 @@ impl From<regex::Error> for AppError {
fn from(err: regex::Error) -> Self {
Self::RuntimeError(err.to_string())
}
}
}

View File

@ -1,30 +1,19 @@
use crate::{Opt, RunMode};
use crate::result::{AppResult, AppError};
use crate::config::{update_config, update_episode, Config};
use crate::initialization::init_config;
use crate::config::{update_episode, update_config, Config};
use crate::result::{AppError, AppResult};
use crate::{generate_completion, Opt, RunMode};
use std::process::Command;
pub fn run(opts: Opt) -> AppResult<()> {
let mode = opts.mode.unwrap_or_else(|| RunMode::Play);
match mode {
RunMode::Init => {
init_config()
}
RunMode::Play => {
play()
}
RunMode::Prev => {
update_episode(prev_episode)
}
RunMode::Next => {
update_episode(next_episode)
}
RunMode::Update => {
update_config()
}
RunMode::Reset => {
update_episode(|_| { Ok(0) })
}
RunMode::Init => init_config(),
RunMode::Play => play(),
RunMode::Prev => update_episode(prev_episode),
RunMode::Next => update_episode(next_episode),
RunMode::Update => update_config(),
RunMode::Reset => update_episode(|_| Ok(0)),
RunMode::Completion { shell } => generate_completion(shell),
}
}
@ -32,9 +21,9 @@ pub fn prev_episode(current: usize) -> AppResult<usize> {
if let Some(episode) = current.checked_sub(1) {
Ok(episode)
} else {
Err(AppError::RuntimeError(
String::from("Episode can't be less than zero.")
))
Err(AppError::RuntimeError(String::from(
"Episode can't be less than zero.",
)))
}
}
@ -42,9 +31,9 @@ pub fn next_episode(current: usize) -> AppResult<usize> {
if let Some(episode) = current.checked_add(1) {
Ok(episode)
} else {
Err(AppError::RuntimeError(
String::from("Reached usize limit. Sorry.")
))
Err(AppError::RuntimeError(String::from(
"Reached usize limit. Sorry.",
)))
}
}
@ -58,7 +47,8 @@ fn add_leading_zero(n: usize) -> String {
fn prepare_command(conf: Config) -> AppResult<String> {
let index = conf.current_episode_count;
Ok(conf.command
Ok(conf
.command
.replace("{}", conf.get_current_episode()?.as_str())
.replace("{n}", format!("{}", index).as_str())
.replace("{n+}", format!("{}", index + 1).as_str())
@ -80,4 +70,4 @@ pub fn play() -> AppResult<()> {
episode = conf.get_current_episode()?;
}
Ok(())
}
}

View File

@ -1,12 +1,12 @@
use crate::result::AppResult;
use crate::config::get_matched_files;
use termion::input::TermRead;
use std::io::{Write, stdout, stdin, Stdout};
use termion::event::Key;
use termion::raw::{IntoRawMode, RawTerminal};
use term_grid::{Grid, GridOptions, Filling, Direction, Cell};
use crate::result::AppResult;
use std::io::{stdin, stdout, Stdout, Write};
use std::process::exit;
use std::str::FromStr;
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::{IntoRawMode, RawTerminal};
pub fn get_matched_files_grid(pattern: String, screen_width: u16) -> AppResult<String> {
let mut grid = Grid::new(GridOptions {
@ -32,20 +32,24 @@ pub fn choose_pattern(current_pattern: String) -> AppResult<String> {
current_pattern,
|stdout, pattern| {
if !pattern.is_empty() {
write!(stdout, "{}------Matched files------", termion::cursor::Goto(1, 3))?;
write!(
stdout,
"{}------Matched files------",
termion::cursor::Goto(1, 3)
)?;
let (col, _) = termion::terminal_size()?;
let grid = get_matched_files_grid(pattern, col)?;
if grid.is_empty() {
write!(stdout, "{}No matches found",
termion::cursor::Goto(1, 4)
)?;
write!(stdout, "{}No matches found", termion::cursor::Goto(1, 4))?;
} else {
for line in grid.lines() {
write!(stdout, "{}{}{}{}",
termion::cursor::Down(1),
termion::clear::CurrentLine,
termion::cursor::Left(col),
line
write!(
stdout,
"{}{}{}{}",
termion::cursor::Down(1),
termion::clear::CurrentLine,
termion::cursor::Left(col),
line
)?;
}
}
@ -63,7 +67,7 @@ pub fn choose_command(current_command: String) -> AppResult<String> {
&mut stdout,
"Command to execute files.",
current_command,
|_, _| { Ok(()) },
|_, _| Ok(()),
);
stdout.suspend_raw_mode()?;
res
@ -75,15 +79,13 @@ pub fn choose_episode(current_episode: usize) -> AppResult<usize> {
&mut stdout,
"Choose episode.",
format!("{}", current_episode),
|_, _| { Ok(()) },
).map(|s| {
usize::from_str(s.as_str()).unwrap_or_else(|_| current_episode)
});
|_, _| Ok(()),
)
.map(|s| usize::from_str(s.as_str()).unwrap_or_else(|_| current_episode));
stdout.suspend_raw_mode()?;
res
}
pub fn read_tty_line(
stdout: &mut RawTerminal<Stdout>,
prompt: &str,
@ -93,25 +95,28 @@ pub fn read_tty_line(
let stdin = stdin();
// Get the standard output stream and go to raw mode.
write!(stdout, "{}{}{}{}",
termion::clear::All,
termion::cursor::Goto(1, 1),
prompt,
termion::cursor::Goto(1, 2)
write!(
stdout,
"{}{}{}{}",
termion::clear::All,
termion::cursor::Goto(1, 1),
prompt,
termion::cursor::Goto(1, 2)
)?;
// Flush stdout (i.e. make the output appear).
stdout.flush()?;
let mut buffer = current_value;
let mut current_pos = buffer.len() + 1;
if !buffer.is_empty() {
write!(stdout, "{}{}{}",
termion::cursor::Goto(1, 2),
termion::clear::AfterCursor,
buffer)?;
after_key_press(stdout, buffer.clone())?;
write!(stdout, "{}",
termion::cursor::Goto(current_pos as u16, 2)
write!(
stdout,
"{}{}{}",
termion::cursor::Goto(1, 2),
termion::clear::AfterCursor,
buffer
)?;
after_key_press(stdout, buffer.clone())?;
write!(stdout, "{}", termion::cursor::Goto(current_pos as u16, 2))?;
stdout.flush()?;
}
for c in stdin.keys() {
@ -122,7 +127,7 @@ pub fn read_tty_line(
}
// Update pattern
Key::Char(c) => {
buffer.insert(current_pos - 1, c.clone());
buffer.insert(current_pos - 1, c);
current_pos += 1;
}
Key::Backspace => {
@ -158,19 +163,25 @@ pub fn read_tty_line(
_ => {}
}
// Clear the current line.
write!(stdout, "{}{}{}",
termion::cursor::Goto(1, 2),
termion::clear::AfterCursor,
buffer)?;
write!(
stdout,
"{}{}{}",
termion::cursor::Goto(1, 2),
termion::clear::AfterCursor,
buffer
)?;
// Print matched files
after_key_press(stdout, buffer.clone())?;
write!(stdout, "{}",
termion::cursor::Goto(current_pos as u16, 2)
)?;
write!(stdout, "{}", termion::cursor::Goto(current_pos as u16, 2))?;
stdout.flush()?;
}
write!(stdout, "{}{}", termion::clear::All, termion::cursor::Goto(1, 1))?;
write!(
stdout,
"{}{}",
termion::clear::All,
termion::cursor::Goto(1, 1)
)?;
stdout.flush()?;
// Show the cursor again before we exit.
Ok(buffer)
}
}