mirror of
https://github.com/jj-vcs/jj.git
synced 2025-12-23 06:01:01 +00:00
* `Some(x).unwrap()` is not necessary. * Add a new test to ensure that we handle the fix child process IO without deadlock. The new test introduces byte mode to fake-formatter which pipe the stdin contents to stdout byte by byte in a streaming fashion. The test contents are 512KB, which is larger than page size, can trigger the blocking read behavior, and doesn't make the test the slowest.
161 lines
4.9 KiB
Rust
161 lines
4.9 KiB
Rust
// Copyright 2024 The Jujutsu Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use std::fs::OpenOptions;
|
|
use std::io::Read as _;
|
|
use std::io::Write as _;
|
|
use std::path::PathBuf;
|
|
use std::process::ExitCode;
|
|
|
|
use bstr::ByteSlice as _;
|
|
use clap::Parser;
|
|
use itertools::Itertools as _;
|
|
|
|
/// A fake code formatter, useful for testing
|
|
///
|
|
/// `fake-formatter` is similar to `cat`.
|
|
/// `fake-formatter --reverse` is similar to `rev` (not `tac`).
|
|
/// `fake-formatter --stdout foo` is similar to `echo foo`.
|
|
/// `fake-formatter --stdout foo --stderr bar --fail` is similar to
|
|
/// `echo foo; echo bar >&2; false`.
|
|
/// `fake-formatter --tee foo` is similar to `tee foo`).
|
|
///
|
|
/// This program acts as a portable alternative to that class of shell commands.
|
|
#[derive(Parser, Debug)]
|
|
struct Args {
|
|
/// Exit with non-successful status.
|
|
#[arg(long, default_value_t = false)]
|
|
fail: bool,
|
|
|
|
/// Reverse the characters in each line when reading stdin.
|
|
#[arg(long, default_value_t = false)]
|
|
reverse: bool,
|
|
|
|
/// Convert all characters to uppercase when reading stdin.
|
|
#[arg(long, default_value_t = false)]
|
|
uppercase: bool,
|
|
|
|
/// Convert all characters to lowercase when reading stdin.
|
|
#[arg(long, default_value_t = false)]
|
|
lowercase: bool,
|
|
|
|
/// Adds a line to the end of the file
|
|
#[arg(long)]
|
|
append: Option<String>,
|
|
|
|
/// Write this string to stdout, and ignore stdin.
|
|
#[arg(long)]
|
|
stdout: Option<String>,
|
|
|
|
/// Write this string to stderr.
|
|
#[arg(long)]
|
|
stderr: Option<String>,
|
|
|
|
/// Duplicate stdout into this file.
|
|
#[arg(long)]
|
|
tee: Option<PathBuf>,
|
|
|
|
/// Read one byte at a time, and send one byte to the stdout before reading
|
|
/// the next byte.
|
|
#[arg(long, default_value_t = false)]
|
|
byte_mode: bool,
|
|
}
|
|
|
|
fn main() -> ExitCode {
|
|
let args: Args = Args::parse();
|
|
// Code formatters tend to print errors before printing the result.
|
|
if let Some(data) = args.stderr {
|
|
eprint!("{data}");
|
|
}
|
|
let stdout = if let Some(data) = args.stdout {
|
|
// Other content-altering flags don't apply to --stdout.
|
|
assert!(!args.reverse);
|
|
assert!(!args.uppercase);
|
|
assert!(!args.lowercase);
|
|
assert!(args.append.is_none());
|
|
print!("{data}");
|
|
data
|
|
} else if args.byte_mode {
|
|
assert!(!args.reverse);
|
|
assert!(args.append.is_none());
|
|
|
|
let mut stdout = vec![];
|
|
#[expect(clippy::unbuffered_bytes)]
|
|
for byte in std::io::stdin().bytes() {
|
|
let byte = byte.expect("Failed to read from stdin");
|
|
let output = if args.uppercase {
|
|
byte.to_ascii_uppercase()
|
|
} else if args.lowercase {
|
|
assert!(!args.uppercase);
|
|
byte.to_ascii_lowercase()
|
|
} else {
|
|
byte
|
|
};
|
|
stdout.push(output);
|
|
std::io::stdout()
|
|
.write_all(&[output])
|
|
.expect("Failed to write to stdout");
|
|
}
|
|
stdout
|
|
.to_str()
|
|
.expect("Output is not a valid UTF-8 string")
|
|
.to_owned()
|
|
} else {
|
|
let mut input = vec![];
|
|
std::io::stdin()
|
|
.read_to_end(&mut input)
|
|
.expect("Failed to read from stdin");
|
|
let mut stdout = input
|
|
.lines_with_terminator()
|
|
.map(|line| {
|
|
let line = line
|
|
.to_str()
|
|
.expect("The input is not valid UTF-8 string")
|
|
.to_owned();
|
|
let line = if args.reverse {
|
|
line.chars().rev().collect()
|
|
} else {
|
|
line
|
|
};
|
|
if args.uppercase {
|
|
assert!(!args.lowercase);
|
|
line.to_uppercase()
|
|
} else if args.lowercase {
|
|
assert!(!args.uppercase);
|
|
line.to_lowercase()
|
|
} else {
|
|
line
|
|
}
|
|
})
|
|
.join("");
|
|
if let Some(line) = args.append {
|
|
stdout.push_str(&line);
|
|
}
|
|
print!("{stdout}");
|
|
stdout
|
|
};
|
|
if let Some(path) = args.tee {
|
|
let mut file = OpenOptions::new()
|
|
.create(true)
|
|
.append(true)
|
|
.open(path)
|
|
.unwrap();
|
|
write!(file, "{stdout}").unwrap();
|
|
}
|
|
if args.fail {
|
|
ExitCode::FAILURE
|
|
} else {
|
|
ExitCode::SUCCESS
|
|
}
|
|
}
|