summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-03-18 22:49:50 +0800
committer魏曹先生 <1992414357@qq.com>2026-03-18 22:49:50 +0800
commit5372793a49567dcba7315bf8e7bc5a1cab2d5a76 (patch)
tree96d13d527835c23b978eae470e54a1d5fd15bc6d
parent2609dfe338b9bace6ff74c5efc93f684ba55a44e (diff)
Add support for reading from stdin and improve error messages
-rw-r--r--Cargo.lock23
-rw-r--r--Cargo.toml1
-rw-r--r--resources/locales/jvn/en.yml8
-rw-r--r--resources/locales/jvn/zh-CN.yml10
-rw-r--r--src/bin/jvn.rs50
-rw-r--r--src/cmds/arg/single_file.rs2
-rw-r--r--src/cmds/cmd/hexdump.rs21
-rw-r--r--src/systems/cmd/cmd_system.rs3
8 files changed, 102 insertions, 16 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8c8e15a..51796ef 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -176,6 +176,17 @@ dependencies = [
]
[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1036,6 +1047,15 @@ dependencies = [
]
[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "hex_display"
version = "0.1.0"
@@ -1211,6 +1231,7 @@ dependencies = [
name = "just_enough_vcs_cli"
version = "0.0.0"
dependencies = [
+ "atty",
"chrono",
"clap",
"cli_utils",
@@ -1602,6 +1623,8 @@ dependencies = [
name = "protocol"
version = "0.1.0"
dependencies = [
+ "constants",
+ "dirs",
"framework",
"serde",
"sheet_system",
diff --git a/Cargo.toml b/Cargo.toml
index 9a957ec..6a41f1a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -67,6 +67,7 @@ log.workspace = true
serde.workspace = true
thiserror.workspace = true
+atty = "0.2"
chrono = "0.4"
clap = { version = "4.5", features = ["derive"] }
colored.workspace = true
diff --git a/resources/locales/jvn/en.yml b/resources/locales/jvn/en.yml
index f118566..46cd0c5 100644
--- a/resources/locales/jvn/en.yml
+++ b/resources/locales/jvn/en.yml
@@ -65,7 +65,7 @@ prepare_error:
error: |
Unknown error in preparation phase!
- Error: %{error}
+ %{error}
local_workspace_not_found: |
Local workspace not found!
@@ -105,7 +105,7 @@ execute_error:
error: |
Error in execution phase!
- Error: %{error}
+ %{error}
render_error:
io: |
@@ -114,11 +114,11 @@ render_error:
error: |
Error in rendering phase!
- Error: %{error}
+ %{error}
serialize_failed: |
Data serialization error!
- Error: %{error}
+ %{error}
renderer_not_found: |
Renderer `%{renderer_name}` not found!
diff --git a/resources/locales/jvn/zh-CN.yml b/resources/locales/jvn/zh-CN.yml
index 8a7bdea..37f45a7 100644
--- a/resources/locales/jvn/zh-CN.yml
+++ b/resources/locales/jvn/zh-CN.yml
@@ -44,8 +44,8 @@ prepare_error:
%{error}
error: |
- 命令在准备阶段发生未知错误!
- 错误信息:%{error}
+ 命令在准备阶段发生错误!
+ %{error}
local_workspace_not_found: |
无法找到本地工作区!
@@ -84,7 +84,7 @@ execute_error:
error: |
命令在运行阶段发生错误!
- 错误信息:%{error}
+ %{error}
render_error:
io: |
@@ -93,11 +93,11 @@ render_error:
error: |
命令在渲染阶段发生错误!
- 错误信息:%{error}
+ %{error}
serialize_failed: |
数据在序列化时发生了错误!
- 错误信息:%{error}
+ %{error}
renderer_not_found: |
无法找到渲染器 `%{renderer_name}`!
diff --git a/src/bin/jvn.rs b/src/bin/jvn.rs
index 062eab8..fd01431 100644
--- a/src/bin/jvn.rs
+++ b/src/bin/jvn.rs
@@ -1,4 +1,8 @@
-use std::{ops::Deref, process::exit};
+use std::{
+ ops::Deref,
+ path::{Path, PathBuf},
+ process::exit,
+};
use cli_utils::legacy::{display::md, env::current_locales, levenshtein_distance};
use just_enough_vcs_cli::{
@@ -23,6 +27,7 @@ use just_progress::{
};
use log::{LevelFilter, error, info, trace, warn};
use rust_i18n::{set_locale, t};
+use tokio::io::AsyncReadExt;
rust_i18n::i18n!("resources/locales/jvn", fallback = "en");
@@ -115,6 +120,18 @@ async fn main() {
info!("{}", t!("verbose.user_input", command = args.join(" ")));
+ // Read pipe inpuit
+ let (stdin_path, stdin_data) = match read_all_from_stdin().await {
+ Ok((path, data)) => {
+ if data.is_empty() {
+ (None, None)
+ } else {
+ (path, Some(data))
+ }
+ }
+ Err(_) => (None, None),
+ };
+
// Build process future
let args_clone = args.clone();
let process_future = jv_cmd_process(
@@ -124,6 +141,8 @@ async fn main() {
confirmed,
args: args.clone(),
lang,
+ stdin_path,
+ stdin_data,
},
renderer_override,
);
@@ -198,6 +217,35 @@ async fn main() {
}
}
+/// Read path or raw information from standard input
+async fn read_all_from_stdin() -> tokio::io::Result<(Option<PathBuf>, Vec<u8>)> {
+ if atty::is(atty::Stream::Stdin) {
+ return Ok((None, Vec::new()));
+ }
+
+ let mut stdin = tokio::io::stdin();
+ let mut buffer = Vec::new();
+
+ stdin.read_to_end(&mut buffer).await?;
+
+ if buffer.is_empty() {
+ return Ok((None, Vec::new()));
+ }
+
+ let path = if let Ok(input_str) = String::from_utf8(buffer.clone()) {
+ let trimmed = input_str.trim();
+ if !trimmed.is_empty() && Path::new(trimmed).exists() {
+ Some(PathBuf::from(trimmed))
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ Ok((path, buffer))
+}
+
fn handle_no_matching_command_error(args: Vec<String>) {
let mut similar_nodes: Vec<String> = Vec::new();
for node in jv_cmd_nodes() {
diff --git a/src/cmds/arg/single_file.rs b/src/cmds/arg/single_file.rs
index 4ed9715..42927fc 100644
--- a/src/cmds/arg/single_file.rs
+++ b/src/cmds/arg/single_file.rs
@@ -4,5 +4,5 @@ use clap::Parser;
#[derive(Parser, Debug)]
pub struct JVSingleFileArgument {
- pub file: PathBuf,
+ pub file: Option<PathBuf>,
}
diff --git a/src/cmds/cmd/hexdump.rs b/src/cmds/cmd/hexdump.rs
index 346fffe..34df45c 100644
--- a/src/cmds/cmd/hexdump.rs
+++ b/src/cmds/cmd/hexdump.rs
@@ -1,9 +1,12 @@
use crate::{
cmd_output,
cmds::{
- arg::single_file::JVSingleFileArgument, collect::single_file::JVSingleFileCollect,
- r#in::empty::JVEmptyInput, out::hex::JVHexOutput,
+ arg::single_file::JVSingleFileArgument,
+ collect::single_file::JVSingleFileCollect,
+ r#in::empty::JVEmptyInput,
+ out::{hex::JVHexOutput, none::JVNoneOutput},
},
+ early_cmd_output,
systems::{
cmd::{
cmd_system::{AnyOutput, JVCommandContext},
@@ -30,9 +33,17 @@ async fn prepare(_args: &Arg, _ctx: &JVCommandContext) -> Result<In, CmdPrepareE
Ok(In {})
}
-async fn collect(args: &Arg, _ctx: &JVCommandContext) -> Result<Collect, CmdPrepareError> {
- let file = &args.file;
- let data = fs::read(file).await?;
+async fn collect(args: &Arg, ctx: &JVCommandContext) -> Result<Collect, CmdPrepareError> {
+ let data = if let Some(ref stdin_path) = ctx.stdin_path {
+ fs::read(stdin_path).await?
+ } else if let Some(ref stdin_data) = ctx.stdin_data {
+ stdin_data.clone()
+ } else if let Some(path) = &args.file {
+ fs::read(&path).await?
+ } else {
+ // No path input, exit early
+ return early_cmd_output!(JVNoneOutput => JVNoneOutput);
+ };
Ok(Collect { data })
}
diff --git a/src/systems/cmd/cmd_system.rs b/src/systems/cmd/cmd_system.rs
index 43d5187..a89842b 100644
--- a/src/systems/cmd/cmd_system.rs
+++ b/src/systems/cmd/cmd_system.rs
@@ -12,6 +12,7 @@ use std::{
any::{TypeId, type_name},
collections::HashMap,
future::Future,
+ path::PathBuf,
};
pub type AnyOutput = (Box<dyn std::any::Any + Send + 'static>, TypeId);
@@ -21,6 +22,8 @@ pub struct JVCommandContext {
pub confirmed: bool,
pub args: Vec<String>,
pub lang: String,
+ pub stdin_path: Option<PathBuf>,
+ pub stdin_data: Option<Vec<u8>>,
}
pub trait JVCommand<Argument, Input, Collect>