From 9890bbdc383648d24c78063a06664b6c98e6fb8d Mon Sep 17 00:00:00 2001 From: June Kim Date: Mon, 11 May 2026 11:46:47 -0700 Subject: [PATCH 1/9] Handle empty file inputs gracefully Fixes #58. When mounting an empty file (e.g., `touch empty.json && ffs empty.json`), the parser would panic with "EOF while parsing a value". This change detects empty input in all three format parsers (JSON, TOML, YAML) and returns an empty object/table/hash instead of panicking. Implementation: - JSON: Peek at first byte to detect empty input before parsing - TOML/YAML: Check if text is empty after reading to string The fix aligns with the existing `--empty` flag behavior, which creates an empty named directory. Now mounting an empty file produces the same result. Test added: tests/ffs.empty_file.sh verifies that an empty JSON file can be mounted, modified, and written back successfully. --- nodelike/src/nodelike.rs | 30 ++++++++++++++++++++++++++++-- tests/ffs.empty_file.sh | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100755 tests/ffs.empty_file.sh diff --git a/nodelike/src/nodelike.rs b/nodelike/src/nodelike.rs index 38638fd..3230696 100644 --- a/nodelike/src/nodelike.rs +++ b/nodelike/src/nodelike.rs @@ -240,6 +240,7 @@ impl Format { pub mod json { use super::*; use base64::Engine as _; + use std::io::Read; pub use serde_json::Value; impl Nodelike for Value { @@ -359,8 +360,25 @@ pub mod json { serde_json::to_writer(writer, self).unwrap(); } } - fn from_reader(reader: std::boxed::Box) -> Self { - serde_json::from_reader(reader).expect("JSON") + fn from_reader(mut reader: std::boxed::Box) -> Self { + // Check if the input is empty by peeking at the first byte + let mut first_byte = [0u8; 1]; + match reader.read(&mut first_byte) { + Ok(0) => { + // Empty file - return empty object + debug!("Empty input detected, returning empty object"); + Value::Object(serde_json::Map::new()) + } + Ok(_) => { + // Non-empty file - reconstruct the input with the first byte prepended + let mut full_input = Vec::new(); + full_input.push(first_byte[0]); + reader.read_to_end(&mut full_input).expect("Reading input"); + + serde_json::from_reader(std::io::Cursor::new(full_input)).expect("JSON") + } + Err(e) => panic!("Error reading input: {}", e), + } } } } @@ -527,6 +545,10 @@ pub mod toml { fn from_reader(mut reader: Box) -> Self { let mut text = String::new(); let _len = reader.read_to_string(&mut text).unwrap(); + if text.trim().is_empty() { + debug!("Empty TOML input detected, returning empty table"); + return Value(Toml::Table(serde_toml::map::Map::new())); + } Value(serde_toml::from_str(&text).expect("TOML")) } @@ -735,6 +757,10 @@ pub mod yaml { fn from_reader(mut reader: Box) -> Self { let mut text = String::new(); let _len = reader.read_to_string(&mut text).unwrap(); + if text.trim().is_empty() { + debug!("Empty YAML input detected, returning empty hash"); + return Value(Yaml::Hash(linked_hash_map::LinkedHashMap::new())); + } yaml_rust::YamlLoader::load_from_str(&text) .map(|vs| { Value(if vs.len() == 1 { diff --git a/tests/ffs.empty_file.sh b/tests/ffs.empty_file.sh new file mode 100755 index 0000000..0280d22 --- /dev/null +++ b/tests/ffs.empty_file.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +WAITFOR="$(cd ../utils; pwd)/waitfor" +. ./fail.def + +MNT=$(mktemp -d) +EMPTY=$(mktemp --suffix=.json) + +# Create an empty JSON file +echo -n "" > "$EMPTY" + +# Mount the empty file - should create an empty object by default +ffs -m "$MNT" "$EMPTY" & +PID=$! +"$WAITFOR" mount "$MNT" +cd "$MNT" + +# Should be an empty directory +[ -z "$(ls)" ] || fail "expected empty directory" + +# Add a field +echo "test value" > testfield + +cd - >/dev/null 2>&1 +"$WAITFOR" umount "$MNT" || fail unmount +"$WAITFOR" exit $PID + +kill -0 $PID >/dev/null 2>&1 && fail process + +# Check the output contains the new field +grep -q "testfield" "$EMPTY" || fail "output should contain testfield" + +rm "$EMPTY" +rmdir "$MNT" || fail mount From 4984fa0beb90150b018a9b54d10f07a7a225637f Mon Sep 17 00:00:00 2001 From: June Kim Date: Mon, 11 May 2026 12:48:02 -0700 Subject: [PATCH 2/9] address review: use Chain for streaming, handle whitespace-only Gemini review findings fixed: 1. JSON parser: replace read_to_end buffering with std::io::Chain to maintain streaming performance 2. JSON parser: add whitespace-only input handling (already done for TOML/YAML) 3. YAML parser: fix compilation error by using Yaml::Null instead of linked_hash_map::LinkedHashMap::new() The Chain approach prepends the peeked byte back to the reader without buffering the entire file. --- nodelike/src/nodelike.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/nodelike/src/nodelike.rs b/nodelike/src/nodelike.rs index 3230696..da990e1 100644 --- a/nodelike/src/nodelike.rs +++ b/nodelike/src/nodelike.rs @@ -369,13 +369,24 @@ pub mod json { debug!("Empty input detected, returning empty object"); Value::Object(serde_json::Map::new()) } - Ok(_) => { - // Non-empty file - reconstruct the input with the first byte prepended - let mut full_input = Vec::new(); - full_input.push(first_byte[0]); - reader.read_to_end(&mut full_input).expect("Reading input"); - - serde_json::from_reader(std::io::Cursor::new(full_input)).expect("JSON") + Ok(n) => { + // Check if it's whitespace-only + if first_byte[0].is_ascii_whitespace() { + // Read the rest to check if it's all whitespace + let mut rest = String::new(); + reader.read_to_string(&mut rest).expect("Reading input"); + if rest.trim().is_empty() { + debug!("Whitespace-only input detected, returning empty object"); + return Value::Object(serde_json::Map::new()); + } + // Not all whitespace - reconstruct and parse + let full_input = format!("{}{}", first_byte[0] as char, rest); + serde_json::from_str(&full_input).expect("JSON") + } else { + // Non-empty file - prepend the first byte back using Chain + let chained = std::io::Cursor::new(&first_byte[..n]).chain(reader); + serde_json::from_reader(chained).expect("JSON") + } } Err(e) => panic!("Error reading input: {}", e), } @@ -758,8 +769,8 @@ pub mod yaml { let mut text = String::new(); let _len = reader.read_to_string(&mut text).unwrap(); if text.trim().is_empty() { - debug!("Empty YAML input detected, returning empty hash"); - return Value(Yaml::Hash(linked_hash_map::LinkedHashMap::new())); + debug!("Empty YAML input detected, returning Null"); + return Value(Yaml::Null); } yaml_rust::YamlLoader::load_from_str(&text) .map(|vs| { From eaa7be5bc5bfff6793c1dedabe8e2fd6728a00a6 Mon Sep 17 00:00:00 2001 From: June Kim Date: Mon, 11 May 2026 15:53:58 -0700 Subject: [PATCH 3/9] Fix UTF-8 handling in JSON whitespace reconstruction The previous code used `first_byte[0] as char` which could produce incorrect characters for non-ASCII bytes. While valid JSON must start with ASCII characters, handling invalid input gracefully produces better error messages. Changed to use byte-level concatenation via Vec and from_slice, which correctly preserves any byte sequence for serde_json to parse. --- nodelike/src/nodelike.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nodelike/src/nodelike.rs b/nodelike/src/nodelike.rs index da990e1..3fe5746 100644 --- a/nodelike/src/nodelike.rs +++ b/nodelike/src/nodelike.rs @@ -380,8 +380,11 @@ pub mod json { return Value::Object(serde_json::Map::new()); } // Not all whitespace - reconstruct and parse - let full_input = format!("{}{}", first_byte[0] as char, rest); - serde_json::from_str(&full_input).expect("JSON") + // Use byte concatenation instead of char conversion to handle any byte value + let mut full_input = Vec::with_capacity(1 + rest.len()); + full_input.push(first_byte[0]); + full_input.extend_from_slice(rest.as_bytes()); + serde_json::from_slice(&full_input).expect("JSON") } else { // Non-empty file - prepend the first byte back using Chain let chained = std::io::Cursor::new(&first_byte[..n]).chain(reader); From 592d51f9cae87cca235c4c6c2228b5c3453dbdb5 Mon Sep 17 00:00:00 2001 From: June Kim Date: Tue, 12 May 2026 06:43:44 -0700 Subject: [PATCH 4/9] address review: use metadata for empty detection, add --strict, cover all formats - Replace read-into-memory whitespace check with std::fs::metadata (zero-byte file detection in input_reader) - Remove unnecessary TOML/YAML empty-file handling (parsers handle it) - Revert JSON from_reader to simple serde_json::from_reader - Add --strict flag to cli_base (disables empty-file detection) - Add comparable empty-file handling to unpack - Tests cover JSON, TOML, YAML empty files and --strict error case - Add json/empty.json (0-byte), yaml/empty.yaml fixtures --- json/empty.json | 1 - nodelike/src/config.rs | 20 ++++++++++++- nodelike/src/nodelike.rs | 44 ++-------------------------- tests/ffs.empty_file.sh | 53 ++++++++++++++++++++++------------ tests/packunpack.empty_file.sh | 36 +++++++++++++++++++++++ unpack/src/main.rs | 6 ++-- yaml/empty.yaml | 0 7 files changed, 96 insertions(+), 64 deletions(-) create mode 100755 tests/packunpack.empty_file.sh create mode 100644 yaml/empty.yaml diff --git a/json/empty.json b/json/empty.json index 0967ef4..e69de29 100644 --- a/json/empty.json +++ b/json/empty.json @@ -1 +0,0 @@ -{} diff --git a/nodelike/src/config.rs b/nodelike/src/config.rs index 899402a..a335b38 100644 --- a/nodelike/src/config.rs +++ b/nodelike/src/config.rs @@ -70,6 +70,12 @@ pub fn cli_base(name: impl Into) -> clap::Command { .action(ArgAction::SetTrue) ) + .arg( + Arg::new("STRICT") + .help("Disable empty-file detection; empty files will produce parse errors instead of empty directories") + .long("strict") + .action(ArgAction::SetTrue) + ) } /// Configuration information @@ -104,6 +110,7 @@ pub struct Config { pub timing: bool, pub mount: Option, pub cleanup_mount: bool, + pub strict: bool, } #[derive(Debug)] @@ -200,6 +207,7 @@ impl Config { config.timing = args.get_flag("TIMING"); config.add_newlines = !args.get_flag("EXACT"); config.allow_xattr = !args.get_flag("NOXATTR"); + config.strict = args.get_flag("STRICT"); // munging policy config.munge = match args.get_one::("MUNGE") { @@ -252,11 +260,20 @@ impl Config { /// Generate a reader for input /// - /// A return of `None` means to start from an empty named directory + /// A return of `None` means to start from an empty named directory. + /// When `--strict` is not set, a zero-byte file is treated as empty. pub fn input_reader(&self) -> Option> { match &self.input { Input::Stdin => Some(Box::new(std::io::stdin())), Input::File(file) => { + if !self.strict { + if let Ok(meta) = std::fs::metadata(file) { + if meta.len() == 0 { + debug!("Empty file detected, treating as empty input"); + return None; + } + } + } let fmt = self.input_format; let file = std::fs::File::open(file).unwrap_or_else(|e| { error!("Unable to open {} for {fmt} input: {e}", file.display()); @@ -315,6 +332,7 @@ impl Default for Config { timing: false, mount: None, cleanup_mount: false, + strict: false, } } } diff --git a/nodelike/src/nodelike.rs b/nodelike/src/nodelike.rs index 3fe5746..38638fd 100644 --- a/nodelike/src/nodelike.rs +++ b/nodelike/src/nodelike.rs @@ -240,7 +240,6 @@ impl Format { pub mod json { use super::*; use base64::Engine as _; - use std::io::Read; pub use serde_json::Value; impl Nodelike for Value { @@ -360,39 +359,8 @@ pub mod json { serde_json::to_writer(writer, self).unwrap(); } } - fn from_reader(mut reader: std::boxed::Box) -> Self { - // Check if the input is empty by peeking at the first byte - let mut first_byte = [0u8; 1]; - match reader.read(&mut first_byte) { - Ok(0) => { - // Empty file - return empty object - debug!("Empty input detected, returning empty object"); - Value::Object(serde_json::Map::new()) - } - Ok(n) => { - // Check if it's whitespace-only - if first_byte[0].is_ascii_whitespace() { - // Read the rest to check if it's all whitespace - let mut rest = String::new(); - reader.read_to_string(&mut rest).expect("Reading input"); - if rest.trim().is_empty() { - debug!("Whitespace-only input detected, returning empty object"); - return Value::Object(serde_json::Map::new()); - } - // Not all whitespace - reconstruct and parse - // Use byte concatenation instead of char conversion to handle any byte value - let mut full_input = Vec::with_capacity(1 + rest.len()); - full_input.push(first_byte[0]); - full_input.extend_from_slice(rest.as_bytes()); - serde_json::from_slice(&full_input).expect("JSON") - } else { - // Non-empty file - prepend the first byte back using Chain - let chained = std::io::Cursor::new(&first_byte[..n]).chain(reader); - serde_json::from_reader(chained).expect("JSON") - } - } - Err(e) => panic!("Error reading input: {}", e), - } + fn from_reader(reader: std::boxed::Box) -> Self { + serde_json::from_reader(reader).expect("JSON") } } } @@ -559,10 +527,6 @@ pub mod toml { fn from_reader(mut reader: Box) -> Self { let mut text = String::new(); let _len = reader.read_to_string(&mut text).unwrap(); - if text.trim().is_empty() { - debug!("Empty TOML input detected, returning empty table"); - return Value(Toml::Table(serde_toml::map::Map::new())); - } Value(serde_toml::from_str(&text).expect("TOML")) } @@ -771,10 +735,6 @@ pub mod yaml { fn from_reader(mut reader: Box) -> Self { let mut text = String::new(); let _len = reader.read_to_string(&mut text).unwrap(); - if text.trim().is_empty() { - debug!("Empty YAML input detected, returning Null"); - return Value(Yaml::Null); - } yaml_rust::YamlLoader::load_from_str(&text) .map(|vs| { Value(if vs.len() == 1 { diff --git a/tests/ffs.empty_file.sh b/tests/ffs.empty_file.sh index 0280d22..7af889c 100755 --- a/tests/ffs.empty_file.sh +++ b/tests/ffs.empty_file.sh @@ -1,34 +1,51 @@ #!/bin/sh +TIMEOUT="$(cd ../utils; pwd)/timeout" WAITFOR="$(cd ../utils; pwd)/waitfor" . ./fail.def +# --- JSON empty file --- MNT=$(mktemp -d) -EMPTY=$(mktemp --suffix=.json) -# Create an empty JSON file -echo -n "" > "$EMPTY" - -# Mount the empty file - should create an empty object by default -ffs -m "$MNT" "$EMPTY" & +ffs -m "$MNT" ../json/empty.json & PID=$! "$WAITFOR" mount "$MNT" -cd "$MNT" -# Should be an empty directory -[ -z "$(ls)" ] || fail "expected empty directory" +[ -z "$(ls "$MNT")" ] || fail json_notempty +"$WAITFOR" umount "$MNT" || fail json_unmount +"$WAITFOR" exit $PID +kill -0 $PID >/dev/null 2>&1 && fail json_process +rmdir "$MNT" || fail json_mount -# Add a field -echo "test value" > testfield +# --- TOML empty file --- +MNT=$(mktemp -d) -cd - >/dev/null 2>&1 -"$WAITFOR" umount "$MNT" || fail unmount +ffs -m "$MNT" ../toml/empty.toml & +PID=$! +"$WAITFOR" mount "$MNT" + +[ -z "$(ls "$MNT")" ] || fail toml_notempty +"$WAITFOR" umount "$MNT" || fail toml_unmount "$WAITFOR" exit $PID +kill -0 $PID >/dev/null 2>&1 && fail toml_process +rmdir "$MNT" || fail toml_mount -kill -0 $PID >/dev/null 2>&1 && fail process +# --- YAML empty file --- +MNT=$(mktemp -d) -# Check the output contains the new field -grep -q "testfield" "$EMPTY" || fail "output should contain testfield" +ffs -m "$MNT" ../yaml/empty.yaml & +PID=$! +"$WAITFOR" mount "$MNT" + +[ -z "$(ls "$MNT")" ] || fail yaml_notempty +"$WAITFOR" umount "$MNT" || fail yaml_unmount +"$WAITFOR" exit $PID +kill -0 $PID >/dev/null 2>&1 && fail yaml_process +rmdir "$MNT" || fail yaml_mount + +# --- --strict should error on empty JSON --- +MNT=$(mktemp -d) -rm "$EMPTY" -rmdir "$MNT" || fail mount +"$TIMEOUT" -t 2 ffs --strict -m "$MNT" ../json/empty.json 2>/dev/null +[ $? -ne 0 ] || fail strict_should_error +rmdir "$MNT" || fail strict_mount diff --git a/tests/packunpack.empty_file.sh b/tests/packunpack.empty_file.sh new file mode 100755 index 0000000..2937578 --- /dev/null +++ b/tests/packunpack.empty_file.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +TIMEOUT="$(cd ../utils; pwd)/timeout" + +fail() { + echo FAILED: $1 + if [ "$MNT" ] + then + rm -r "$MNT" + fi + exit 1 +} + +# --- JSON empty file --- +MNT=$(mktemp -d) +unpack --into "$MNT" ../json/empty.json || fail json_unpack +[ -z "$(ls "$MNT")" ] || fail json_notempty +rm -r "$MNT" || fail json_cleanup + +# --- TOML empty file --- +MNT=$(mktemp -d) +unpack --into "$MNT" ../toml/empty.toml || fail toml_unpack +[ -z "$(ls "$MNT")" ] || fail toml_notempty +rm -r "$MNT" || fail toml_cleanup + +# --- YAML empty file --- +MNT=$(mktemp -d) +unpack --into "$MNT" ../yaml/empty.yaml || fail yaml_unpack +[ -z "$(ls "$MNT")" ] || fail yaml_notempty +rm -r "$MNT" || fail yaml_cleanup + +# --- --strict should error on empty JSON --- +MNT=$(mktemp -d) +"$TIMEOUT" -t 2 unpack --strict --into "$MNT" ../json/empty.json 2>/dev/null +[ $? -ne 0 ] || fail strict_should_error +rm -r "$MNT" || fail strict_cleanup diff --git a/unpack/src/main.rs b/unpack/src/main.rs index e2c221f..43bcfdb 100644 --- a/unpack/src/main.rs +++ b/unpack/src/main.rs @@ -310,8 +310,10 @@ fn main() -> std::io::Result<()> { let reader = match config.input_reader() { Some(reader) => reader, None => { - error!("Input not specified"); - std::process::exit(ERROR_STATUS_CLI); + // Empty input: the mount directory already exists, so just + // leave it as an empty directory. + info!("Empty input; unpacked into empty directory {mount:?}"); + return Ok(()); } }; diff --git a/yaml/empty.yaml b/yaml/empty.yaml new file mode 100644 index 0000000..e69de29 From 3cb01ee3fb190b432d87bb20709e4a9618d9b03b Mon Sep 17 00:00:00 2001 From: Michael Greenberg Date: Tue, 12 May 2026 09:51:15 -0400 Subject: [PATCH 5/9] clarify `--strict` --- nodelike/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodelike/src/config.rs b/nodelike/src/config.rs index a335b38..df0c819 100644 --- a/nodelike/src/config.rs +++ b/nodelike/src/config.rs @@ -72,7 +72,7 @@ pub fn cli_base(name: impl Into) -> clap::Command { ) .arg( Arg::new("STRICT") - .help("Disable empty-file detection; empty files will produce parse errors instead of empty directories") + .help("Strictly conform to format specifications (disables empty-file detection; empty JSON files will produce parse errors)") .long("strict") .action(ArgAction::SetTrue) ) From 5b52a090829b798604da2f2856874f7806826a6d Mon Sep 17 00:00:00 2001 From: Michael Greenberg Date: Tue, 12 May 2026 14:15:15 -0400 Subject: [PATCH 6/9] quieter utils/waitfor --- utils/waitfor | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/utils/waitfor b/utils/waitfor index 42d461f..1eb2071 100755 --- a/utils/waitfor +++ b/utils/waitfor @@ -28,8 +28,8 @@ elapsed=0 while [ "$(echo "$elapsed < $timeout" | bc)" -eq 1 ]; do case "$cmd" in mount) mountpoint -q "$arg" && exit 0 ;; - umount) umount "$arg" 2>/dev/null && exit 0 ;; - exit) kill -0 "$arg" 2>/dev/null || exit 0 ;; + umount) umount "$arg" >/dev/null 2>&1 && exit 0 ;; + exit) kill -0 "$arg" >/dev/null 2>&1 || exit 0 ;; *) echo "waitfor: unknown command '$cmd'" >&2; exit 1 ;; esac sleep "$INTERVAL" @@ -37,10 +37,10 @@ while [ "$(echo "$elapsed < $timeout" | bc)" -eq 1 ]; do done if [ "$cmd" = "exit" ]; then - kill -TERM "$arg" 2>/dev/null - if kill -0 "$arg" 2>/dev/null; then + kill -TERM "$arg" >/dev/null 2>&1 + if kill -0 "$arg" >/dev/null 2>&1; then sleep "$INTERVAL" - kill -KILL "$arg" 2>/dev/null + kill -KILL "$arg" >/dev/null 2>&1 fi fi From cba93e9fe7931ef435d34e13e63b3519339145f7 Mon Sep 17 00:00:00 2001 From: Michael Greenberg Date: Tue, 12 May 2026 14:15:57 -0400 Subject: [PATCH 7/9] full debug output (nodelike was silenced) when running tests --- run_tests.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/run_tests.sh b/run_tests.sh index 0e9f03c..724ea2d 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -32,9 +32,9 @@ then printf "Couldn't find ffs or pack/unpack; building...\n" >&2 (cd "$FFS_TOP" if [ "$(uname -s)" = "Darwin" ] - then - cargo build --bin pack --bin unpack - else + then + cargo build --bin pack --bin unpack + else cargo build --workspace fi) if ! detect_tools @@ -67,7 +67,7 @@ do esac printf "========== STARTING TEST: $tname\n" - (RUST_LOG="ffs=debug,unpack=debug,pack=debug,fuser=debug"; export RUST_LOG; ./${test} >$LOG/$tname.out 2>$LOG/$tname.err; echo $?>$LOG/$tname.ec) & + (RUST_LOG="debug"; export RUST_LOG; ./${test} >$LOG/$tname.out 2>$LOG/$tname.err; echo $?>$LOG/$tname.ec) & : $((TOTAL += 1)) # don't slam 'em From c41359098c9cd3e78b6ee2064b2c626f5102a0d3 Mon Sep 17 00:00:00 2001 From: Michael Greenberg Date: Tue, 12 May 2026 14:16:51 -0400 Subject: [PATCH 8/9] only call things list directories when there is at least one thing in them --- pack/src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pack/src/main.rs b/pack/src/main.rs index 03fa73e..73a5adc 100644 --- a/pack/src/main.rs +++ b/pack/src/main.rs @@ -390,16 +390,19 @@ impl Pack { Resolving type automatically." ); } + let mut count = 0; let all_files_begin_with_num = fs::read_dir(path.clone())? .map(|res| res.map(|e| e.path())) .map(|e| e.unwrap().file_name().unwrap().to_str().unwrap().to_owned()) .all(|filename| { + count += 1; + filename.chars().nth(0).unwrap().is_ascii_digit() || filename.len() > 1 && filename.chars().nth(0).unwrap() == '-' && filename.chars().nth(1).unwrap().is_ascii_digit() }); - if all_files_begin_with_num { + if all_files_begin_with_num && count > 0 { path_type = "list" } else { path_type = "named" From cbd74355cd4847430272654abec5b844e7f71adc Mon Sep 17 00:00:00 2001 From: Michael Greenberg Date: Tue, 12 May 2026 14:17:04 -0400 Subject: [PATCH 9/9] code style/formatting --- nodelike/src/config.rs | 13 ++++++------- nodelike/src/nodelike.rs | 11 +++++------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/nodelike/src/config.rs b/nodelike/src/config.rs index df0c819..c1fe391 100644 --- a/nodelike/src/config.rs +++ b/nodelike/src/config.rs @@ -266,13 +266,12 @@ impl Config { match &self.input { Input::Stdin => Some(Box::new(std::io::stdin())), Input::File(file) => { - if !self.strict { - if let Ok(meta) = std::fs::metadata(file) { - if meta.len() == 0 { - debug!("Empty file detected, treating as empty input"); - return None; - } - } + if !self.strict + && let Ok(meta) = std::fs::metadata(file) + && meta.len() == 0 + { + debug!("Empty file detected, treating as empty input"); + return None; } let fmt = self.input_format; let file = std::fs::File::open(file).unwrap_or_else(|e| { diff --git a/nodelike/src/nodelike.rs b/nodelike/src/nodelike.rs index 38638fd..284068d 100644 --- a/nodelike/src/nodelike.rs +++ b/nodelike/src/nodelike.rs @@ -187,9 +187,11 @@ where match (*self).node(config) { Node::String(t, s) => Node::String(t, s), Node::Bytes(b) => Node::Bytes(b), - Node::List(vs) => { - Node::List(vs.into_iter().map(|v| Box::new(v) as Box).collect()) - } + Node::List(vs) => Node::List( + vs.into_iter() + .map(|v| Box::new(v) as Box) + .collect(), + ), Node::Map(kvs) => Node::Map( kvs.into_iter() .map(|(k, v)| (k, Box::new(v) as Box)) @@ -281,7 +283,6 @@ pub mod json { } } - fn from_string(typ: Typ, contents: String, _config: &Config) -> Self { match typ { Typ::Auto => { @@ -439,7 +440,6 @@ pub mod toml { } } - fn from_string(typ: Typ, contents: String, _config: &Config) -> Self { let v = match typ { Typ::Auto => { @@ -653,7 +653,6 @@ pub mod yaml { } } - fn from_string(typ: Typ, contents: String, _config: &Config) -> Self { match typ { Typ::Auto => {