mirror of
https://github.com/Noratrieb/jsonformat.git
synced 2026-01-14 22:25:01 +01:00
Merge pull request #4 from Kryptos-FR/buffered
Refactor using a buffer for reading and a buffer for writing
This commit is contained in:
commit
69832423b4
3 changed files with 90 additions and 46 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1,5 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
|
|
|
|||
85
src/lib.rs
85
src/lib.rs
|
|
@ -3,6 +3,9 @@
|
|||
//!
|
||||
//! It does not do anything more than that, which makes it so fast.
|
||||
|
||||
use std::error::Error;
|
||||
use std::io::{BufReader, BufWriter, Read, Write};
|
||||
|
||||
///
|
||||
/// Set the indentation used for the formatting.
|
||||
///
|
||||
|
|
@ -24,32 +27,52 @@ pub enum Indentation<'a> {
|
|||
/// The default indentation is faster than a custom one
|
||||
///
|
||||
pub fn format_json(json: &str, indentation: Indentation) -> String {
|
||||
// at least as big as the input to avoid resizing
|
||||
// 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 out = String::with_capacity(json.len());
|
||||
let mut reader = BufReader::new(json.as_bytes());
|
||||
let mut writer = BufWriter::new(Vec::new());
|
||||
|
||||
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: Read,
|
||||
W: Write,
|
||||
{
|
||||
let mut escaped = false;
|
||||
let mut in_string = false;
|
||||
let mut indent_level = 0usize;
|
||||
let mut newline_requested = false; // invalidated if next character is ] or }
|
||||
|
||||
for char in json.chars() {
|
||||
for char in reader.bytes() {
|
||||
let char = char?;
|
||||
if in_string {
|
||||
let mut escape_here = false;
|
||||
match char {
|
||||
'"' => {
|
||||
b'"' => {
|
||||
if !escaped {
|
||||
in_string = false;
|
||||
}
|
||||
}
|
||||
'\\' => {
|
||||
b'\\' => {
|
||||
if !escaped {
|
||||
escape_here = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
out.push(char);
|
||||
writer.write_all(&[char])?;
|
||||
escaped = escape_here;
|
||||
} else {
|
||||
let mut auto_push = true;
|
||||
|
|
@ -57,65 +80,73 @@ pub fn format_json(json: &str, indentation: Indentation) -> String {
|
|||
let old_level = indent_level;
|
||||
|
||||
match char {
|
||||
'"' => in_string = true,
|
||||
' ' | '\n' | '\t' => continue,
|
||||
'[' => {
|
||||
b'"' => in_string = true,
|
||||
b' ' | b'\n' | b'\t' => continue,
|
||||
b'[' => {
|
||||
indent_level += 1;
|
||||
request_newline = true;
|
||||
}
|
||||
'{' => {
|
||||
b'{' => {
|
||||
indent_level += 1;
|
||||
request_newline = true;
|
||||
}
|
||||
'}' | ']' => {
|
||||
b'}' | b']' => {
|
||||
indent_level = indent_level.saturating_sub(1);
|
||||
if !newline_requested {
|
||||
// see comment below about newline_requested
|
||||
out.push('\n');
|
||||
indent(&mut out, indent_level, indentation);
|
||||
writer.write_all(&[b'\n'])?;
|
||||
indent_buffered(writer, indent_level, indentation)?;
|
||||
}
|
||||
}
|
||||
':' => {
|
||||
b':' => {
|
||||
auto_push = false;
|
||||
out.push(char);
|
||||
out.push(' ');
|
||||
writer.write_all(&[char])?;
|
||||
writer.write_all(&[b' '])?;
|
||||
}
|
||||
',' => {
|
||||
b',' => {
|
||||
request_newline = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if newline_requested && char != ']' && char != '}' {
|
||||
if newline_requested && char != b']' && char != b'}' {
|
||||
// newline only happens after { [ and ,
|
||||
// this means we can safely assume that it being followed up by } or ]
|
||||
// means an empty object/array
|
||||
out.push('\n');
|
||||
indent(&mut out, old_level, indentation);
|
||||
writer.write_all(&[b'\n'])?;
|
||||
indent_buffered(writer, old_level, indentation)?;
|
||||
}
|
||||
|
||||
if auto_push {
|
||||
out.push(char);
|
||||
writer.write_all(&[char])?;
|
||||
}
|
||||
|
||||
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 {
|
||||
match indent_str {
|
||||
Indentation::Default => {
|
||||
buf.push(' ');
|
||||
buf.push(' ');
|
||||
writer.write_all(b" ")?;
|
||||
}
|
||||
Indentation::Custom(indent) => {
|
||||
buf.push_str(indent);
|
||||
writer.write_all(indent.as_bytes())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
49
src/main.rs
49
src/main.rs
|
|
@ -1,10 +1,10 @@
|
|||
use clap::clap_app;
|
||||
use jsonformat::{format_json, Indentation};
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use jsonformat::{format_json_buffered, Indentation};
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter, Read, Write};
|
||||
|
||||
fn main() -> Result<(), io::Error> {
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let matches = clap_app!(jsonformat =>
|
||||
(version: "1.1")
|
||||
(author: "nilstrieb <nilstrieb@gmail.com>")
|
||||
|
|
@ -16,13 +16,16 @@ fn main() -> Result<(), io::Error> {
|
|||
)
|
||||
.get_matches();
|
||||
|
||||
let str = match matches.value_of("input") {
|
||||
Some(path) => fs::read_to_string(path)?,
|
||||
// Note: on-stack dynamic dispatch
|
||||
let (mut file, mut stdin);
|
||||
let reader: &mut dyn Read = match matches.value_of("input") {
|
||||
Some(path) => {
|
||||
file = File::open(path)?;
|
||||
&mut file
|
||||
}
|
||||
None => {
|
||||
let mut buf = String::new();
|
||||
let stdin = std::io::stdin();
|
||||
stdin.lock().read_to_string(&mut buf)?;
|
||||
buf
|
||||
stdin = std::io::stdin();
|
||||
&mut stdin
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -41,8 +44,6 @@ fn main() -> Result<(), io::Error> {
|
|||
None => Indentation::Default,
|
||||
};
|
||||
|
||||
let formatted = format_json(&str, indent);
|
||||
|
||||
let mut output = matches.value_of("output");
|
||||
let mut windows_output_default_file: Option<String> = None;
|
||||
|
||||
|
|
@ -57,12 +58,22 @@ fn main() -> Result<(), io::Error> {
|
|||
|
||||
output = windows_output_default_file.as_deref().or(output);
|
||||
|
||||
match output {
|
||||
Some(file) => {
|
||||
fs::write(file, formatted)?;
|
||||
}
|
||||
None => println!("{}", formatted),
|
||||
}
|
||||
// Note: on-stack dynamic dispatch
|
||||
let (mut file, mut stdout);
|
||||
let writer: &mut dyn Write = match output {
|
||||
Some(filename) => {
|
||||
file = File::create(filename)?;
|
||||
&mut file
|
||||
},
|
||||
None => {
|
||||
stdout = std::io::stdout();
|
||||
&mut stdout
|
||||
},
|
||||
};
|
||||
|
||||
let mut reader = BufReader::new(reader);
|
||||
let mut writer = BufWriter::new(writer);
|
||||
format_json_buffered(&mut reader, &mut writer, indent)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue