From e5c6aea874e17183db77040f8e870b73e1b6e56f Mon Sep 17 00:00:00 2001 From: Haruaki Tamada Date: Tue, 31 Dec 2024 11:23:39 +0900 Subject: [PATCH 1/2] feat: add input option to read directory list from file and update completions --- assets/completions/bash/sibling | 16 +++--- assets/completions/elvish/sibling | 3 +- assets/completions/fish/sibling | 4 +- assets/completions/powershell/sibling | 39 +++++++-------- assets/completions/zsh/_sibling | 9 ++-- src/cli.rs | 6 ++- src/dirs.rs | 70 ++++++++++++++++++++++++++- src/main.rs | 32 ++++++++++++ testdata/dirlist.txt | 5 ++ 9 files changed, 149 insertions(+), 35 deletions(-) create mode 100644 testdata/dirlist.txt diff --git a/assets/completions/bash/sibling b/assets/completions/bash/sibling index 0b5d3d1..f0a26cb 100644 --- a/assets/completions/bash/sibling +++ b/assets/completions/bash/sibling @@ -19,7 +19,7 @@ _sibling() { case "${cmd}" in sibling) - opts="-a -l -p -P -s -i -t -h -V --csv --absolute --list --progress --parent --step --init --type --help --version [DIR]..." + opts="-a -l -p -P -s -t -i -h -V --csv --absolute --list --progress --parent --step --init --type --input --help --version [DIR]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -37,10 +37,6 @@ _sibling() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - -i) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; --type) COMPREPLY=($(compgen -W "first last previous next random keep" -- "${cur}")) return 0 @@ -49,8 +45,16 @@ _sibling() { COMPREPLY=($(compgen -W "first last previous next random keep" -- "${cur}")) return 0 ;; + --input) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -i) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) - COMPREPLY=($(compgen -A directory -- "${cur}")) + COMPREPLY=() ;; esac COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) diff --git a/assets/completions/elvish/sibling b/assets/completions/elvish/sibling index 9926c89..f4922ba 100644 --- a/assets/completions/elvish/sibling +++ b/assets/completions/elvish/sibling @@ -20,10 +20,11 @@ set edit:completion:arg-completer[sibling] = {|@words| &'sibling'= { cand -s 'specify the number of times to execute sibling' cand --step 'specify the number of times to execute sibling' - cand -i 'generate the initialize script for the shell' cand --init 'generate the initialize script for the shell' cand -t 'specify the nexter type' cand --type 'specify the nexter type' + cand -i 'directory list from file, if FILE is "-", reads from stdin.' + cand --input 'directory list from file, if FILE is "-", reads from stdin.' cand --csv 'print the result in the csv format' cand -a 'print the directory name in the absolute path' cand --absolute 'print the directory name in the absolute path' diff --git a/assets/completions/fish/sibling b/assets/completions/fish/sibling index 1758517..274ca9a 100644 --- a/assets/completions/fish/sibling +++ b/assets/completions/fish/sibling @@ -1,6 +1,7 @@ complete -c sibling -s s -l step -d 'specify the number of times to execute sibling' -r -complete -c sibling -s i -l init -d 'generate the initialize script for the shell' -r +complete -c sibling -l init -d 'generate the initialize script for the shell' -r complete -c sibling -s t -l type -d 'specify the nexter type' -r -f -a "{first\t'',last\t'',previous\t'',next\t'',random\t'',keep\t''}" +complete -c sibling -s i -l input -d 'directory list from file, if FILE is "-", reads from stdin.' -r complete -c sibling -l csv -d 'print the result in the csv format' complete -c sibling -s a -l absolute -d 'print the directory name in the absolute path' complete -c sibling -s l -l list -d 'list the sibling directories' @@ -8,4 +9,3 @@ complete -c sibling -s p -l progress -d 'print the progress of traversing direct complete -c sibling -s P -l parent -d 'print parent directory, when no more sibling directories are found' complete -c sibling -s h -l help -d 'Print help' complete -c sibling -s V -l version -d 'Print version' -complete -c sibling -xa '(__fish_complete_directories)' diff --git a/assets/completions/powershell/sibling b/assets/completions/powershell/sibling index 3999568..c889f55 100644 --- a/assets/completions/powershell/sibling +++ b/assets/completions/powershell/sibling @@ -21,25 +21,26 @@ Register-ArgumentCompleter -Native -CommandName 'sibling' -ScriptBlock { $completions = @(switch ($command) { 'sibling' { - [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'specify the number of times to execute sibling') - [CompletionResult]::new('--step', 'step', [CompletionResultType]::ParameterName, 'specify the number of times to execute sibling') - [CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'generate the initialize script for the shell') - [CompletionResult]::new('--init', 'init', [CompletionResultType]::ParameterName, 'generate the initialize script for the shell') - [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'specify the nexter type') - [CompletionResult]::new('--type', 'type', [CompletionResultType]::ParameterName, 'specify the nexter type') - [CompletionResult]::new('--csv', 'csv', [CompletionResultType]::ParameterName, 'print the result in the csv format') - [CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'print the directory name in the absolute path') - [CompletionResult]::new('--absolute', 'absolute', [CompletionResultType]::ParameterName, 'print the directory name in the absolute path') - [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'list the sibling directories') - [CompletionResult]::new('--list', 'list', [CompletionResultType]::ParameterName, 'list the sibling directories') - [CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'print the progress of traversing directories') - [CompletionResult]::new('--progress', 'progress', [CompletionResultType]::ParameterName, 'print the progress of traversing directories') - [CompletionResult]::new('-P', 'P ', [CompletionResultType]::ParameterName, 'print parent directory, when no more sibling directories are found') - [CompletionResult]::new('--parent', 'parent', [CompletionResultType]::ParameterName, 'print parent directory, when no more sibling directories are found') - [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') - [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') - [CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version') - [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version') + [CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'specify the number of times to execute sibling') + [CompletionResult]::new('--step', '--step', [CompletionResultType]::ParameterName, 'specify the number of times to execute sibling') + [CompletionResult]::new('--init', '--init', [CompletionResultType]::ParameterName, 'generate the initialize script for the shell') + [CompletionResult]::new('-t', '-t', [CompletionResultType]::ParameterName, 'specify the nexter type') + [CompletionResult]::new('--type', '--type', [CompletionResultType]::ParameterName, 'specify the nexter type') + [CompletionResult]::new('-i', '-i', [CompletionResultType]::ParameterName, 'directory list from file, if FILE is "-", reads from stdin.') + [CompletionResult]::new('--input', '--input', [CompletionResultType]::ParameterName, 'directory list from file, if FILE is "-", reads from stdin.') + [CompletionResult]::new('--csv', '--csv', [CompletionResultType]::ParameterName, 'print the result in the csv format') + [CompletionResult]::new('-a', '-a', [CompletionResultType]::ParameterName, 'print the directory name in the absolute path') + [CompletionResult]::new('--absolute', '--absolute', [CompletionResultType]::ParameterName, 'print the directory name in the absolute path') + [CompletionResult]::new('-l', '-l', [CompletionResultType]::ParameterName, 'list the sibling directories') + [CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'list the sibling directories') + [CompletionResult]::new('-p', '-p', [CompletionResultType]::ParameterName, 'print the progress of traversing directories') + [CompletionResult]::new('--progress', '--progress', [CompletionResultType]::ParameterName, 'print the progress of traversing directories') + [CompletionResult]::new('-P', '-P ', [CompletionResultType]::ParameterName, 'print parent directory, when no more sibling directories are found') + [CompletionResult]::new('--parent', '--parent', [CompletionResultType]::ParameterName, 'print parent directory, when no more sibling directories are found') + [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help') + [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help') + [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version') + [CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version') break } }) diff --git a/assets/completions/zsh/_sibling b/assets/completions/zsh/_sibling index b890783..955804a 100644 --- a/assets/completions/zsh/_sibling +++ b/assets/completions/zsh/_sibling @@ -15,12 +15,13 @@ _sibling() { local context curcontext="$curcontext" state line _arguments "${_arguments_options[@]}" : \ -'-s+[specify the number of times to execute sibling]:COUNT: ' \ -'--step=[specify the number of times to execute sibling]:COUNT: ' \ -'-i+[generate the initialize script for the shell]:SHELL: ' \ -'--init=[generate the initialize script for the shell]:SHELL: ' \ +'-s+[specify the number of times to execute sibling]:COUNT:_default' \ +'--step=[specify the number of times to execute sibling]:COUNT:_default' \ +'--init=[generate the initialize script for the shell]:SHELL:_default' \ '-t+[specify the nexter type]:TYPE:(first last previous next random keep)' \ '--type=[specify the nexter type]:TYPE:(first last previous next random keep)' \ +'-i+[directory list from file, if FILE is "-", reads from stdin.]:FILE:_default' \ +'--input=[directory list from file, if FILE is "-", reads from stdin.]:FILE:_default' \ '--csv[print the result in the csv format]' \ '-a[print the directory name in the absolute path]' \ '--absolute[print the directory name in the absolute path]' \ diff --git a/src/cli.rs b/src/cli.rs index 49ecd98..c60c112 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -10,6 +10,7 @@ pub enum SiblingError { NotFound(PathBuf), NoParent(PathBuf), NotDir(PathBuf), + NotFile(PathBuf), Fatal(String), Array(Vec), } @@ -38,12 +39,15 @@ pub struct CliOpts { #[arg(short, long, help = "specify the number of times to execute sibling", value_name = "COUNT", default_value_t = 1)] pub step: i32, - #[arg(short, long, help = "generate the initialize script for the shell", value_name = "SHELL", hide = true, default_missing_value = "bash")] + #[arg(long, help = "generate the initialize script for the shell", value_name = "SHELL", hide = true, default_missing_value = "bash")] pub init: Option, #[arg(short = 't', long = "type", help = "specify the nexter type", value_enum, default_value_t = NexterType::Next, value_name = "TYPE", ignore_case = true)] pub nexter: NexterType, + #[arg(short, long, help = "directory list from file, if FILE is \"-\", reads from stdin.", value_name = "FILE")] + pub input: Option, + #[arg(index = 1, help = "the target directory", value_name = "DIR")] pub dirs: Vec, } diff --git a/src/dirs.rs b/src/dirs.rs index e0b0022..9446ca2 100644 --- a/src/dirs.rs +++ b/src/dirs.rs @@ -1,4 +1,5 @@ -use std::{fs, path::{Path, PathBuf}}; +use std::io::{BufRead, BufReader}; +use std::path::{Path, PathBuf}; use crate::cli::{Result, SiblingError}; @@ -20,7 +21,7 @@ impl Dirs { } } else if current_dir.exists() { if current_dir.is_dir() { - let current = fs::canonicalize(¤t_dir).unwrap(); + let current = std::fs::canonicalize(¤t_dir).unwrap(); build_dirs(current.clone().parent(), current) } else { Err(SiblingError::NotDir(current_dir)) @@ -29,6 +30,19 @@ impl Dirs { Err(SiblingError::NotFound(current_dir)) } } + pub fn new_from_file(file: String) -> Result { + if file == "-" { + return build_from_reader(Box::new(std::io::stdin().lock())); + } + let path = PathBuf::from(file); + if !path.exists() { + return Err(SiblingError::NotFound(path)); + } else if path.is_dir() { + return Err(SiblingError::NotFile(path)); + } else { + return build_from_list(path); + } + } pub fn current_path(&self) -> PathBuf { self.dirs[self.current].clone() @@ -87,6 +101,48 @@ fn find_current(dirs: &[PathBuf], current: &PathBuf) -> usize { dirs.iter().position(|dir| dir == current).unwrap_or(0) } +fn build_from_reader(reader: Box) -> Result { + let lines = reader.lines() + .filter_map(|line| line.map(|n| n.trim().to_string()).ok()) + .collect::>(); + let base = if let Some(base) = lines.iter().find(|l| l.starts_with("parent:")) { + base.chars().skip(7).collect::().trim().to_string() + } else { + ".".to_string() + }; + let dirs = lines.iter() + .filter(|l| !l.starts_with("parent:")) + .map(|line| PathBuf::from(line)) + .collect::>(); + let current = find_current_dir_index(&dirs); + Ok(Dirs { + dirs, + parent: PathBuf::from(base), + current: current, + next: -1, + no_more_dir: false, + }) +} + +fn find_current_dir_index(dirs: &Vec) -> usize { + if let Ok(pwd) = std::env::current_dir() { + let cwd = PathBuf::from("."); + if let Some(pos) = dirs.iter().position(|dir| dir == &cwd || pwd.ends_with(dir)) { + return pos; + } + } + 0 as usize +} + +fn build_from_list(filename: PathBuf) -> Result { + if let Ok(f) = std::fs::File::open(filename) { + let reader = BufReader::new(f); + build_from_reader(Box::new(reader)) + } else { + Err(SiblingError::Io(std::io::Error::last_os_error())) + } +} + #[cfg(test)] mod tests { use super::*; @@ -107,4 +163,14 @@ mod tests { let dirs = dirs.unwrap(); assert_eq!(dirs.current_path().file_name().map(|s| s.to_str()), Some("sibling".into())); } + + #[test] + fn test_dir_from_file() { + let dirs = Dirs::new_from_file("testdata/dirlist.txt".into()); + assert!(dirs.is_ok()); + let dirs = dirs.unwrap(); + assert_eq!(dirs.dirs.len(), 4); + assert_eq!(dirs.current, 1); + assert_eq!(dirs.parent, PathBuf::from("testdata")); + } } diff --git a/src/main.rs b/src/main.rs index 01b1e45..2b3de80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; +use std::vec; use clap::Parser; use crate::cli::{CliOpts, PrintingOpts, Result, SiblingError}; @@ -15,6 +16,18 @@ fn perform_impl(mut dirs: dirs::Dirs, nexter: &Box, step: i32, opts: printer::result_string(&dirs, opts) } +fn perform_from_file(opts: CliOpts) -> Vec> { + let nexter = nexter::build_nexter(opts.nexter); + let r = match opts.input { + None => Err(SiblingError::Fatal("input is not specified".into())), + Some(file) => match dirs::Dirs::new_from_file(file) { + Err(e) => Err(e), + Ok(dirs) => perform_impl(dirs, &nexter, opts.step, &opts.p_opts), + } + }; + vec![r] +} + fn perform_each(dir: std::path::PathBuf, nexter: &Box, step: i32, opts: &PrintingOpts) -> Result { match dirs::Dirs::new(dir) { Err(e) => Err(e), @@ -45,6 +58,8 @@ fn perform_sibling(opts: CliOpts) -> Vec> { fn perform(opts: CliOpts) -> Vec> { if let Some(shell) = opts.init { vec![init::generate_init_script(shell)] + } else if opts.input.is_some() { + perform_from_file(opts) } else { perform_sibling(opts) } @@ -58,6 +73,7 @@ fn print_error(e: &SiblingError) { SiblingError::Array(array) => { array.iter().for_each(print_error); }, + SiblingError::NotFile(path) => eprintln!("{:?}: not a file", path), SiblingError::NotFound(path) => eprintln!("{:?}: not found", path), SiblingError::Fatal(message) => eprintln!("fatal error: {}", message) } @@ -98,4 +114,20 @@ mod tests { Ok(result) => println!("{}", result), } } + + #[test] + fn test_from_file() { + let opts_r = cli::CliOpts::try_parse_from(vec!["sibling", "--input", "testdata/dirlist.txt", "--type", "previous"]); + + if let Err(e) = &opts_r { + eprintln!("{}", e); + } + assert!(opts_r.is_ok()); + let r = perform(opts_r.unwrap()); + assert_eq!(r.len(), 1); + match r.get(0).unwrap() { + Err(e) => print_error(&e), + Ok(result) => assert_eq!(result, "testdata/a") + } + } } \ No newline at end of file diff --git a/testdata/dirlist.txt b/testdata/dirlist.txt new file mode 100644 index 0000000..8a409be --- /dev/null +++ b/testdata/dirlist.txt @@ -0,0 +1,5 @@ +testdata/a +. +testdata/b +testdata/c +parent:testdata \ No newline at end of file From 20c297c73766bee177c952cd571e8fc49e302ad1 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 31 Dec 2024 02:23:59 +0000 Subject: [PATCH 2/2] update version to 2.0.0-beta-7, ready to publish v2.0.0-beta-7 --- Cargo.toml | 2 +- docs/config.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 31003f6..ba47794 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sibling" -version = "2.0.0-beta-6" +version = "2.0.0-beta-7" description = "get next/previous sibling directory name." repository = "https://github.com/tamada/sibling" homepage = "https://tamada.github.io/sibling" diff --git a/docs/config.toml b/docs/config.toml index b22ea60..a9a3fc1 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -15,7 +15,7 @@ pygmentsStyle = "pygments" project_tagline = "get next/previous sibling directory name" dateFormat = "2006-01-02" katex = true - version = "2.0.0-beta-6" + version = "2.0.0-beta-7" footer = "[![GitHub](https://img.shields.io/badge/GitHub-tamada/sibling-blueviolet.svg?logo=github)](https://github.com/tamada/sibling) Made with [Hugo](https://gohugo.io/). Theme by [Cayman](https://github.com/zwbetz-gh/cayman-hugo-theme). Deployed to [GitHub Pages](https://pages.github.com/)." [menu]