From 508505a96c3679e41018104c0bea358ebeef90c8 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Sun, 22 Sep 2024 13:16:23 -0400
Subject: [PATCH 01/24] Add support for test
---
Cargo.lock | 93 ++++++++++++++++++++++++++++++
crates/shell/Cargo.toml | 3 +-
crates/shell/src/commands/mod.rs | 6 ++
crates/shell/src/commands/touch.rs | 31 ++++++++++
crates/tests/file.txt | 0
crates/tests/src/lib.rs | 10 ++++
crates/tests/src/test_builder.rs | 2 +
7 files changed, 144 insertions(+), 1 deletion(-)
create mode 100644 crates/shell/src/commands/touch.rs
create mode 100644 crates/tests/file.txt
diff --git a/Cargo.lock b/Cargo.lock
index 8a68334..38a5458 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,6 +17,15 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "android-tzdata"
version = "0.1.1"
@@ -392,6 +401,18 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "filetime"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "libredox",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "futures"
version = "0.3.30"
@@ -625,6 +646,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags",
"libc",
+ "redox_syscall",
]
[[package]]
@@ -695,6 +717,12 @@ dependencies = [
"syn",
]
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
[[package]]
name = "miniz_oxide"
version = "0.8.0"
@@ -737,6 +765,16 @@ dependencies = [
"libc",
]
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
[[package]]
name = "nu-ansi-term"
version = "0.49.0"
@@ -830,6 +868,17 @@ dependencies = [
"windows-targets 0.52.6",
]
+[[package]]
+name = "parse_datetime"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8720474e3dd4af20cea8716703498b9f3b690f318fa9d9d9e2e38eaf44b96d0"
+dependencies = [
+ "chrono",
+ "nom",
+ "regex",
+]
+
[[package]]
name = "path-dedot"
version = "3.1.1"
@@ -988,6 +1037,35 @@ dependencies = [
"thiserror",
]
+[[package]]
+name = "regex"
+version = "1.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@@ -1108,6 +1186,7 @@ dependencies = [
"rustyline",
"tokio",
"uu_ls",
+ "uu_touch",
"uu_uname",
"which",
]
@@ -1366,6 +1445,20 @@ dependencies = [
"uutils_term_grid",
]
+[[package]]
+name = "uu_touch"
+version = "0.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07698cd67da84b138369eeed24bee77c860b8b22021581a56edd31c4697dd61d"
+dependencies = [
+ "chrono",
+ "clap",
+ "filetime",
+ "parse_datetime",
+ "uucore",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "uu_uname"
version = "0.0.27"
diff --git a/crates/shell/Cargo.toml b/crates/shell/Cargo.toml
index 78797fe..48795ec 100644
--- a/crates/shell/Cargo.toml
+++ b/crates/shell/Cargo.toml
@@ -33,7 +33,8 @@ uu_ls = "0.0.27"
dirs = "5.0.1"
which = "6.0.3"
uu_uname = "0.0.27"
+uu_touch = "0.0.27"
[package.metadata.release]
# Dont publish the binary
-release = false
\ No newline at end of file
+release = false
diff --git a/crates/shell/src/commands/mod.rs b/crates/shell/src/commands/mod.rs
index 810485b..e7361ba 100644
--- a/crates/shell/src/commands/mod.rs
+++ b/crates/shell/src/commands/mod.rs
@@ -8,9 +8,11 @@ use uu_ls::uumain as uu_ls;
use crate::execute;
pub mod uname;
+pub mod touch;
pub mod which;
pub use uname::UnameCommand;
+pub use touch::TouchCommand;
pub use which::WhichCommand;
pub struct LsCommand;
@@ -44,6 +46,10 @@ pub fn get_commands() -> HashMap> {
"uname".to_string(),
Rc::new(UnameCommand) as Rc,
),
+ (
+ "touch".to_string(),
+ Rc::new(TouchCommand) as Rc,
+ ),
])
}
diff --git a/crates/shell/src/commands/touch.rs b/crates/shell/src/commands/touch.rs
new file mode 100644
index 0000000..1388493
--- /dev/null
+++ b/crates/shell/src/commands/touch.rs
@@ -0,0 +1,31 @@
+use std::ffi::OsString;
+
+use deno_task_shell::{ExecuteResult, ShellCommand, ShellCommandContext};
+use futures::future::LocalBoxFuture;
+use uu_touch::uumain as uu_touch;
+
+pub struct TouchCommand;
+
+impl ShellCommand for TouchCommand {
+ fn execute(&self, mut context: ShellCommandContext) -> LocalBoxFuture<'static, ExecuteResult> {
+ Box::pin(futures::future::ready(match execute_touch(&mut context) {
+ Ok(_) => ExecuteResult::from_exit_code(0),
+ Err(exit_code) => ExecuteResult::from_exit_code(exit_code),
+ }))
+ }
+}
+
+fn execute_touch(context: &mut ShellCommandContext) -> Result<(), i32> {
+ let mut args: Vec = vec![OsString::from("touch")];
+
+ context
+ .args
+ .iter()
+ .for_each(|arg| args.push(OsString::from(arg)));
+
+ let exit_code = uu_touch(args.into_iter());
+ if exit_code != 0 {
+ return Err(exit_code);
+ }
+ Ok(())
+}
\ No newline at end of file
diff --git a/crates/tests/file.txt b/crates/tests/file.txt
new file mode 100644
index 0000000..e69de29
diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs
index 7eb8367..9764cf1 100644
--- a/crates/tests/src/lib.rs
+++ b/crates/tests/src/lib.rs
@@ -870,6 +870,16 @@ async fn arithmetic() {
.await;
}
+#[tokio::test]
+async fn touch() {
+ TestBuilder::new()
+ .command("touch file.txt")
+ .assert_exists("file.txt")
+ .run()
+ .await;
+
+}
+
#[cfg(test)]
fn no_such_file_error_text() -> &'static str {
if cfg!(windows) {
diff --git a/crates/tests/src/test_builder.rs b/crates/tests/src/test_builder.rs
index 4b1ad3c..d5e06bb 100644
--- a/crates/tests/src/test_builder.rs
+++ b/crates/tests/src/test_builder.rs
@@ -256,6 +256,8 @@ impl TestBuilder {
for assertion in &self.assertions {
match assertion {
TestAssertion::FileExists(path) => {
+ println!("path: {}", path);
+ println!("cwd: {:?}", cwd.join(path));
assert!(
cwd.join(path).exists(),
"\n\nFailed for: {}\nExpected '{}' to exist.",
From 404c90f9067d22531f6b1d9a3f50b2bf250feb4d Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Sun, 22 Sep 2024 14:08:57 -0400
Subject: [PATCH 02/24] Added tests
---
crates/shell/src/commands/mod.rs | 4 ++--
crates/shell/src/commands/touch.rs | 30 ++++++++++++++++++++++++++++--
crates/tests/file.txt | 0
crates/tests/src/lib.rs | 30 ++++++++++++++++++++++++++++++
crates/tests/src/test_builder.rs | 2 --
5 files changed, 60 insertions(+), 6 deletions(-)
delete mode 100644 crates/tests/file.txt
diff --git a/crates/shell/src/commands/mod.rs b/crates/shell/src/commands/mod.rs
index e7361ba..9694cb2 100644
--- a/crates/shell/src/commands/mod.rs
+++ b/crates/shell/src/commands/mod.rs
@@ -7,12 +7,12 @@ use uu_ls::uumain as uu_ls;
use crate::execute;
-pub mod uname;
pub mod touch;
+pub mod uname;
pub mod which;
-pub use uname::UnameCommand;
pub use touch::TouchCommand;
+pub use uname::UnameCommand;
pub use which::WhichCommand;
pub struct LsCommand;
diff --git a/crates/shell/src/commands/touch.rs b/crates/shell/src/commands/touch.rs
index 1388493..fa626e9 100644
--- a/crates/shell/src/commands/touch.rs
+++ b/crates/shell/src/commands/touch.rs
@@ -1,4 +1,4 @@
-use std::ffi::OsString;
+use std::{ffi::OsString, path::Path};
use deno_task_shell::{ExecuteResult, ShellCommand, ShellCommandContext};
use futures::future::LocalBoxFuture;
@@ -23,9 +23,35 @@ fn execute_touch(context: &mut ShellCommandContext) -> Result<(), i32> {
.iter()
.for_each(|arg| args.push(OsString::from(arg)));
+ let mut new_args = Vec::new();
+ let mut skip_next = false;
+
+ for (index, arg) in args[1..].iter().enumerate() {
+ if skip_next {
+ skip_next = false;
+ continue;
+ }
+
+ if arg.to_str().map_or(false, |s| s == "-t" || s == "-d") && index + 1 < args[1..].len() {
+ new_args.push(arg.clone());
+ new_args.push(args[index + 2].clone());
+ skip_next = true;
+ } else if !arg.to_str().map_or(false, |s| s.starts_with('-')) {
+ new_args.push(if Path::new(arg).is_absolute() {
+ arg.clone()
+ } else {
+ context.state.cwd().join(arg).into_os_string()
+ });
+ } else {
+ new_args.push(arg.clone());
+ }
+ }
+
+ args.splice(1.., new_args);
+
let exit_code = uu_touch(args.into_iter());
if exit_code != 0 {
return Err(exit_code);
}
Ok(())
-}
\ No newline at end of file
+}
diff --git a/crates/tests/file.txt b/crates/tests/file.txt
deleted file mode 100644
index e69de29..0000000
diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs
index 9764cf1..210506b 100644
--- a/crates/tests/src/lib.rs
+++ b/crates/tests/src/lib.rs
@@ -878,6 +878,36 @@ async fn touch() {
.run()
.await;
+ TestBuilder::new()
+ .command("touch -m file.txt")
+ .assert_exists("file.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch -c nonexistent.txt")
+ .assert_not_exists("nonexistent.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch file1.txt file2.txt")
+ .assert_exists("file1.txt")
+ .assert_exists("file2.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch -t 202401010000.00 timestamp.txt")
+ .assert_exists("timestamp.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch file.txt && touch -r file.txt reference.txt")
+ .assert_exists("reference.txt")
+ .run()
+ .await;
}
#[cfg(test)]
diff --git a/crates/tests/src/test_builder.rs b/crates/tests/src/test_builder.rs
index d5e06bb..4b1ad3c 100644
--- a/crates/tests/src/test_builder.rs
+++ b/crates/tests/src/test_builder.rs
@@ -256,8 +256,6 @@ impl TestBuilder {
for assertion in &self.assertions {
match assertion {
TestAssertion::FileExists(path) => {
- println!("path: {}", path);
- println!("cwd: {:?}", cwd.join(path));
assert!(
cwd.join(path).exists(),
"\n\nFailed for: {}\nExpected '{}' to exist.",
From ad2bf31acc58f5177c0cae9ea42aaa3f32933d34 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Sun, 22 Sep 2024 14:16:12 -0400
Subject: [PATCH 03/24] run fmt
---
crates/shell/src/commands/mod.rs | 2 +-
crates/tests/src/lib.rs | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/crates/shell/src/commands/mod.rs b/crates/shell/src/commands/mod.rs
index d2ea317..86f16db 100644
--- a/crates/shell/src/commands/mod.rs
+++ b/crates/shell/src/commands/mod.rs
@@ -12,8 +12,8 @@ pub mod touch;
pub mod uname;
pub mod which;
-pub use touch::TouchCommand;
pub use date::DateCommand;
+pub use touch::TouchCommand;
pub use uname::UnameCommand;
pub use which::WhichCommand;
diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs
index 1d9497d..66c62e0 100644
--- a/crates/tests/src/lib.rs
+++ b/crates/tests/src/lib.rs
@@ -878,8 +878,8 @@ async fn date() {
.check_stdout(false)
.run()
.await;
-
- TestBuilder::new()
+
+ TestBuilder::new()
.command("date +%Y-%m-%d")
.assert_exit_code(0)
.check_stdout(false)
@@ -895,7 +895,7 @@ async fn touch() {
.check_stdout(false)
.run()
.await;
-
+
TestBuilder::new()
.command("touch -m file.txt")
.assert_exists("file.txt")
From 4fa753e8f88d86029387296fdc111c7af22daee9 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Mon, 23 Sep 2024 23:17:58 -0400
Subject: [PATCH 04/24] Moved touch stuff to shell side
---
Cargo.lock | 83 +++++++-
crates/shell/Cargo.toml | 4 +
crates/shell/src/commands/touch.rs | 305 ++++++++++++++++++++++++++---
3 files changed, 353 insertions(+), 39 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 3ccb10a..f4d5a55 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -282,6 +282,20 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "datetime"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c3f7a77f3e57fedf80e09136f2d8777ebf621207306f6d96d610af048354bc"
+dependencies = [
+ "iso8601",
+ "libc",
+ "locale",
+ "pad",
+ "redox_syscall 0.1.57",
+ "winapi",
+]
+
[[package]]
name = "deno_task_shell"
version = "0.17.0"
@@ -509,7 +523,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
- "version_check",
+ "version_check 0.9.5",
]
[[package]]
@@ -602,6 +616,15 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+[[package]]
+name = "iso8601"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43e86914a73535f3f541a765adea0a9fafcf53fa6adb73662c4988fd9233766f"
+dependencies = [
+ "nom 4.2.3",
+]
+
[[package]]
name = "itertools"
version = "0.13.0"
@@ -646,7 +669,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags",
"libc",
- "redox_syscall",
+ "redox_syscall 0.5.4",
]
[[package]]
@@ -655,6 +678,15 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+[[package]]
+name = "locale"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "lock_api"
version = "0.4.12"
@@ -765,6 +797,16 @@ dependencies = [
"libc",
]
+[[package]]
+name = "nom"
+version = "4.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
+dependencies = [
+ "memchr",
+ "version_check 0.1.5",
+]
+
[[package]]
name = "nom"
version = "7.1.3"
@@ -845,6 +887,15 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56"
+[[package]]
+name = "pad"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3"
+dependencies = [
+ "unicode-width",
+]
+
[[package]]
name = "parking_lot"
version = "0.12.3"
@@ -863,7 +914,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall",
+ "redox_syscall 0.5.4",
"smallvec",
"windows-targets 0.52.6",
]
@@ -875,7 +926,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8720474e3dd4af20cea8716703498b9f3b690f318fa9d9d9e2e38eaf44b96d0"
dependencies = [
"chrono",
- "nom",
+ "nom 7.1.3",
"regex",
]
@@ -1017,6 +1068,12 @@ dependencies = [
"nibble_vec",
]
+[[package]]
+name = "redox_syscall"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+
[[package]]
name = "redox_syscall"
version = "0.5.4"
@@ -1180,10 +1237,14 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
+ "datetime",
"deno_task_shell",
"dirs",
+ "filetime",
"futures",
+ "miette",
"rustyline",
+ "thiserror",
"tokio",
"uu_date",
"uu_ls",
@@ -1325,18 +1386,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.63"
+version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.63"
+version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
@@ -1533,6 +1594,12 @@ dependencies = [
"ansi-width",
]
+[[package]]
+name = "version_check"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
+
[[package]]
name = "version_check"
version = "0.9.5"
diff --git a/crates/shell/Cargo.toml b/crates/shell/Cargo.toml
index 9a5d699..c651bdd 100644
--- a/crates/shell/Cargo.toml
+++ b/crates/shell/Cargo.toml
@@ -35,6 +35,10 @@ which = "6.0.3"
uu_uname = "0.0.27"
uu_touch = "0.0.27"
uu_date = "0.0.27"
+miette = { version = "7.2.0", features = ["fancy"] }
+thiserror = "1.0.64"
+filetime = "0.2.25"
+datetime = "0.5.2"
[package.metadata.release]
# Dont publish the binary
diff --git a/crates/shell/src/commands/touch.rs b/crates/shell/src/commands/touch.rs
index fa626e9..b243ebb 100644
--- a/crates/shell/src/commands/touch.rs
+++ b/crates/shell/src/commands/touch.rs
@@ -1,8 +1,12 @@
-use std::{ffi::OsString, path::Path};
+use std::{ffi::OsString, fs::{self, File}, path::{Path, PathBuf}};
use deno_task_shell::{ExecuteResult, ShellCommand, ShellCommandContext};
use futures::future::LocalBoxFuture;
-use uu_touch::uumain as uu_touch;
+use uu_touch::{uu_app as uu_touch, options};
+use miette::{miette, Result, IntoDiagnostic};
+use filetime::{set_file_times, set_symlink_file_times, FileTime};
+
+static ARG_FILES: &str = "files";
pub struct TouchCommand;
@@ -10,48 +14,287 @@ impl ShellCommand for TouchCommand {
fn execute(&self, mut context: ShellCommandContext) -> LocalBoxFuture<'static, ExecuteResult> {
Box::pin(futures::future::ready(match execute_touch(&mut context) {
Ok(_) => ExecuteResult::from_exit_code(0),
- Err(exit_code) => ExecuteResult::from_exit_code(exit_code),
+ Err(e) => {
+ let _ = context.stderr.write_all(format!("{:#}", e).as_bytes());
+ ExecuteResult::from_exit_code(1)
+ },
}))
}
}
-fn execute_touch(context: &mut ShellCommandContext) -> Result<(), i32> {
- let mut args: Vec = vec![OsString::from("touch")];
+fn execute_touch(context: &mut ShellCommandContext) -> Result<()> {
+ let matches = uu_touch().try_get_matches_from(context.args.clone()).into_diagnostic()?;
- context
- .args
- .iter()
- .for_each(|arg| args.push(OsString::from(arg)));
+ let files = match matches.get_many::(ARG_FILES) {
+ Some(files) => files.map(|file| {
+ let path = PathBuf::from(file);
+ if path.is_absolute() {
+ path
+ } else {
+ context.state.cwd().join(path)
+ }
+ }),
+ None => {
+ return Err(miette!("missing file operand\nTry 'touch --help' for more information."));
+ }
+ };
- let mut new_args = Vec::new();
- let mut skip_next = false;
+ let (atime, mtime) = match (
+ matches.get_one::(options::sources::REFERENCE),
+ matches.get_one::(options::sources::DATE),
+ ) {
+ (Some(reference), Some(date)) => {
+ let (atime, mtime) = stat(Path::new(&reference), !matches.get_flag(options::NO_DEREF))?;
+ let atime = filetime_to_datetime(&atime).ok_or_else(|| {
+ miette!("Could not process the reference access time")
+ })?;
+ let mtime = filetime_to_datetime(&mtime).ok_or_else(|| {
+ miette!("Could not process the reference modification time")
+ })?;
+ Ok((parse_date(atime, date)?, parse_date(mtime, date)?))
+ }
+ (Some(reference), None) => {
+ stat(Path::new(&reference), !matches.get_flag(options::NO_DEREF))
+ }
+ (None, Some(date)) => {
+ let timestamp = parse_date(Local::now(), date)?;
+ Ok((timestamp, timestamp))
+ }
+ (None, None) => {
+ let timestamp = if let Some(ts) = matches.get_one::(options::sources::TIMESTAMP)
+ {
+ parse_timestamp(ts)?
+ } else {
+ datetime_to_filetime(&Local::now())
+ };
+ Ok((timestamp, timestamp))
+ }
+ }.map_err(|e| miette!("{}", e))?;
+
+ for filename in files {
+ // FIXME: find a way to avoid having to clone the path
+ let pathbuf = if filename.to_string_lossy() == "-" {
+ pathbuf_from_stdout()
+ .into_diagnostic()?
+ } else {
+ PathBuf::from(filename)
+ };
+
+ let path = pathbuf.as_path();
+
+ let metadata_result = if matches.get_flag(options::NO_DEREF) {
+ path.symlink_metadata()
+ } else {
+ path.metadata()
+ };
- for (index, arg) in args[1..].iter().enumerate() {
- if skip_next {
- skip_next = false;
- continue;
+ if let Err(e) = metadata_result {
+ if e.kind() != std::io::ErrorKind::NotFound {
+ return Err(miette!("setting times of {}: {}", filename.to_string_lossy(), e));
+ }
+
+ if matches.get_flag(options::NO_CREATE) {
+ continue;
+ }
+
+ if matches.get_flag(options::NO_DEREF) {
+ context.stderr.write_all(format!("setting times of {}: No such file or directory", filename.to_string_lossy()).as_bytes())
+ .map_err(|e| miette!("Failed to write to stderr: {}", e))?;
+ continue;
+ }
+
+ File::create(path)
+ .into_diagnostic()
+ .map_err(|e| miette!("cannot touch {}: {}", path.display(), e))?;
+
+ // Minor optimization: if no reference time was specified, we're done.
+ if !matches.contains_id(options::SOURCES) {
+ continue;
+ }
}
- if arg.to_str().map_or(false, |s| s == "-t" || s == "-d") && index + 1 < args[1..].len() {
- new_args.push(arg.clone());
- new_args.push(args[index + 2].clone());
- skip_next = true;
- } else if !arg.to_str().map_or(false, |s| s.starts_with('-')) {
- new_args.push(if Path::new(arg).is_absolute() {
- arg.clone()
- } else {
- context.state.cwd().join(arg).into_os_string()
- });
+ if matches.get_flag(options::ACCESS)
+ || matches.get_flag(options::MODIFICATION)
+ || matches.contains_id(options::TIME)
+ {
+ let st = stat(path, !matches.get_flag(options::NO_DEREF))?;
+ let time = matches
+ .get_one::(options::TIME)
+ .map(|s| s.as_str())
+ .unwrap_or("");
+
+ if !(matches.get_flag(options::ACCESS)
+ || time.contains(&"access".to_owned())
+ || time.contains(&"atime".to_owned())
+ || time.contains(&"use".to_owned()))
+ {
+ atime = st.0;
+ }
+
+ if !(matches.get_flag(options::MODIFICATION)
+ || time.contains(&"modify".to_owned())
+ || time.contains(&"mtime".to_owned()))
+ {
+ mtime = st.1;
+ }
+ }
+
+ // sets the file access and modification times for a file or a symbolic link.
+ // The filename, access time (atime), and modification time (mtime) are provided as inputs.
+
+ // If the filename is not "-", indicating a special case for touch -h -,
+ // the code checks if the NO_DEREF flag is set, which means the user wants to
+ // set the times for a symbolic link itself, rather than the file it points to.
+ if filename.to_string_lossy() == "-" {
+ filetime::set_file_times(path, atime, mtime)
+ } else if matches.get_flag(options::NO_DEREF) {
+ set_symlink_file_times(path, atime, mtime)
} else {
- new_args.push(arg.clone());
+ set_file_times(path, atime, mtime)
+ }
+ .map_err(|e| miette!("setting times of {}: {}", path.display(), e))?;
}
+
+ Ok(())
+}
+
+fn stat(path: &Path, follow: bool) -> Result<(FileTime, FileTime)> {
+ let metadata = if follow {
+ fs::metadata(path).or_else(|_| fs::symlink_metadata(path))
+ } else {
+ fs::symlink_metadata(path)
}
+ .map_err(|e| miette!("failed to get attributes of {}: {}", path.display(), e))?;
+
+ Ok((
+ FileTime::from_last_access_time(&metadata),
+ FileTime::from_last_modification_time(&metadata),
+ ))
+}
+
+fn filetime_to_datetime(ft: &FileTime) -> Option> {
+ Some(DateTime::from_timestamp(ft.unix_seconds(), ft.nanoseconds())?.into())
+}
+
+fn parse_timestamp(s: &str) -> Result {
+ let current_year = || Local::now().year();
+
+ let (format, ts) = match s.chars().count() {
+ 15 => (YYYYMMDDHHMM_DOT_SS, s.to_owned()),
+ 12 => (YYYYMMDDHHMM, s.to_owned()),
+ // If we don't add "20", we have insufficient information to parse
+ 13 => (YYYYMMDDHHMM_DOT_SS, format!("20{}", s)),
+ 10 => (YYYYMMDDHHMM, format!("20{}", s)),
+ 11 => (YYYYMMDDHHMM_DOT_SS, format!("{}{}", current_year(), s)),
+ 8 => (YYYYMMDDHHMM, format!("{}{}", current_year(), s)),
+ _ => {
+ return Err(miette!("invalid date format '{}'", s));
+ }
+ };
- args.splice(1.., new_args);
+ let local = NaiveDateTime::parse_from_str(&ts, format)
+ .map_err(|_| miette!("invalid date ts format '{}'", s))?;
+ let mut local = match chrono::Local.from_local_datetime(&local) {
+ LocalResult::Single(dt) => dt,
+ _ => {
+ return Err(miette!(
+ "invalid date ts format '{}'", s,
+ ))
+ }
+ };
- let exit_code = uu_touch(args.into_iter());
- if exit_code != 0 {
- return Err(exit_code);
+ // Chrono caps seconds at 59, but 60 is valid. It might be a leap second
+ // or wrap to the next minute. But that doesn't really matter, because we
+ // only care about the timestamp anyway.
+ // Tested in gnu/tests/touch/60-seconds
+ if local.second() == 59 && ts.ends_with(".60") {
+ local += Duration::try_seconds(1).unwrap();
+ }
+
+ // Due to daylight saving time switch, local time can jump from 1:59 AM to
+ // 3:00 AM, in which case any time between 2:00 AM and 2:59 AM is not
+ // valid. If we are within this jump, chrono takes the offset from before
+ // the jump. If we then jump forward an hour, we get the new corrected
+ // offset. Jumping back will then now correctly take the jump into account.
+ let local2 = local + Duration::try_hours(1).unwrap() - Duration::try_hours(1).unwrap();
+ if local.hour() != local2.hour() {
+ return Err(miette!(
+ "invalid date format '{}'", s,
+ ));
+ }
+
+ let local = FileTime::from_unix_time(local.timestamp(), local.timestamp_subsec_nanos());
+ Ok(local)
+}
+
+// TODO: this may be a good candidate to put in fsext.rs
+/// Returns a PathBuf to stdout.
+///
+/// On Windows, uses GetFinalPathNameByHandleW to attempt to get the path
+/// from the stdout handle.
+fn pathbuf_from_stdout() -> Result {
+ #[cfg(all(unix, not(target_os = "android")))]
+ {
+ Ok(PathBuf::from("/dev/stdout"))
+ }
+ #[cfg(target_os = "android")]
+ {
+ Ok(PathBuf::from("/proc/self/fd/1"))
+ }
+ #[cfg(windows)]
+ {
+ use std::os::windows::prelude::AsRawHandle;
+ use windows_sys::Win32::Foundation::{
+ GetLastError, ERROR_INVALID_PARAMETER, ERROR_NOT_ENOUGH_MEMORY, ERROR_PATH_NOT_FOUND,
+ HANDLE, MAX_PATH,
+ };
+ use windows_sys::Win32::Storage::FileSystem::{
+ GetFinalPathNameByHandleW, FILE_NAME_OPENED,
+ };
+
+ let handle = std::io::stdout().lock().as_raw_handle() as HANDLE;
+ let mut file_path_buffer: [u16; MAX_PATH as usize] = [0; MAX_PATH as usize];
+
+ // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlea#examples
+ // SAFETY: We transmute the handle to be able to cast *mut c_void into a
+ // HANDLE (i32) so rustc will let us call GetFinalPathNameByHandleW. The
+ // reference example code for GetFinalPathNameByHandleW implies that
+ // it is safe for us to leave lpszfilepath uninitialized, so long as
+ // the buffer size is correct. We know the buffer size (MAX_PATH) at
+ // compile time. MAX_PATH is a small number (260) so we can cast it
+ // to a u32.
+ let ret = unsafe {
+ GetFinalPathNameByHandleW(
+ handle,
+ file_path_buffer.as_mut_ptr(),
+ file_path_buffer.len() as u32,
+ FILE_NAME_OPENED,
+ )
+ };
+
+ let buffer_size = match ret {
+ ERROR_PATH_NOT_FOUND | ERROR_NOT_ENOUGH_MEMORY | ERROR_INVALID_PARAMETER => {
+ return Err(USimpleError::new(
+ 1,
+ format!("GetFinalPathNameByHandleW failed with code {ret}"),
+ ))
+ }
+ 0 => {
+ return Err(USimpleError::new(
+ 1,
+ format!(
+ "GetFinalPathNameByHandleW failed with code {}",
+ // SAFETY: GetLastError is thread-safe and has no documented memory unsafety.
+ unsafe { GetLastError() }
+ ),
+ ));
+ }
+ e => e as usize,
+ };
+
+ // Don't include the null terminator
+ Ok(String::from_utf16(&file_path_buffer[0..buffer_size])
+ .map_err(|e| USimpleError::new(1, e.to_string()))?
+ .into())
}
- Ok(())
}
From 74ecc51a81426cff3008cc695662033fce27ed6b Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Thu, 26 Sep 2024 02:00:57 -0400
Subject: [PATCH 05/24] Redo testing by moving functions on touch.rs to shell
---
Cargo.lock | 33 ++++++
crates/shell/Cargo.toml | 3 +
crates/shell/src/commands/touch.rs | 179 ++++++++++++++++++-----------
crates/tests/src/lib.rs | 46 +++++++-
4 files changed, 189 insertions(+), 72 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index f4d5a55..0e7eac9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -105,6 +105,12 @@ version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
[[package]]
name = "ascii_tree"
version = "0.1.1"
@@ -197,7 +203,9 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
"android-tzdata",
"iana-time-zone",
+ "js-sys",
"num-traits",
+ "wasm-bindgen",
"windows-targets 0.52.6",
]
@@ -358,6 +366,18 @@ dependencies = [
"windows-sys 0.48.0",
]
+[[package]]
+name = "dtparse"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23fb403c0926d35af2cc54d961bc2696a10d40725c08360ef69db04a4c201fd7"
+dependencies = [
+ "chrono",
+ "lazy_static",
+ "num-traits",
+ "rust_decimal",
+]
+
[[package]]
name = "dunce"
version = "1.0.5"
@@ -1123,6 +1143,16 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+[[package]]
+name = "rust_decimal"
+version = "1.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555"
+dependencies = [
+ "arrayvec",
+ "num-traits",
+]
+
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@@ -1236,13 +1266,16 @@ name = "shell"
version = "0.1.0"
dependencies = [
"anyhow",
+ "chrono",
"clap",
"datetime",
"deno_task_shell",
"dirs",
+ "dtparse",
"filetime",
"futures",
"miette",
+ "parse_datetime",
"rustyline",
"thiserror",
"tokio",
diff --git a/crates/shell/Cargo.toml b/crates/shell/Cargo.toml
index c651bdd..b79d6bf 100644
--- a/crates/shell/Cargo.toml
+++ b/crates/shell/Cargo.toml
@@ -39,6 +39,9 @@ miette = { version = "7.2.0", features = ["fancy"] }
thiserror = "1.0.64"
filetime = "0.2.25"
datetime = "0.5.2"
+chrono = "0.4.38"
+parse_datetime = "0.6.0"
+dtparse = "2.0.1"
[package.metadata.release]
# Dont publish the binary
diff --git a/crates/shell/src/commands/touch.rs b/crates/shell/src/commands/touch.rs
index b243ebb..91a6499 100644
--- a/crates/shell/src/commands/touch.rs
+++ b/crates/shell/src/commands/touch.rs
@@ -1,10 +1,15 @@
-use std::{ffi::OsString, fs::{self, File}, path::{Path, PathBuf}};
+use std::{
+ ffi::OsString,
+ fs::{self, File},
+ path::{Path, PathBuf},
+};
+use chrono::{DateTime, Duration, Local, NaiveDateTime, TimeZone, Timelike};
use deno_task_shell::{ExecuteResult, ShellCommand, ShellCommandContext};
-use futures::future::LocalBoxFuture;
-use uu_touch::{uu_app as uu_touch, options};
-use miette::{miette, Result, IntoDiagnostic};
use filetime::{set_file_times, set_symlink_file_times, FileTime};
+use futures::future::LocalBoxFuture;
+use miette::{miette, IntoDiagnostic, Result};
+use uu_touch::{options, uu_app as uu_touch};
static ARG_FILES: &str = "files";
@@ -17,13 +22,17 @@ impl ShellCommand for TouchCommand {
Err(e) => {
let _ = context.stderr.write_all(format!("{:#}", e).as_bytes());
ExecuteResult::from_exit_code(1)
- },
+ }
}))
}
}
fn execute_touch(context: &mut ShellCommandContext) -> Result<()> {
- let matches = uu_touch().try_get_matches_from(context.args.clone()).into_diagnostic()?;
+ let matches = uu_touch()
+ .override_usage("touch [OPTION]...")
+ .no_binary_name(true)
+ .try_get_matches_from(&context.args)
+ .into_diagnostic()?;
let files = match matches.get_many::(ARG_FILES) {
Some(files) => files.map(|file| {
@@ -35,26 +44,38 @@ fn execute_touch(context: &mut ShellCommandContext) -> Result<()> {
}
}),
None => {
- return Err(miette!("missing file operand\nTry 'touch --help' for more information."));
+ return Err(miette!(
+ "missing file operand\nTry 'touch --help' for more information."
+ ))
}
};
- let (atime, mtime) = match (
+ let (mut atime, mut mtime) = match (
matches.get_one::(options::sources::REFERENCE),
matches.get_one::(options::sources::DATE),
) {
(Some(reference), Some(date)) => {
- let (atime, mtime) = stat(Path::new(&reference), !matches.get_flag(options::NO_DEREF))?;
- let atime = filetime_to_datetime(&atime).ok_or_else(|| {
- miette!("Could not process the reference access time")
- })?;
- let mtime = filetime_to_datetime(&mtime).ok_or_else(|| {
- miette!("Could not process the reference modification time")
- })?;
+ let reference_path = PathBuf::from(reference);
+ let reference_path = if reference_path.is_absolute() {
+ reference_path
+ } else {
+ context.state.cwd().join(reference_path)
+ };
+ let (atime, mtime) = stat(&reference_path, !matches.get_flag(options::NO_DEREF))?;
+ let atime = filetime_to_datetime(&atime)
+ .ok_or_else(|| miette!("Could not process the reference access time"))?;
+ let mtime = filetime_to_datetime(&mtime)
+ .ok_or_else(|| miette!("Could not process the reference modification time"))?;
Ok((parse_date(atime, date)?, parse_date(mtime, date)?))
}
(Some(reference), None) => {
- stat(Path::new(&reference), !matches.get_flag(options::NO_DEREF))
+ let reference_path = PathBuf::from(reference);
+ let reference_path = if reference_path.is_absolute() {
+ reference_path
+ } else {
+ context.state.cwd().join(reference_path)
+ };
+ stat(&reference_path, !matches.get_flag(options::NO_DEREF))
}
(None, Some(date)) => {
let timestamp = parse_date(Local::now(), date)?;
@@ -69,15 +90,14 @@ fn execute_touch(context: &mut ShellCommandContext) -> Result<()> {
};
Ok((timestamp, timestamp))
}
- }.map_err(|e| miette!("{}", e))?;
+ }
+ .map_err(|e| miette!("{}", e))?;
for filename in files {
- // FIXME: find a way to avoid having to clone the path
- let pathbuf = if filename.to_string_lossy() == "-" {
- pathbuf_from_stdout()
- .into_diagnostic()?
+ let pathbuf = if filename.to_str() == Some("-") {
+ pathbuf_from_stdout()?
} else {
- PathBuf::from(filename)
+ filename
};
let path = pathbuf.as_path();
@@ -90,7 +110,7 @@ fn execute_touch(context: &mut ShellCommandContext) -> Result<()> {
if let Err(e) = metadata_result {
if e.kind() != std::io::ErrorKind::NotFound {
- return Err(miette!("setting times of {}: {}", filename.to_string_lossy(), e));
+ return Err(miette!("setting times of {}: {}", path.display(), e));
}
if matches.get_flag(options::NO_CREATE) {
@@ -98,8 +118,13 @@ fn execute_touch(context: &mut ShellCommandContext) -> Result<()> {
}
if matches.get_flag(options::NO_DEREF) {
- context.stderr.write_all(format!("setting times of {}: No such file or directory", filename.to_string_lossy()).as_bytes())
- .map_err(|e| miette!("Failed to write to stderr: {}", e))?;
+ let _ = context.stderr.write_all(
+ format!(
+ "setting times of {:?}: No such file or directory",
+ path.display()
+ )
+ .as_bytes(),
+ );
continue;
}
@@ -145,15 +170,15 @@ fn execute_touch(context: &mut ShellCommandContext) -> Result<()> {
// If the filename is not "-", indicating a special case for touch -h -,
// the code checks if the NO_DEREF flag is set, which means the user wants to
// set the times for a symbolic link itself, rather than the file it points to.
- if filename.to_string_lossy() == "-" {
- filetime::set_file_times(path, atime, mtime)
+ if path.to_string_lossy() == "-" {
+ set_file_times(path, atime, mtime)
} else if matches.get_flag(options::NO_DEREF) {
set_symlink_file_times(path, atime, mtime)
} else {
set_file_times(path, atime, mtime)
}
.map_err(|e| miette!("setting times of {}: {}", path.display(), e))?;
- }
+ }
Ok(())
}
@@ -177,54 +202,36 @@ fn filetime_to_datetime(ft: &FileTime) -> Option> {
}
fn parse_timestamp(s: &str) -> Result {
- let current_year = || Local::now().year();
-
- let (format, ts) = match s.chars().count() {
- 15 => (YYYYMMDDHHMM_DOT_SS, s.to_owned()),
- 12 => (YYYYMMDDHHMM, s.to_owned()),
- // If we don't add "20", we have insufficient information to parse
- 13 => (YYYYMMDDHHMM_DOT_SS, format!("20{}", s)),
- 10 => (YYYYMMDDHHMM, format!("20{}", s)),
- 11 => (YYYYMMDDHHMM_DOT_SS, format!("{}{}", current_year(), s)),
- 8 => (YYYYMMDDHHMM, format!("{}{}", current_year(), s)),
- _ => {
- return Err(miette!("invalid date format '{}'", s));
- }
+ let now = Local::now();
+ let parsed = if s.len() == 15 && s.contains('.') {
+ // Handle the specific format "202401010000.00"
+ NaiveDateTime::parse_from_str(s, "%Y%m%d%H%M.%S")
+ .map_err(|_| miette!("invalid date format '{}'", s))?
+ } else {
+ dtparse::parse(s)
+ .map(|(dt, _)| dt)
+ .map_err(|_| miette!("invalid date format '{}'", s))?
};
- let local = NaiveDateTime::parse_from_str(&ts, format)
- .map_err(|_| miette!("invalid date ts format '{}'", s))?;
- let mut local = match chrono::Local.from_local_datetime(&local) {
- LocalResult::Single(dt) => dt,
- _ => {
- return Err(miette!(
- "invalid date ts format '{}'", s,
- ))
- }
- };
+ let local = now
+ .timezone()
+ .from_local_datetime(&parsed)
+ .single()
+ .ok_or_else(|| miette!("invalid date '{}'", s))?;
- // Chrono caps seconds at 59, but 60 is valid. It might be a leap second
- // or wrap to the next minute. But that doesn't really matter, because we
- // only care about the timestamp anyway.
- // Tested in gnu/tests/touch/60-seconds
- if local.second() == 59 && ts.ends_with(".60") {
- local += Duration::try_seconds(1).unwrap();
- }
+ // Handle leap seconds
+ let local = if parsed.second() == 59 && s.ends_with(".60") {
+ local + Duration::seconds(1)
+ } else {
+ local
+ };
- // Due to daylight saving time switch, local time can jump from 1:59 AM to
- // 3:00 AM, in which case any time between 2:00 AM and 2:59 AM is not
- // valid. If we are within this jump, chrono takes the offset from before
- // the jump. If we then jump forward an hour, we get the new corrected
- // offset. Jumping back will then now correctly take the jump into account.
- let local2 = local + Duration::try_hours(1).unwrap() - Duration::try_hours(1).unwrap();
- if local.hour() != local2.hour() {
- return Err(miette!(
- "invalid date format '{}'", s,
- ));
+ // Check for daylight saving time issues
+ if (local + Duration::hours(1) - Duration::hours(1)).hour() != local.hour() {
+ return Err(miette!("invalid date '{}'", s));
}
- let local = FileTime::from_unix_time(local.timestamp(), local.timestamp_subsec_nanos());
- Ok(local)
+ Ok(datetime_to_filetime(&local))
}
// TODO: this may be a good candidate to put in fsext.rs
@@ -298,3 +305,35 @@ fn pathbuf_from_stdout() -> Result {
.into())
}
}
+
+fn parse_date(ref_time: DateTime, s: &str) -> Result {
+ // Using the dtparse crate for more robust date parsing
+
+ match dtparse::parse(s) {
+ Ok((naive_dt, offset)) => {
+ let dt = offset.map_or_else(
+ || Local.from_local_datetime(&naive_dt).unwrap(),
+ |off| DateTime::::from_naive_utc_and_offset(naive_dt, off),
+ );
+ Ok(datetime_to_filetime(&dt))
+ }
+ Err(_) => {
+ // Fallback to parsing Unix timestamp if dtparse fails
+ if let Some(stripped) = s.strip_prefix('@') {
+ stripped
+ .parse::()
+ .map(|ts| FileTime::from_unix_time(ts, 0))
+ .map_err(|_| miette!("Unable to parse date: {s}"))
+ } else {
+ // Use ref_time as a base for relative date parsing
+ parse_datetime::parse_datetime_at_date(ref_time, s)
+ .map(|dt| datetime_to_filetime(&dt))
+ .map_err(|_| miette!("Unable to parse date: {s}"))
+ }
+ }
+ }
+}
+
+fn datetime_to_filetime(dt: &DateTime) -> FileTime {
+ FileTime::from_unix_time(dt.timestamp(), dt.timestamp_subsec_nanos())
+}
diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs
index 66c62e0..821dc20 100644
--- a/crates/tests/src/lib.rs
+++ b/crates/tests/src/lib.rs
@@ -916,8 +916,50 @@ async fn touch() {
.await;
TestBuilder::new()
- .command("touch -t 202401010000.00 timestamp.txt")
- .assert_exists("timestamp.txt")
+ .command("touch -d 'Tue Feb 20 14:30:00 2024' posix_locale.txt")
+ .assert_exists("posix_locale.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch -d '2024-02-20' iso_8601.txt")
+ .assert_exists("iso_8601.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch -t 202402201430.00 yyyymmddhhmmss.txt")
+ .assert_exists("yyyymmddhhmmss.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch -d '2024-02-20 14:30:00.000000' yyyymmddhhmmss_ms.txt")
+ .assert_exists("yyyymmddhhmmss_ms.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch -d '2024-02-20 14:30:00' yyyymmddhhmms.txt")
+ .assert_exists("yyyymmddhhmms.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch -d '2024-02-20 14:30' yyyy_mm_dd_hh_mm.txt")
+ .assert_exists("yyyy_mm_dd_hh_mm.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch -t 202402201430 yyyymmddhhmm.txt")
+ .assert_exists("yyyymmddhhmm.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch -d '2024-02-20 14:30 +0000' yyyymmddhhmm_offset.txt")
+ .assert_exists("yyyymmddhhmm_offset.txt")
.run()
.await;
From 77a489b054fa7af4676c410eb80574e58d68b78d Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Thu, 26 Sep 2024 02:44:59 -0400
Subject: [PATCH 06/24] Increase coverage
---
Cargo.lock | 1 +
crates/shell/src/commands/touch.rs | 2 +-
crates/tests/Cargo.toml | 1 +
crates/tests/src/lib.rs | 58 ++++++++++++++++++++++++++----
crates/tests/src/test_builder.rs | 37 ++++++++++++++-----
5 files changed, 83 insertions(+), 16 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 0e7eac9..7d4aaf1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1399,6 +1399,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"deno_task_shell",
+ "dirs",
"futures",
"pretty_assertions",
"shell",
diff --git a/crates/shell/src/commands/touch.rs b/crates/shell/src/commands/touch.rs
index 91a6499..44b8bfb 100644
--- a/crates/shell/src/commands/touch.rs
+++ b/crates/shell/src/commands/touch.rs
@@ -20,7 +20,7 @@ impl ShellCommand for TouchCommand {
Box::pin(futures::future::ready(match execute_touch(&mut context) {
Ok(_) => ExecuteResult::from_exit_code(0),
Err(e) => {
- let _ = context.stderr.write_all(format!("{:#}", e).as_bytes());
+ let _ = context.stderr.write_all(format!("{:?}", e).as_bytes());
ExecuteResult::from_exit_code(1)
}
}))
diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml
index 6aa7577..b0215d1 100644
--- a/crates/tests/Cargo.toml
+++ b/crates/tests/Cargo.toml
@@ -9,6 +9,7 @@ shell = { path = "../shell" }
anyhow = "1.0.87"
futures = "0.3.30"
tokio = { version = "1.40.0", features = ["full"] }
+dirs = "5.0.1"
[dev-dependencies]
pretty_assertions = "1.0.0"
diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs
index 821dc20..175c1a0 100644
--- a/crates/tests/src/lib.rs
+++ b/crates/tests/src/lib.rs
@@ -776,7 +776,6 @@ async fn uname() {
TestBuilder::new()
.command("uname")
.assert_exit_code(0)
- .check_stderr(false)
.check_stdout(false)
.run()
.await;
@@ -939,12 +938,6 @@ async fn touch() {
.run()
.await;
- TestBuilder::new()
- .command("touch -d '2024-02-20 14:30:00' yyyymmddhhmms.txt")
- .assert_exists("yyyymmddhhmms.txt")
- .run()
- .await;
-
TestBuilder::new()
.command("touch -d '2024-02-20 14:30' yyyy_mm_dd_hh_mm.txt")
.assert_exists("yyyy_mm_dd_hh_mm.txt")
@@ -968,6 +961,57 @@ async fn touch() {
.assert_exists("reference.txt")
.run()
.await;
+ // Test for non-existent file with -c option
+ TestBuilder::new()
+ .command("touch -c nonexistent.txt")
+ .assert_not_exists("nonexistent.txt")
+ .run()
+ .await;
+
+ // Test for invalid date format
+ TestBuilder::new()
+ .command("touch -d 'invalid date' invalid_date.txt")
+ .assert_stderr_contains("Unable to parse date: invalid date\n")
+ .assert_exit_code(1)
+ .run()
+ .await;
+
+ // Test for invalid timestamp format
+ TestBuilder::new()
+ .command("touch -t 9999999999 invalid_timestamp.txt")
+ .assert_stderr_contains("invalid date format '9999999999'\n")
+ .assert_exit_code(1)
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch ~/absolute_path.txt")
+ .assert_exists("~/absolute_path.txt")
+ .run()
+ .await;
+
+ TestBuilder::new()
+ .command("touch /non_existent_dir/non_existent.txt")
+ .assert_stderr_contains("No such file or directory")
+ .assert_exit_code(1)
+ .run()
+ .await;
+
+ // Test with -h option on a symlink
+ TestBuilder::new()
+ .command("touch original.txt && ln -s original.txt symlink.txt && touch -h symlink.txt")
+ .assert_exists("symlink.txt")
+ .run()
+ .await;
+
+ // Test with multiple files, including one that doesn't exist
+ TestBuilder::new()
+ .command("touch existing.txt && touch existing.txt nonexistent.txt another_existing.txt")
+ .assert_exists("existing.txt")
+ .assert_exists("nonexistent.txt")
+ .assert_exists("another_existing.txt")
+ .run()
+ .await;
}
#[cfg(test)]
diff --git a/crates/tests/src/test_builder.rs b/crates/tests/src/test_builder.rs
index 4b1ad3c..122318c 100644
--- a/crates/tests/src/test_builder.rs
+++ b/crates/tests/src/test_builder.rs
@@ -65,6 +65,7 @@ pub struct TestBuilder {
expected_exit_code: i32,
expected_stderr: String,
expected_stdout: String,
+ expected_stderr_contains: String,
assertions: Vec,
assert_stdout: bool,
assert_stderr: bool,
@@ -101,9 +102,10 @@ impl TestBuilder {
expected_exit_code: 0,
expected_stderr: Default::default(),
expected_stdout: Default::default(),
+ expected_stderr_contains: Default::default(),
assertions: Default::default(),
assert_stdout: true,
- assert_stderr: true,
+ assert_stderr: false,
}
}
@@ -163,6 +165,15 @@ impl TestBuilder {
pub fn assert_stderr(&mut self, output: &str) -> &mut Self {
self.expected_stderr.push_str(output);
+ self.assert_stderr = true;
+ self.expected_stderr_contains.clear();
+ self
+ }
+
+ pub fn assert_stderr_contains(&mut self, output: &str) -> &mut Self {
+ self.expected_stderr_contains.push_str(output);
+ self.assert_stderr = false;
+ self.expected_stderr.clear();
self
}
@@ -176,11 +187,6 @@ impl TestBuilder {
self
}
- pub fn check_stderr(&mut self, check_stderr: bool) -> &mut Self {
- self.assert_stderr = check_stderr;
- self
- }
-
pub fn assert_exists(&mut self, path: &str) -> &mut Self {
self.ensure_temp_dir();
self.assertions
@@ -231,13 +237,21 @@ impl TestBuilder {
} else {
"NO_TEMP_DIR".to_string()
};
+ let stderr_output = stderr_handle.await.unwrap();
if self.assert_stderr {
assert_eq!(
- stderr_handle.await.unwrap(),
+ stderr_output,
self.expected_stderr.replace("$TEMP_DIR", &temp_dir),
"\n\nFailed for: {}",
self.command
);
+ } else if !self.expected_stderr_contains.is_empty() {
+ assert!(
+ stderr_output.contains(&self.expected_stderr_contains),
+ "\n\nFailed for: {}\nExpected stderr to contain: {}",
+ self.command,
+ self.expected_stderr_contains
+ );
}
if self.assert_stdout {
assert_eq!(
@@ -256,8 +270,15 @@ impl TestBuilder {
for assertion in &self.assertions {
match assertion {
TestAssertion::FileExists(path) => {
+ let path_to_check = if path.starts_with('/') {
+ PathBuf::from(path)
+ } else if path.starts_with("~/") {
+ dirs::home_dir().unwrap().join(path.strip_prefix("~/").unwrap())
+ } else {
+ cwd.join(path)
+ };
assert!(
- cwd.join(path).exists(),
+ path_to_check.exists(),
"\n\nFailed for: {}\nExpected '{}' to exist.",
self.command,
path,
From 135b3c67b7d8cab7a78aa077e7c398517a7f1f8f Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Thu, 26 Sep 2024 02:45:14 -0400
Subject: [PATCH 07/24] run fmt
---
crates/tests/src/test_builder.rs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/crates/tests/src/test_builder.rs b/crates/tests/src/test_builder.rs
index 122318c..c46fd3b 100644
--- a/crates/tests/src/test_builder.rs
+++ b/crates/tests/src/test_builder.rs
@@ -273,7 +273,9 @@ impl TestBuilder {
let path_to_check = if path.starts_with('/') {
PathBuf::from(path)
} else if path.starts_with("~/") {
- dirs::home_dir().unwrap().join(path.strip_prefix("~/").unwrap())
+ dirs::home_dir()
+ .unwrap()
+ .join(path.strip_prefix("~/").unwrap())
} else {
cwd.join(path)
};
From 3b97729bb6502f04606e8375c7ca0b66987d8380 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Thu, 26 Sep 2024 03:06:33 -0400
Subject: [PATCH 08/24] Remove extra crates
---
Cargo.lock | 73 +++--------------------------------------
crates/shell/Cargo.toml | 2 --
2 files changed, 4 insertions(+), 71 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 7d4aaf1..d31055a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -290,20 +290,6 @@ dependencies = [
"typenum",
]
-[[package]]
-name = "datetime"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44c3f7a77f3e57fedf80e09136f2d8777ebf621207306f6d96d610af048354bc"
-dependencies = [
- "iso8601",
- "libc",
- "locale",
- "pad",
- "redox_syscall 0.1.57",
- "winapi",
-]
-
[[package]]
name = "deno_task_shell"
version = "0.17.0"
@@ -543,7 +529,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
- "version_check 0.9.5",
+ "version_check",
]
[[package]]
@@ -636,15 +622,6 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
-[[package]]
-name = "iso8601"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43e86914a73535f3f541a765adea0a9fafcf53fa6adb73662c4988fd9233766f"
-dependencies = [
- "nom 4.2.3",
-]
-
[[package]]
name = "itertools"
version = "0.13.0"
@@ -689,7 +666,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags",
"libc",
- "redox_syscall 0.5.4",
+ "redox_syscall",
]
[[package]]
@@ -698,15 +675,6 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
-[[package]]
-name = "locale"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "lock_api"
version = "0.4.12"
@@ -817,16 +785,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "nom"
-version = "4.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
-dependencies = [
- "memchr",
- "version_check 0.1.5",
-]
-
[[package]]
name = "nom"
version = "7.1.3"
@@ -907,15 +865,6 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56"
-[[package]]
-name = "pad"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3"
-dependencies = [
- "unicode-width",
-]
-
[[package]]
name = "parking_lot"
version = "0.12.3"
@@ -934,7 +883,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall 0.5.4",
+ "redox_syscall",
"smallvec",
"windows-targets 0.52.6",
]
@@ -946,7 +895,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8720474e3dd4af20cea8716703498b9f3b690f318fa9d9d9e2e38eaf44b96d0"
dependencies = [
"chrono",
- "nom 7.1.3",
+ "nom",
"regex",
]
@@ -1088,12 +1037,6 @@ dependencies = [
"nibble_vec",
]
-[[package]]
-name = "redox_syscall"
-version = "0.1.57"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
-
[[package]]
name = "redox_syscall"
version = "0.5.4"
@@ -1268,7 +1211,6 @@ dependencies = [
"anyhow",
"chrono",
"clap",
- "datetime",
"deno_task_shell",
"dirs",
"dtparse",
@@ -1277,7 +1219,6 @@ dependencies = [
"miette",
"parse_datetime",
"rustyline",
- "thiserror",
"tokio",
"uu_date",
"uu_ls",
@@ -1628,12 +1569,6 @@ dependencies = [
"ansi-width",
]
-[[package]]
-name = "version_check"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
-
[[package]]
name = "version_check"
version = "0.9.5"
diff --git a/crates/shell/Cargo.toml b/crates/shell/Cargo.toml
index b79d6bf..327a47e 100644
--- a/crates/shell/Cargo.toml
+++ b/crates/shell/Cargo.toml
@@ -36,9 +36,7 @@ uu_uname = "0.0.27"
uu_touch = "0.0.27"
uu_date = "0.0.27"
miette = { version = "7.2.0", features = ["fancy"] }
-thiserror = "1.0.64"
filetime = "0.2.25"
-datetime = "0.5.2"
chrono = "0.4.38"
parse_datetime = "0.6.0"
dtparse = "2.0.1"
From 211b393fd67428aacd2c91de4e757ea7f7034412 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Thu, 26 Sep 2024 12:42:23 -0400
Subject: [PATCH 09/24] Add support for windows
---
crates/shell/Cargo.toml | 1 +
crates/shell/src/commands/touch.rs | 18 ++++++------------
2 files changed, 7 insertions(+), 12 deletions(-)
diff --git a/crates/shell/Cargo.toml b/crates/shell/Cargo.toml
index 327a47e..8fa58c1 100644
--- a/crates/shell/Cargo.toml
+++ b/crates/shell/Cargo.toml
@@ -40,6 +40,7 @@ filetime = "0.2.25"
chrono = "0.4.38"
parse_datetime = "0.6.0"
dtparse = "2.0.1"
+windows-sys = "0.59.0"
[package.metadata.release]
# Dont publish the binary
diff --git a/crates/shell/src/commands/touch.rs b/crates/shell/src/commands/touch.rs
index 44b8bfb..79b6ead 100644
--- a/crates/shell/src/commands/touch.rs
+++ b/crates/shell/src/commands/touch.rs
@@ -281,19 +281,13 @@ fn pathbuf_from_stdout() -> Result {
let buffer_size = match ret {
ERROR_PATH_NOT_FOUND | ERROR_NOT_ENOUGH_MEMORY | ERROR_INVALID_PARAMETER => {
- return Err(USimpleError::new(
- 1,
- format!("GetFinalPathNameByHandleW failed with code {ret}"),
- ))
+ return Err(miette!("GetFinalPathNameByHandleW failed with code {ret}"))
}
0 => {
- return Err(USimpleError::new(
- 1,
- format!(
- "GetFinalPathNameByHandleW failed with code {}",
- // SAFETY: GetLastError is thread-safe and has no documented memory unsafety.
- unsafe { GetLastError() }
- ),
+ return Err(miette!(
+ "GetFinalPathNameByHandleW failed with code {}",
+ // SAFETY: GetLastError is thread-safe and has no documented memory unsafety.
+ unsafe { GetLastError() }
));
}
e => e as usize,
@@ -301,7 +295,7 @@ fn pathbuf_from_stdout() -> Result {
// Don't include the null terminator
Ok(String::from_utf16(&file_path_buffer[0..buffer_size])
- .map_err(|e| USimpleError::new(1, e.to_string()))?
+ .map_err(|e| miette!("GetFinalPathNameByHandleW failed with code {ret}"))?
.into())
}
}
From 06bb7728921f3e772ec7ee9a25a3a6bd35895212 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Thu, 26 Sep 2024 12:42:49 -0400
Subject: [PATCH 10/24] Update lockfile
---
Cargo.lock | 1 +
1 file changed, 1 insertion(+)
diff --git a/Cargo.lock b/Cargo.lock
index d31055a..c36ed25 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1225,6 +1225,7 @@ dependencies = [
"uu_touch",
"uu_uname",
"which",
+ "windows-sys 0.59.0",
]
[[package]]
From 5368061788c57c573fcd10348113b5a3629a2a47 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Thu, 26 Sep 2024 12:48:30 -0400
Subject: [PATCH 11/24] Fix error message
---
crates/shell/src/commands/touch.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crates/shell/src/commands/touch.rs b/crates/shell/src/commands/touch.rs
index 79b6ead..4526229 100644
--- a/crates/shell/src/commands/touch.rs
+++ b/crates/shell/src/commands/touch.rs
@@ -295,7 +295,7 @@ fn pathbuf_from_stdout() -> Result {
// Don't include the null terminator
Ok(String::from_utf16(&file_path_buffer[0..buffer_size])
- .map_err(|e| miette!("GetFinalPathNameByHandleW failed with code {ret}"))?
+ .map_err(|e| miette!("Generated path is not valid UTF-16: {e}"))?
.into())
}
}
From 5de60018e0d248bc35b0836acd85fc4afadad060 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Thu, 26 Sep 2024 19:06:01 -0400
Subject: [PATCH 12/24] Update test
---
crates/tests/src/lib.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs
index 175c1a0..1b5dbc5 100644
--- a/crates/tests/src/lib.rs
+++ b/crates/tests/src/lib.rs
@@ -991,7 +991,7 @@ async fn touch() {
.await;
TestBuilder::new()
- .command("touch /non_existent_dir/non_existent.txt")
+ .command("touch ~/non_existent_dir/non_existent.txt")
.assert_stderr_contains("No such file or directory")
.assert_exit_code(1)
.run()
From 76c5c9a2b14d2721ccaa0d1228682b4a0503cdf7 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Fri, 27 Sep 2024 11:55:44 -0400
Subject: [PATCH 13/24] Using TEMPDIR for absolute path instead of ~
---
crates/tests/src/lib.rs | 6 +++---
crates/tests/src/test_builder.rs | 22 +++++++++++-----------
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs
index 1b5dbc5..8efe800 100644
--- a/crates/tests/src/lib.rs
+++ b/crates/tests/src/lib.rs
@@ -985,13 +985,13 @@ async fn touch() {
.await;
TestBuilder::new()
- .command("touch ~/absolute_path.txt")
- .assert_exists("~/absolute_path.txt")
+ .command("touch $TEMP_DIR/absolute_path.txt")
+ .assert_exists("$TEMP_DIR/absolute_path.txt")
.run()
.await;
TestBuilder::new()
- .command("touch ~/non_existent_dir/non_existent.txt")
+ .command("touch $TEMP_DIR/non_existent_dir/non_existent.txt")
.assert_stderr_contains("No such file or directory")
.assert_exit_code(1)
.run()
diff --git a/crates/tests/src/test_builder.rs b/crates/tests/src/test_builder.rs
index c46fd3b..14215d7 100644
--- a/crates/tests/src/test_builder.rs
+++ b/crates/tests/src/test_builder.rs
@@ -117,6 +117,8 @@ impl TestBuilder {
fn get_temp_dir(&mut self) -> &mut TempDir {
if self.temp_dir.is_none() {
self.temp_dir = Some(TempDir::new());
+ let temp_dir_string = self.temp_dir_path().display().to_string();
+ self.env_vars.insert("TEMP_DIR".to_string(), temp_dir_string);
}
self.temp_dir.as_mut().unwrap()
}
@@ -189,8 +191,13 @@ impl TestBuilder {
pub fn assert_exists(&mut self, path: &str) -> &mut Self {
self.ensure_temp_dir();
+ let temp_dir = if let Some(temp_dir) = &self.temp_dir {
+ temp_dir.cwd.display().to_string()
+ } else {
+ "NO_TEMP_DIR".to_string()
+ };
self.assertions
- .push(TestAssertion::FileExists(path.to_string()));
+ .push(TestAssertion::FileExists(path.to_string().replace("$TEMP_DIR", &temp_dir)));
self
}
@@ -247,7 +254,7 @@ impl TestBuilder {
);
} else if !self.expected_stderr_contains.is_empty() {
assert!(
- stderr_output.contains(&self.expected_stderr_contains),
+ stderr_output.contains(&self.expected_stderr_contains.replace("$TEMP_DIR", &temp_dir)),
"\n\nFailed for: {}\nExpected stderr to contain: {}",
self.command,
self.expected_stderr_contains
@@ -270,15 +277,8 @@ impl TestBuilder {
for assertion in &self.assertions {
match assertion {
TestAssertion::FileExists(path) => {
- let path_to_check = if path.starts_with('/') {
- PathBuf::from(path)
- } else if path.starts_with("~/") {
- dirs::home_dir()
- .unwrap()
- .join(path.strip_prefix("~/").unwrap())
- } else {
- cwd.join(path)
- };
+ let path_to_check = cwd.join(path);
+
assert!(
path_to_check.exists(),
"\n\nFailed for: {}\nExpected '{}' to exist.",
From 596aa66ac3e855d80b9244a1ffd373dd435dd947 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Fri, 27 Sep 2024 11:56:43 -0400
Subject: [PATCH 14/24] run fmt
---
crates/tests/src/test_builder.rs | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/crates/tests/src/test_builder.rs b/crates/tests/src/test_builder.rs
index 14215d7..8e4c73f 100644
--- a/crates/tests/src/test_builder.rs
+++ b/crates/tests/src/test_builder.rs
@@ -118,7 +118,8 @@ impl TestBuilder {
if self.temp_dir.is_none() {
self.temp_dir = Some(TempDir::new());
let temp_dir_string = self.temp_dir_path().display().to_string();
- self.env_vars.insert("TEMP_DIR".to_string(), temp_dir_string);
+ self.env_vars
+ .insert("TEMP_DIR".to_string(), temp_dir_string);
}
self.temp_dir.as_mut().unwrap()
}
@@ -191,13 +192,14 @@ impl TestBuilder {
pub fn assert_exists(&mut self, path: &str) -> &mut Self {
self.ensure_temp_dir();
- let temp_dir = if let Some(temp_dir) = &self.temp_dir {
+ let temp_dir = if let Some(temp_dir) = &self.temp_dir {
temp_dir.cwd.display().to_string()
} else {
"NO_TEMP_DIR".to_string()
};
- self.assertions
- .push(TestAssertion::FileExists(path.to_string().replace("$TEMP_DIR", &temp_dir)));
+ self.assertions.push(TestAssertion::FileExists(
+ path.to_string().replace("$TEMP_DIR", &temp_dir),
+ ));
self
}
@@ -254,7 +256,11 @@ impl TestBuilder {
);
} else if !self.expected_stderr_contains.is_empty() {
assert!(
- stderr_output.contains(&self.expected_stderr_contains.replace("$TEMP_DIR", &temp_dir)),
+ stderr_output.contains(
+ &self
+ .expected_stderr_contains
+ .replace("$TEMP_DIR", &temp_dir)
+ ),
"\n\nFailed for: {}\nExpected stderr to contain: {}",
self.command,
self.expected_stderr_contains
@@ -278,7 +284,7 @@ impl TestBuilder {
match assertion {
TestAssertion::FileExists(path) => {
let path_to_check = cwd.join(path);
-
+
assert!(
path_to_check.exists(),
"\n\nFailed for: {}\nExpected '{}' to exist.",
From af545de9ecdae41d36e0c83e00a3c40762da7cd9 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Fri, 27 Sep 2024 12:33:02 -0400
Subject: [PATCH 15/24] Using OpenOptions instead of File for file creation
---
crates/shell/src/commands/touch.rs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/crates/shell/src/commands/touch.rs b/crates/shell/src/commands/touch.rs
index 4526229..4384a84 100644
--- a/crates/shell/src/commands/touch.rs
+++ b/crates/shell/src/commands/touch.rs
@@ -1,6 +1,6 @@
use std::{
ffi::OsString,
- fs::{self, File},
+ fs::{self, OpenOptions},
path::{Path, PathBuf},
};
@@ -128,7 +128,7 @@ fn execute_touch(context: &mut ShellCommandContext) -> Result<()> {
continue;
}
- File::create(path)
+ OpenOptions::new().create(true).truncate(false).write(true).open(path)
.into_diagnostic()
.map_err(|e| miette!("cannot touch {}: {}", path.display(), e))?;
From 46166beb61cb061bc5073126d3fb410f5d854b1b Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Fri, 27 Sep 2024 12:33:21 -0400
Subject: [PATCH 16/24] run fmt
---
crates/shell/src/commands/touch.rs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/crates/shell/src/commands/touch.rs b/crates/shell/src/commands/touch.rs
index 4384a84..9561966 100644
--- a/crates/shell/src/commands/touch.rs
+++ b/crates/shell/src/commands/touch.rs
@@ -128,7 +128,11 @@ fn execute_touch(context: &mut ShellCommandContext) -> Result<()> {
continue;
}
- OpenOptions::new().create(true).truncate(false).write(true).open(path)
+ OpenOptions::new()
+ .create(true)
+ .truncate(false)
+ .write(true)
+ .open(path)
.into_diagnostic()
.map_err(|e| miette!("cannot touch {}: {}", path.display(), e))?;
From 07609d3028b8971575299ff8e9134c19df08f100 Mon Sep 17 00:00:00 2001
From: Parsa Bahraminejad
Date: Sat, 28 Sep 2024 19:07:06 -0400
Subject: [PATCH 17/24] Updated tests
---
crates/tests/src/lib.rs | 27 ++++++++++++++++++++++-----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs
index 8efe800..d4be7e6 100644
--- a/crates/tests/src/lib.rs
+++ b/crates/tests/src/lib.rs
@@ -227,9 +227,16 @@ async fn pipeline() {
.run()
.await;
+ // TODO: implement tee in shell and then enable this test
+ // TestBuilder::new()
+ // .command(r#"echo 1 | tee output.txt"#)
+ // .assert_stdout("1\n")
+ // .assert_file_equals("output.txt", "1\n")
+ // .run()
+ // .await;
+
TestBuilder::new()
- .command(r#"echo 1 | tee output.txt"#)
- .assert_stdout("1\n")
+ .command(r#"echo 1 | cat > output.txt"#)
.assert_file_equals("output.txt", "1\n")
.run()
.await;
@@ -990,6 +997,7 @@ async fn touch() {
.run()
.await;
+ #[cfg(not(windows))]
TestBuilder::new()
.command("touch $TEMP_DIR/non_existent_dir/non_existent.txt")
.assert_stderr_contains("No such file or directory")
@@ -997,13 +1005,22 @@ async fn touch() {
.run()
.await;
- // Test with -h option on a symlink
+ #[cfg(windows)]
TestBuilder::new()
- .command("touch original.txt && ln -s original.txt symlink.txt && touch -h symlink.txt")
- .assert_exists("symlink.txt")
+ .command("touch $TEMP_DIR/non_existent_dir/non_existent.txt")
+ .assert_stderr_contains("The system cannot find the path specified")
+ .assert_exit_code(1)
.run()
.await;
+ // TODO: implement ln in shell and then enable this test
+ // // Test with -h option on a symlink
+ // TestBuilder::new()
+ // .command("touch original.txt && ln -s original.txt symlink.txt && touch -h symlink.txt")
+ // .assert_exists("symlink.txt")
+ // .run()
+ // .await;
+
// Test with multiple files, including one that doesn't exist
TestBuilder::new()
.command("touch existing.txt && touch existing.txt nonexistent.txt another_existing.txt")
From a16399b0ffb4e1049a94646e49ae8744ef9704ec Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Sat, 28 Sep 2024 16:54:07 -0400
Subject: [PATCH 18/24] map error to print the same error on both platforms
---
crates/shell/src/commands/touch.rs | 13 +++++++++++--
crates/tests/src/lib.rs | 9 ---------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/crates/shell/src/commands/touch.rs b/crates/shell/src/commands/touch.rs
index 9561966..87c9481 100644
--- a/crates/shell/src/commands/touch.rs
+++ b/crates/shell/src/commands/touch.rs
@@ -1,6 +1,7 @@
use std::{
ffi::OsString,
fs::{self, OpenOptions},
+ io,
path::{Path, PathBuf},
};
@@ -133,8 +134,16 @@ fn execute_touch(context: &mut ShellCommandContext) -> Result<()> {
.truncate(false)
.write(true)
.open(path)
- .into_diagnostic()
- .map_err(|e| miette!("cannot touch {}: {}", path.display(), e))?;
+ .map_err(|e| match e.kind() {
+ io::ErrorKind::NotFound => {
+ miette!(
+ "cannot touch {}: {}",
+ path.display(),
+ "No such file or directory".to_string()
+ )
+ }
+ _ => miette!("cannot touch {}: {}", path.display(), e),
+ })?;
// Minor optimization: if no reference time was specified, we're done.
if !matches.contains_id(options::SOURCES) {
diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs
index d4be7e6..6b34db7 100644
--- a/crates/tests/src/lib.rs
+++ b/crates/tests/src/lib.rs
@@ -997,7 +997,6 @@ async fn touch() {
.run()
.await;
- #[cfg(not(windows))]
TestBuilder::new()
.command("touch $TEMP_DIR/non_existent_dir/non_existent.txt")
.assert_stderr_contains("No such file or directory")
@@ -1005,14 +1004,6 @@ async fn touch() {
.run()
.await;
- #[cfg(windows)]
- TestBuilder::new()
- .command("touch $TEMP_DIR/non_existent_dir/non_existent.txt")
- .assert_stderr_contains("The system cannot find the path specified")
- .assert_exit_code(1)
- .run()
- .await;
-
// TODO: implement ln in shell and then enable this test
// // Test with -h option on a symlink
// TestBuilder::new()
From 40c93374a7ba59c7afebf5980f54c473b99c8c20 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Sat, 28 Sep 2024 20:53:39 -0400
Subject: [PATCH 19/24] Add tmate for debugging
---
.github/workflows/rust-tests.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml
index 729109b..cf66557 100644
--- a/.github/workflows/rust-tests.yml
+++ b/.github/workflows/rust-tests.yml
@@ -28,6 +28,9 @@ jobs:
toolchain: stable
profile: minimal
override: true
+
+ - name: Setup tmate session
+ uses: mxschmitt/action-tmate@v3
- uses: Swatinem/rust-cache@v2
From c19fc12db3d1e228e8f89da7f03a15eb90326b34 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Sat, 28 Sep 2024 23:30:40 -0400
Subject: [PATCH 20/24] Update tmate github action
---
.github/workflows/rust-tests.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml
index cf66557..6706715 100644
--- a/.github/workflows/rust-tests.yml
+++ b/.github/workflows/rust-tests.yml
@@ -31,6 +31,8 @@ jobs:
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
+ with:
+ limit-access-to-actor: false
- uses: Swatinem/rust-cache@v2
From 8ffad011fae34d75b12cc5decd1a1b474ebdb730 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Sun, 29 Sep 2024 14:32:04 -0400
Subject: [PATCH 21/24] Add temp_dir correctly to test_builder
---
.github/workflows/rust-tests.yml | 5 -----
crates/deno_task_shell/Cargo.toml | 2 +-
crates/tests/src/test_builder.rs | 7 +++----
3 files changed, 4 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml
index 6706715..729109b 100644
--- a/.github/workflows/rust-tests.yml
+++ b/.github/workflows/rust-tests.yml
@@ -28,11 +28,6 @@ jobs:
toolchain: stable
profile: minimal
override: true
-
- - name: Setup tmate session
- uses: mxschmitt/action-tmate@v3
- with:
- limit-access-to-actor: false
- uses: Swatinem/rust-cache@v2
diff --git a/crates/deno_task_shell/Cargo.toml b/crates/deno_task_shell/Cargo.toml
index 8a3f909..655825e 100644
--- a/crates/deno_task_shell/Cargo.toml
+++ b/crates/deno_task_shell/Cargo.toml
@@ -28,7 +28,7 @@ pest = { git = "https://github.com/pest-parser/pest.git", branch = "master", fea
pest_derive = "2.7.12"
dirs = "5.0.1"
pest_ascii_tree = { git = "https://github.com/prsabahrami/pest_ascii_tree.git", branch = "master" }
-miette = "7.2.0"
+miette = { version = "7.2.0", features = ["fancy"] }
lazy_static = "1.4.0"
[dev-dependencies]
diff --git a/crates/tests/src/test_builder.rs b/crates/tests/src/test_builder.rs
index bf62348..7b21062 100644
--- a/crates/tests/src/test_builder.rs
+++ b/crates/tests/src/test_builder.rs
@@ -117,9 +117,6 @@ impl TestBuilder {
fn get_temp_dir(&mut self) -> &mut TempDir {
if self.temp_dir.is_none() {
self.temp_dir = Some(TempDir::new());
- let temp_dir_string = self.temp_dir_path().display().to_string();
- self.env_vars
- .insert("TEMP_DIR".to_string(), temp_dir_string);
}
self.temp_dir.as_mut().unwrap()
}
@@ -238,6 +235,7 @@ impl TestBuilder {
let (stderr, stderr_handle) = get_output_writer_and_handle();
let local_set = tokio::task::LocalSet::new();
+ self.env_var("TEMP_DIR", &cwd.display().to_string());
let state = ShellState::new(
self.env_vars.clone(),
&cwd,
@@ -266,8 +264,9 @@ impl TestBuilder {
.expected_stderr_contains
.replace("$TEMP_DIR", &temp_dir)
),
- "\n\nFailed for: {}\nExpected stderr to contain: {}",
+ "\n\nFailed for: {}\nExpected stderr to contain: {}, {}",
self.command,
+ stderr_output,
self.expected_stderr_contains
);
}
From 30c1fa12d8b5405b6d18d2759934764b85b20773 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Sun, 29 Sep 2024 15:53:10 -0400
Subject: [PATCH 22/24] Use NO_GRAPHICS for miette erros
---
crates/tests/src/test_builder.rs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/crates/tests/src/test_builder.rs b/crates/tests/src/test_builder.rs
index 7b21062..e5b63f2 100644
--- a/crates/tests/src/test_builder.rs
+++ b/crates/tests/src/test_builder.rs
@@ -222,6 +222,8 @@ impl TestBuilder {
}
pub async fn run(&mut self) {
+ std::env::set_var("NO_GRAPHICS", "1");
+
let list = parse(&self.command).unwrap();
let cwd = if let Some(temp_dir) = &self.temp_dir {
temp_dir.cwd.clone()
From 7e8d7cc0dfdcd717f97d021b70cedc5bac78daa5 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Sun, 29 Sep 2024 15:55:10 -0400
Subject: [PATCH 23/24] Remove extra print in assert
---
crates/tests/src/test_builder.rs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/crates/tests/src/test_builder.rs b/crates/tests/src/test_builder.rs
index e5b63f2..985aa0a 100644
--- a/crates/tests/src/test_builder.rs
+++ b/crates/tests/src/test_builder.rs
@@ -266,9 +266,8 @@ impl TestBuilder {
.expected_stderr_contains
.replace("$TEMP_DIR", &temp_dir)
),
- "\n\nFailed for: {}\nExpected stderr to contain: {}, {}",
+ "\n\nFailed for: {}\nExpected stderr to contain: {}",
self.command,
- stderr_output,
self.expected_stderr_contains
);
}
From 6936a2bc22f5df55841997cd8ac653bc43e19e00 Mon Sep 17 00:00:00 2001
From: prsabahrami
Date: Sun, 29 Sep 2024 17:18:01 -0400
Subject: [PATCH 24/24] Update duplicate in toml file
---
Cargo.lock | 1 -
crates/shell/Cargo.toml | 1 -
2 files changed, 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 5b316a5..b8ab949 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1239,7 +1239,6 @@ dependencies = [
"futures",
"miette",
"parse_datetime",
- "miette",
"rustyline",
"tokio",
"uu_date",
diff --git a/crates/shell/Cargo.toml b/crates/shell/Cargo.toml
index e54cd22..ee5449c 100644
--- a/crates/shell/Cargo.toml
+++ b/crates/shell/Cargo.toml
@@ -41,7 +41,6 @@ parse_datetime = "0.6.0"
dtparse = "2.0.1"
windows-sys = "0.59.0"
ctrlc = "3.4.5"
-miette = { version="7.2.0", features = ["fancy"] }
[package.metadata.release]
# Dont publish the binary