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", "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]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -35,12 +47,19 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]] [[package]]
name = "awatch" name = "awatch"
version = "0.3.1" version = "0.3.1"
dependencies = [ dependencies = [
"alphanumeric-sort", "alphanumeric-sort",
"colored", "colored",
"dirs",
"failure", "failure",
"failure_derive", "failure_derive",
"lazy_static", "lazy_static",
@ -75,12 +94,29 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "base64"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 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]] [[package]]
name = "cc" name = "cc"
version = "1.0.50" version = "1.0.50"
@ -119,6 +155,45 @@ dependencies = [
"winapi", "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]] [[package]]
name = "failure" name = "failure"
version = "0.1.7" version = "0.1.7"
@ -141,6 +216,17 @@ dependencies = [
"synstructure", "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]] [[package]]
name = "heck" name = "heck"
version = "0.3.1" version = "0.3.1"
@ -248,6 +334,17 @@ dependencies = [
"redox_syscall", "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]] [[package]]
name = "regex" name = "regex"
version = "1.3.5" version = "1.3.5"
@ -266,6 +363,18 @@ version = "0.6.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" 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]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.16" version = "0.1.16"
@ -439,6 +548,12 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" 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]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.8" 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 = "1.0" # A generic serialization/deserialization framework.
serde_derive = "1.0.105" # Used to configure json config stucture. serde_derive = "1.0.105" # Used to configure json config stucture.
serde_json = "1.0" # Used to store config. 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 = "0.1.7" # Experimental error handling abstraction.
failure_derive = "0.1.7" # Used to create new error type. failure_derive = "0.1.7" # Used to create new error type.
lazy_static = "1.4" # Define lazy static vars. lazy_static = "1.4" # Define lazy static vars.

View File

@ -8,6 +8,35 @@ pub struct Opt {
pub mode: Option<RunMode> 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)] #[derive(StructOpt, Debug)]
pub enum RunMode { pub enum RunMode {
#[structopt(name = "init", about = "Initialize config")] #[structopt(name = "init", about = "Initialize config")]
@ -22,4 +51,76 @@ pub enum RunMode {
Update, Update,
#[structopt(name = "reset", about = "Set episode to 0")] #[structopt(name = "reset", about = "Set episode to 0")]
Reset, 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 crate::CONFIG_PATH;
use std::io::{Write, Read}; use std::io::{Read, Write};
use crate::tty_stuff::{choose_pattern, choose_command, choose_episode};
#[derive(Serialize, Default, Clone, Deserialize)] #[derive(Serialize, Default, Clone, Deserialize)]
pub struct Config { pub struct Config {
@ -19,9 +19,9 @@ impl Config {
pub fn new(pattern: String, maybe_command: String) -> AppResult<Self> { pub fn new(pattern: String, maybe_command: String) -> AppResult<Self> {
let mut command = maybe_command; let mut command = maybe_command;
if pattern.is_empty() { if pattern.is_empty() {
return Err(AppError::RuntimeError( return Err(AppError::RuntimeError(String::from(
String::from("Pattern can't be empty") "Pattern can't be empty",
)); )));
} }
if command.is_empty() { if command.is_empty() {
command = default_command(); command = default_command();
@ -43,9 +43,7 @@ impl Config {
pub fn read() -> AppResult<Self> { pub fn read() -> AppResult<Self> {
let config_path = std::path::Path::new(CONFIG_PATH.as_str()); let config_path = std::path::Path::new(CONFIG_PATH.as_str());
if !config_path.exists() { if !config_path.exists() {
return Err( return Err(AppError::StdErr(String::from("Run 'awatch init' first.")));
AppError::StdErr(String::from("Run 'awatch init' first."))
);
} }
let mut file = std::fs::File::open(CONFIG_PATH.as_str())?; let mut file = std::fs::File::open(CONFIG_PATH.as_str())?;
let mut buffer = String::new(); let mut buffer = String::new();

View File

@ -1,10 +1,12 @@
use crate::result::AppResult;
use crate::tty_stuff::{choose_pattern, choose_command};
use crate::config::Config; use crate::config::Config;
use crate::result::AppResult;
use crate::tty_stuff::{choose_command, choose_pattern};
pub fn init_config() -> AppResult<()> { pub fn init_config() -> AppResult<()> {
let pattern = choose_pattern(String::new())?; 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)?; let config = Config::new(pattern, command)?;
config.save()?; config.save()?;
Ok(()) Ok(())

View File

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

View File

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

View File

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