Refactor using a buffer for reading and a buffer for writing

This commit is contained in:
Nicolas Musset 2021-08-19 13:39:25 +09:00
parent db2be0a36c
commit f8c49ca661
No known key found for this signature in database
GPG key ID: 090B167B09F29DB2
4 changed files with 79 additions and 34 deletions

18
Cargo.lock generated
View file

@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.11.0" version = "0.11.0"
@ -9,6 +11,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -242,6 +250,7 @@ version = "1.0.1"
dependencies = [ dependencies = [
"clap", "clap",
"criterion", "criterion",
"utf8-chars",
] ]
[[package]] [[package]]
@ -519,6 +528,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "utf8-chars"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1348d8face79d019be7cbc0198e36bf93e160ddbfaa7bb54c9592627b9ec841"
dependencies = [
"arrayvec",
]
[[package]] [[package]]
name = "vec_map" name = "vec_map"
version = "0.8.2" version = "0.8.2"

View file

@ -15,6 +15,7 @@ categories = ["command-line-utilities"]
[dependencies] [dependencies]
clap = { version= "2.33.3", optional = true } clap = { version= "2.33.3", optional = true }
utf8-chars = "1.0.0"
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"

View file

@ -3,6 +3,10 @@
//! //!
//! It does not do anything more than that, which makes it so fast. //! It does not do anything more than that, which makes it so fast.
use std::io::{BufReader, BufWriter, Write};
use utf8_chars::BufReadCharsExt;
use std::error::Error;
/// ///
/// Set the indentation used for the formatting. /// Set the indentation used for the formatting.
/// ///
@ -24,16 +28,32 @@ pub enum Indentation<'a> {
/// The default indentation is faster than a custom one /// The default indentation is faster than a custom one
/// ///
pub fn format_json(json: &str, indentation: Indentation) -> String { pub fn format_json(json: &str, indentation: Indentation) -> String {
// at least as big as the input to avoid resizing let mut reader = BufReader::new(json.as_bytes());
// this might be too big if the input string is formatted in a weird way, but that's not expected, and it will still be efficient let mut writer = BufWriter::new(Vec::new());
let mut out = String::with_capacity(json.len());
format_json_buffered(&mut reader, &mut writer, indentation).unwrap();
String::from_utf8(writer.into_inner().unwrap()).unwrap()
}
///
/// # Formats a json string
///
/// The indentation can be set to any value using [Indentation](jsonformat::Indentation)
/// The default value is two spaces
/// The default indentation is faster than a custom one
///
pub fn format_json_buffered<R, W>(reader: &mut BufReader<R>, writer: &mut BufWriter<W>, indentation: Indentation) -> Result<(), Box<dyn Error>>
where
R: std::io::Read,
W: std::io::Write {
let mut escaped = false; let mut escaped = false;
let mut in_string = false; let mut in_string = false;
let mut indent_level = 0usize; let mut indent_level = 0usize;
let mut newline_requested = false; // invalidated if next character is ] or } let mut newline_requested = false; // invalidated if next character is ] or }
for char in json.chars() { for char in reader.chars() {
let char = char?;
if in_string { if in_string {
let mut escape_here = false; let mut escape_here = false;
match char { match char {
@ -49,7 +69,7 @@ pub fn format_json(json: &str, indentation: Indentation) -> String {
} }
_ => {} _ => {}
} }
out.push(char); writer.write_all(char.encode_utf8(&mut [0; 4]).as_bytes())?;
escaped = escape_here; escaped = escape_here;
} else { } else {
let mut auto_push = true; let mut auto_push = true;
@ -71,14 +91,14 @@ pub fn format_json(json: &str, indentation: Indentation) -> String {
indent_level = indent_level.saturating_sub(1); indent_level = indent_level.saturating_sub(1);
if !newline_requested { if !newline_requested {
// see comment below about newline_requested // see comment below about newline_requested
out.push('\n'); writeln!(writer)?;
indent(&mut out, indent_level, indentation); indent_buffered(writer, indent_level, indentation)?;
} }
} }
':' => { ':' => {
auto_push = false; auto_push = false;
out.push(char); writer.write_all(char.encode_utf8(&mut [0; 4]).as_bytes())?;
out.push(' '); writer.write_all(" ".as_bytes())?;
} }
',' => { ',' => {
request_newline = true; request_newline = true;
@ -89,33 +109,37 @@ pub fn format_json(json: &str, indentation: Indentation) -> String {
// newline only happens after { [ and , // newline only happens after { [ and ,
// this means we can safely assume that it being followed up by } or ] // this means we can safely assume that it being followed up by } or ]
// means an empty object/array // means an empty object/array
out.push('\n'); writeln!(writer)?;
indent(&mut out, old_level, indentation); indent_buffered(writer, old_level, indentation)?;
} }
if auto_push { if auto_push {
out.push(char); writer.write_all(char.encode_utf8(&mut [0; 4]).as_bytes())?;
} }
newline_requested = request_newline; newline_requested = request_newline;
} }
} }
out Ok(())
} }
fn indent(buf: &mut String, level: usize, indent_str: Indentation) { fn indent_buffered<W>(writer: &mut BufWriter<W>, level: usize, indent_str: Indentation) -> Result<(), Box<dyn Error>>
where
W: std::io::Write {
for _ in 0..level { for _ in 0..level {
match indent_str { match indent_str {
Indentation::Default => { Indentation::Default => {
buf.push(' '); writer.write_all(" ".as_bytes())?;
buf.push(' '); writer.write_all(" ".as_bytes())?;
} }
Indentation::Custom(indent) => { Indentation::Custom(indent) => {
buf.push_str(indent); writer.write_all(indent.as_bytes())?;
} }
} }
} }
Ok(())
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1,10 +1,10 @@
use clap::clap_app; use clap::clap_app;
use jsonformat::{format_json, Indentation}; use jsonformat::{Indentation, format_json_buffered};
use std::fs; use std::error::Error;
use std::io; use std::fs::File;
use std::io::Read; use std::io::{BufReader, BufWriter, Read, Write};
fn main() -> Result<(), io::Error> { fn main() -> Result<(), Box<dyn Error>> {
let matches = clap_app!(jsonformat => let matches = clap_app!(jsonformat =>
(version: "1.1") (version: "1.1")
(author: "nilstrieb <nilstrieb@gmail.com>") (author: "nilstrieb <nilstrieb@gmail.com>")
@ -16,13 +16,13 @@ fn main() -> Result<(), io::Error> {
) )
.get_matches(); .get_matches();
let str = match matches.value_of("input") { let reader: Box<dyn Read> = match matches.value_of("input") {
Some(path) => fs::read_to_string(path)?, Some(path) => {
let f = File::open(path)?;
Box::new(BufReader::new(f))
},
None => { None => {
let mut buf = String::new(); Box::new(std::io::stdin())
let stdin = std::io::stdin();
stdin.lock().read_to_string(&mut buf)?;
buf
} }
}; };
@ -41,8 +41,6 @@ fn main() -> Result<(), io::Error> {
None => Indentation::Default, None => Indentation::Default,
}; };
let formatted = format_json(&str, indent);
let mut output = matches.value_of("output"); let mut output = matches.value_of("output");
let mut windows_output_default_file: Option<String> = None; let mut windows_output_default_file: Option<String> = None;
@ -57,12 +55,16 @@ fn main() -> Result<(), io::Error> {
output = windows_output_default_file.as_deref().or(output); output = windows_output_default_file.as_deref().or(output);
match output { let writer : Box<dyn Write> = match output {
Some(file) => { Some(file) => {
fs::write(file, formatted)?; Box::new(File::create(file)?)
} }
None => println!("{}", formatted), None => Box::new(std::io::stdout()),
} };
let mut reader = BufReader::new(reader);
let mut writer = BufWriter::new(writer);
format_json_buffered(&mut reader, &mut writer, indent)?;
Ok(()) Ok(())
} }