Skip to content

Commit 61c4b3a

Browse files
committed
refactor: enhance directory handling and update CLI options for improved functionality
1 parent 81348c2 commit 61c4b3a

12 files changed

Lines changed: 147 additions & 69 deletions

File tree

cli/src/cli.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ pub(crate) struct NexterOpts {
6565
)]
6666
pub input: Option<String>,
6767

68-
#[arg(short = 'S', long, help = "strictly check the directory traversing", default_value_t = false)]
69-
pub strict: bool,
68+
#[arg(short = 'a', long, help = "Set the targets to all directories from the given list. By default, the sibling skips non-existent directories.", default_value_t = false)]
69+
pub all: bool,
7070
}
7171

7272
#[cfg(debug_assertions)]
@@ -108,7 +108,7 @@ pub(crate) struct PrintingOpts {
108108
pub format: Format,
109109

110110
#[arg(
111-
short,
111+
short = 'A',
112112
long,
113113
help = "print the directory name in the absolute path",
114114
default_value_t = false

cli/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use sibling::{Dirs, Error, Nexter, Result};
77
mod cli;
88
mod gencomp;
99
mod init;
10-
mod printer;
10+
pub(crate) mod printer;
1111
pub(crate) mod minisib;
1212

1313
#[derive(clap::ValueEnum, Clone, Debug)]
@@ -50,7 +50,7 @@ fn perform_from_file(opts: CliOpts) -> Vec<Result<String>> {
5050
let nexter = sibling::NexterFactory::create(opts.nexter_opts.nexter);
5151
let r = match opts.nexter_opts.input {
5252
None => Err(Error::Fatal("input is not specified".into())),
53-
Some(file) => match Dirs::new_from_file(file) {
53+
Some(file) => match Dirs::new_from_file_with(file, opts.nexter_opts.all) {
5454
Err(e) => Err(e),
5555
Ok(dirs) => Ok(perform_impl(&dirs, nexter.as_ref(), opts.nexter_opts.step, &opts.p_opts)),
5656
},

cli/src/minisib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,16 @@ impl MiniSibOpts {
9191
};
9292
if let Some(dir) = target_dirs.next_with(nexter.as_ref(), self.step) {
9393
Ok(format!("{}\n{}\n{}\n{}",
94-
dir.path().to_string_lossy(),
94+
crate::printer::pathbuf_to_string(Some(dir.path()), false, target_dirs.on_dirs),
9595
target_dirs.len(),
9696
dir.index(),
9797
dir.is_last_item()))
9898
} else {
99-
Err(Error::Fatal("No sibling directory found".into()))
99+
Ok(format!("{}\n{}\n{}\n{}",
100+
crate::printer::pathbuf_to_string(Some(target_dirs.parent()), false, target_dirs.on_dirs),
101+
target_dirs.len(),
102+
-1,
103+
true))
100104
}
101105

102106
}

cli/src/printer.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ pub(crate) fn result_string(
2424

2525
fn json_string(dirs: &Dirs, next: Option<Dir<'_>>, absolute: bool) -> String {
2626
let current = dirs.current();
27-
let next_path = next.as_ref().map(|n| pathbuf_to_string(Some(n.path()), absolute));
27+
let next_path = next.as_ref().map(|n| pathbuf_to_string(Some(n.path()), absolute, dirs.on_dirs));
2828
format!(
2929
r#"{{"current":{{"path":"{}","index":{}}},"next":{{"path":"{}","index":{}}},"total":{}}}"#,
30-
pathbuf_to_string(Some(dirs.current().path()), absolute),
30+
pathbuf_to_string(Some(dirs.current().path()), absolute, dirs.on_dirs),
3131
current.index() + 1,
3232
next_path.unwrap_or_default(),
3333
next.map_or(-1, |n| i32::try_from(n.index()).unwrap() + 1),
@@ -39,8 +39,8 @@ fn csv_string(dirs: &Dirs, next: Option<Dir<'_>>, absolute: bool) -> String {
3939
let current = dirs.current();
4040
format!(
4141
r#""{}","{}",{},{},{}"#,
42-
pathbuf_to_string(Some(dirs.current().path()), absolute),
43-
pathbuf_to_string(next.as_ref().map(Dir::path), absolute),
42+
pathbuf_to_string(Some(dirs.current().path()), absolute, dirs.on_dirs),
43+
pathbuf_to_string(next.as_ref().map(Dir::path), absolute, dirs.on_dirs),
4444
current.index() + 1,
4545
next.map_or(-1, |n| i32::try_from(n.index()).unwrap() + 1),
4646
dirs.len()
@@ -49,7 +49,7 @@ fn csv_string(dirs: &Dirs, next: Option<Dir<'_>>, absolute: bool) -> String {
4949

5050
fn no_more_dir_string(dirs: &Dirs, opts: &PrintingOpts) -> String {
5151
if opts.parent {
52-
pathbuf_to_string(Some(dirs.parent()), opts.absolute)
52+
pathbuf_to_string(Some(dirs.parent()), opts.absolute, dirs.on_dirs)
5353
} else {
5454
String::from("no more sibling directory")
5555
}
@@ -71,7 +71,7 @@ fn list_string(dirs: &Dirs, next: Option<&Dir<'_>>, opts: &PrintingOpts) -> Stri
7171
"{:>4} {}{}",
7272
i + 1,
7373
prefix,
74-
pathbuf_to_string(Some(dir), opts.absolute)
74+
pathbuf_to_string(Some(dir), opts.absolute, dirs.on_dirs)
7575
));
7676
}
7777
result.join("\n")
@@ -81,16 +81,16 @@ fn result_string_impl(dirs: &Dirs, next: Option<Dir<'_>>, opts: &PrintingOpts) -
8181
if opts.progress {
8282
format!(
8383
"{} ({}/{})",
84-
pathbuf_to_string(next.as_ref().map(Dir::path), opts.absolute),
84+
pathbuf_to_string(next.as_ref().map(Dir::path), opts.absolute, dirs.on_dirs),
8585
next.map_or(-1, |n| i32::try_from(n.index()).unwrap()) + 1,
8686
dirs.len()
8787
)
8888
} else {
89-
pathbuf_to_string(next.as_ref().map(Dir::path), opts.absolute).to_string()
89+
pathbuf_to_string(next.as_ref().map(Dir::path), opts.absolute, dirs.on_dirs).to_string()
9090
}
9191
}
9292

93-
fn pathbuf_to_string(path: Option<&Path>, absolute: bool) -> String {
93+
pub(crate) fn pathbuf_to_string(path: Option<&Path>, absolute: bool, on_dirs: bool) -> String {
9494
match path {
9595
Some(p) => {
9696
if absolute {
@@ -99,7 +99,11 @@ fn pathbuf_to_string(path: Option<&Path>, absolute: bool) -> String {
9999
.to_string_lossy()
100100
.to_string()
101101
} else {
102-
p.to_string_lossy().to_string()
102+
if on_dirs && !p.is_absolute() {
103+
format!("../{}", p.to_string_lossy())
104+
} else {
105+
p.to_string_lossy().to_string()
106+
}
103107
}
104108
}
105109
None => String::new(),

lib/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ rand = "0.10.0"
1616
rust-embed = "8.5.0"
1717
clap = { version = "4.5.23", features = ["derive"] }
1818
log = "0.4.29"
19+
20+
[dev-dependencies]
21+
env_logger = "0.11.8"

lib/src/lib.rs

Lines changed: 65 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ pub struct Dirs {
104104
entries: Vec<PathBuf>,
105105
/// The parent directory that contains all the sibling directories.
106106
parent: PathBuf,
107+
/// Flag indicating whether the current directory is among the sibling directories.
108+
pub on_dirs: bool,
107109
/// The index of the current directory in the `dirs` vector.
108110
current: usize,
109111
}
@@ -167,7 +169,7 @@ impl Dirs {
167169
///
168170
/// # Returns
169171
///
170-
/// A [`Result`]<[`Dirs`]> instance.
172+
/// A [`Result`]<[`Dirs`], [`Error`]> instance.
171173
///
172174
/// # Errors
173175
///
@@ -202,34 +204,41 @@ impl Dirs {
202204
/// Create a new [`Dirs`] instance from the given file.
203205
/// The file should contain a list of directories, one per line.
204206
/// The first line can optionally specify the parent directory in the format `parent:/path/to`.
207+
/// If the current directory is not found in the list, the current index is set to 0.
208+
/// If `all_target` parameter is false, non-existent directories are skipped and
209+
/// the resultant list does not include them.
205210
///
206211
/// # Returns
207-
/// A [`Result`]<[`Dirs`]> instance.
212+
/// A [`Result`]<[`Dirs`], [`Error`]> instance.
208213
///
209214
/// # Errors
210215
///
211216
/// - Returns [`Error::Io`] if an I/O error occurs.
212217
/// - Returns [`Error::NotFile`] if the given path is not a file.
213218
/// - Returns [`Error::NotFound`] if the given path does not exist.
214-
pub fn new_from_file<S: AsRef<str>>(file: S) -> Result<Self> {
219+
pub fn new_from_file_with<S: AsRef<str>>(file: S, all_target: bool) -> Result<Self> {
215220
log::debug!("Dirs::new_from_file(file={})", file.as_ref());
216221
let file = file.as_ref();
217222
if file == "-" {
218223
log::info!("Reading directories from stdin");
219-
return Ok(build_from_reader(Box::new(std::io::stdin().lock())));
224+
return Ok(build_from_reader(Box::new(std::io::stdin().lock()), all_target));
220225
}
221226
let path = PathBuf::from(file);
222227
if !path.exists() {
223-
log::error!("Dirs::new_from_file: Not found: {}", path.display());
228+
log::error!("Dirs::new_from_file: Not found: {}, pwd: {}", path.display(), std::env::current_dir().unwrap().display());
224229
Err(Error::NotFound(path))
225230
} else if path.is_dir() {
226231
log::error!("Dirs::new_from_file: Not a file: {}", path.display());
227232
Err(Error::NotFile(path))
228233
} else {
229-
build_from_list(&path)
234+
build_from_list(&path, all_target)
230235
}
231236
}
232237

238+
pub fn new_from_file<S: AsRef<str>>(file: S) -> Result<Self> {
239+
Dirs::new_from_file_with(file, false)
240+
}
241+
233242
/// Get the parent directory path.
234243
#[must_use]
235244
pub fn parent(&self) -> &Path {
@@ -281,20 +290,12 @@ fn build_dirs(parent: Option<&Path>, current: PathBuf) -> Result<Dirs> {
281290
let mut errs = vec![];
282291
let dirs = collect_dirs(parent, &mut errs);
283292
if errs.is_empty() {
284-
let current_index = find_current(&dirs, &current);
285-
let index = if current_index == -1 {
286-
log::warn!(
287-
"build_dirs: current directory not found in siblings: {}",
288-
current.display()
289-
);
290-
0
291-
} else {
292-
usize::try_from(current_index).unwrap()
293-
};
293+
let (index, on_dirs) = find_current(&dirs, &current);
294294
log::info!("build_dirs: siblings={}, current_index={index}", dirs.len());
295295
Ok(Dirs {
296296
entries: dirs,
297297
parent: parent.to_path_buf(),
298+
on_dirs: on_dirs,
298299
current: index,
299300
})
300301
} else {
@@ -330,61 +331,77 @@ fn collect_dirs(parent: &Path, errs: &mut Vec<Error>) -> Vec<PathBuf> {
330331
}
331332

332333
/// Return the index of current in dirs, or 0 if not found.
333-
fn find_current(dirs: &[PathBuf], current: &PathBuf) -> i32 {
334+
fn find_current(dirs: &[PathBuf], current: &PathBuf) -> (usize, bool) {
334335
let idx = dirs
335336
.iter()
336-
.position(|dir| dir == current)
337-
.map_or(-1, |i| i32::try_from(i).unwrap());
338-
log::trace!("find_current: index={} for {}", idx, current.display());
339-
idx
337+
.position(|dir| dir == current);
338+
if let Some(index) = idx {
339+
log::trace!("find_current: found current at index {index}");
340+
(index, true)
341+
} else {
342+
log::warn!("find_current: current directory not found in siblings");
343+
(0, false)
344+
}
340345
}
341346

342347
/// Parse lines from a reader; parent: sets base, remaining lines are directory entries.
343-
fn build_from_reader(reader: Box<dyn BufRead>) -> Dirs {
344-
let lines = reader
345-
.lines()
346-
.filter_map(|line| line.map(|n| n.trim().to_string()).ok())
347-
.collect::<Vec<String>>();
348-
let base = if let Some(base) = lines.iter().find(|l| l.starts_with("parent:")) {
349-
base.chars().skip(7).collect::<String>().trim().to_string()
350-
} else {
351-
".".to_string()
352-
};
353-
let dirs = lines
354-
.iter()
355-
.filter(|l| !l.starts_with("parent:"))
356-
.map(PathBuf::from)
357-
.collect::<Vec<PathBuf>>();
358-
log::debug!("build_from_reader: base='{}', entries={}", base, dirs.len());
359-
let current = find_current_dir_index(&dirs);
360-
if current == 0 {
361-
log::warn!("build_from_reader: current directory not found in siblings");
348+
fn build_from_reader(reader: Box<dyn BufRead>, all_target: bool) -> Dirs {
349+
let mut parent = String::from(".");
350+
let mut lines = vec![];
351+
for line in reader.lines() {
352+
if let Ok(line) = line {
353+
if line.starts_with("parent:") {
354+
parent = line.chars().skip("parent:".len()).collect::<String>().trim().to_string();
355+
} else {
356+
let p = Path::new(line.trim());
357+
if !all_target && not_exists(p) {
358+
continue;
359+
} else {
360+
lines.push(p.to_path_buf());
361+
}
362+
}
363+
}
364+
}
365+
log::debug!("build_from_reader: base='{}', entries={}", parent, lines.len());
366+
let (current, on_dirs) = find_current_dir_index(&lines);
367+
if !on_dirs {
368+
log::debug!("build_from_reader: current directory not found in siblings");
362369
}
363370
Dirs {
364-
entries: dirs,
365-
parent: PathBuf::from(base),
371+
entries: lines,
372+
parent: PathBuf::from(parent),
373+
on_dirs,
366374
current,
367375
}
368376
}
369377

370-
fn find_current_dir_index(dirs: &[PathBuf]) -> usize {
378+
fn not_exists(path: &Path) -> bool {
379+
if path.exists() {
380+
false
381+
} else {
382+
let sibling_p = Path::new("..").join(path);
383+
!sibling_p.exists()
384+
}
385+
}
386+
387+
fn find_current_dir_index(dirs: &[PathBuf]) -> (usize, bool) {
371388
log::trace!("find_current_dir_index(dirs.len={})", dirs.len());
372389
if let Ok(pwd) = std::env::current_dir() {
373390
let cwd = PathBuf::from(".");
374391
if let Some(pos) = dirs
375392
.iter()
376393
.position(|dir| dir == &cwd || pwd.ends_with(dir))
377394
{
378-
return pos;
395+
return (pos, true);
379396
}
380397
}
381-
0
398+
(0, false)
382399
}
383400

384-
fn build_from_list(filename: &Path) -> Result<Dirs> {
401+
fn build_from_list(filename: &Path, all_target: bool) -> Result<Dirs> {
385402
if let Ok(f) = std::fs::File::open(filename) {
386403
let reader = BufReader::new(f);
387-
Ok(build_from_reader(Box::new(reader)))
404+
Ok(build_from_reader(Box::new(reader), all_target))
388405
} else {
389406
log::error!("build_from_list: I/O error: {}", filename.display());
390407
Err(Error::Io(std::io::Error::last_os_error()))
@@ -548,7 +565,7 @@ mod tests {
548565
let dirs = dirs.unwrap();
549566
assert_eq!(dirs.len(), 4);
550567
assert_eq!(dirs.current, 1);
551-
assert_eq!(dirs.parent, PathBuf::from("testdata/basic"));
568+
assert_eq!(dirs.parent, PathBuf::from("../testdata/basic"));
552569
}
553570

554571
#[test]

lib/tests/common/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pub(super) fn init() {
2+
if std::env::var_os("RUST_LOG").is_none() {
3+
unsafe {
4+
std::env::set_var("RUST_LOG", "debug");
5+
}
6+
}
7+
let _ = env_logger::builder().is_test(true).try_init();
8+
}

lib/tests/skip_non_existent_dir.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use std::path::Path;
2+
3+
mod common;
4+
5+
#[test]
6+
pub fn test_skip_non_existent_dir() {
7+
common::init();
8+
let _ = std::env::set_current_dir(Path::new("../testdata/basic"));
9+
let dirs = sibling::Dirs::new_from_file("../skip.txt")
10+
.expect("Failed to create Dirs from skip.txt");
11+
assert_eq!(dirs.len(), 2);
12+
13+
let dir = dirs.next(
14+
sibling::NexterFactory::create(sibling::NexterType::Next).as_ref(),
15+
).expect("Failed to get next directory");
16+
assert_eq!(dir.path().to_string_lossy(), "worried");
17+
}
18+
19+
#[test]
20+
pub fn test_skip_non_existent_dir2() {
21+
common::init();
22+
let _ = std::env::set_current_dir(Path::new("../testdata/basic"));
23+
let dirs = sibling::Dirs::new_from_file_with("../skip.txt", true)
24+
.expect("Failed to create Dirs from skip.txt");
25+
assert_eq!(dirs.len(), 4);
26+
27+
let dir = dirs.next(
28+
sibling::NexterFactory::create(sibling::NexterType::Previous).as_ref(),
29+
).expect("Failed to get next directory");
30+
assert_eq!(dir.path().to_string_lossy(), "unknown_dir");
31+
}

testdata/basic/dirlist.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
testdata/basic/a
1+
../testdata/basic/a
22
.
3-
testdata/basic/b
4-
testdata/basic/c
5-
parent:testdata/basic
3+
../testdata/basic/b
4+
../testdata/basic/c
5+
parent:../testdata/basic

testdata/demo/.init_demo.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | fold -w 8 | head -n 100 | xargs mkdir
2+
\ls | shuf | head -5 > target.txt

0 commit comments

Comments
 (0)