diff --git a/src/app.rs b/src/app.rs index 7224995d..c91431e2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,7 +4,7 @@ pub use sub::Sub; use crate::{ args::{Args, Quantity}, - json_data::JsonData, + json_data::{JsonData, UnitAndTree}, reporter::{ErrorOnlyReporter, ErrorReport, ProgressAndErrorReporter, ProgressReport}, runtime_error::RuntimeError, size::{Bytes, Size}, @@ -59,9 +59,10 @@ impl App { } = self.args; let direction = Direction::from_top_down(top_down); - let json_data = stdin() + let unit_and_tree = stdin() .pipe(serde_json::from_reader::<_, JsonData>) - .map_err(RuntimeError::DeserializationFailure)?; + .map_err(RuntimeError::DeserializationFailure)? + .unit_and_tree; macro_rules! visualize { ($reflection:expr, $bytes_format: expr) => {{ @@ -79,9 +80,9 @@ impl App { }}; } - let visualization = match json_data { - JsonData::Bytes(reflection) => visualize!(reflection, bytes_format), - JsonData::Blocks(reflection) => visualize!(reflection, ()), + let visualization = match unit_and_tree { + UnitAndTree::Bytes(reflection) => visualize!(reflection, bytes_format), + UnitAndTree::Blocks(reflection) => visualize!(reflection, ()), }; print!("{}", visualization); // it already ends with "\n", println! isn't needed here. diff --git a/src/app/sub.rs b/src/app/sub.rs index 017ae65d..530c8f5b 100644 --- a/src/app/sub.rs +++ b/src/app/sub.rs @@ -2,7 +2,7 @@ use crate::{ args::Fraction, data_tree::{DataTree, DataTreeReflection}, fs_tree_builder::FsTreeBuilder, - json_data::JsonData, + json_data::{BinaryVersion, JsonData, SchemaVersion, UnitAndTree}, os_string_display::OsStringDisplay, reporter::ParallelReporter, runtime_error::RuntimeError, @@ -19,7 +19,7 @@ where Data: Size + Into + Serialize + Send + Sync, Report: ParallelReporter + Sync, GetData: Fn(&Metadata) -> Data + Copy + Sync, - DataTreeReflection: Into, + DataTreeReflection: Into, { /// List of files and/or directories. pub files: Vec, @@ -48,7 +48,7 @@ where Data: Size + Into + Serialize + Send + Sync, Report: ParallelReporter + Sync, GetData: Fn(&Metadata) -> Data + Copy + Sync, - DataTreeReflection: Into, + DataTreeReflection: Into, { /// Run the sub program. pub fn run(self) -> Result<(), RuntimeError> { @@ -116,11 +116,16 @@ where }; if json_output { - let json_data: JsonData = data_tree + let unit_and_tree: UnitAndTree = data_tree .into_reflection() // I really want to use std::mem::transmute here but can't. .par_convert_names_to_utf8() // TODO: allow non-UTF8 somehow. .expect("convert all names from raw string to UTF-8") .into(); + let json_data = JsonData { + schema_version: SchemaVersion, + binary_version: Some(BinaryVersion::current()), + unit_and_tree, + }; return serde_json::to_writer(stdout(), &json_data) .map_err(RuntimeError::SerializationFailure); } diff --git a/src/data_tree/reflection.rs b/src/data_tree/reflection.rs index b5de7145..cd9d5353 100644 --- a/src/data_tree/reflection.rs +++ b/src/data_tree/reflection.rs @@ -27,6 +27,7 @@ use serde::{Deserialize, Serialize}; /// **Serialization and deserialization:** Requires enabling the `json` feature to enable `serde`. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "json", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "json", serde(rename_all = "kebab-case"))] pub struct Reflection { /// Name of the tree. pub name: Name, diff --git a/src/fs_tree_builder.rs b/src/fs_tree_builder.rs index 55de73f0..63ccbf7a 100644 --- a/src/fs_tree_builder.rs +++ b/src/fs_tree_builder.rs @@ -4,7 +4,6 @@ use super::{ reporter::{error_report::Operation::*, ErrorReport, Event, Reporter}, size::Size, tree_builder::{Info, TreeBuilder}, - utils::path_name, }; use pipe_trait::Pipe; use std::{ @@ -43,7 +42,7 @@ where } = builder; TreeBuilder:: { - name: path_name(&root), + name: OsStringDisplay::os_string_from(&root), path: root, diff --git a/src/json_data.rs b/src/json_data.rs index 670b3ff2..4c89133c 100644 --- a/src/json_data.rs +++ b/src/json_data.rs @@ -1,3 +1,9 @@ +pub mod binary_version; +pub mod schema_version; + +pub use binary_version::BinaryVersion; +pub use schema_version::SchemaVersion; + use crate::{ data_tree::Reflection, size::{Blocks, Bytes}, @@ -7,14 +13,30 @@ use derive_more::{From, TryInto}; #[cfg(feature = "json")] use serde::{Deserialize, Serialize}; -/// Output of the program with `--json-output` flag as well as -/// input of the program with `--json-input` flag. +/// The `"unit"` field and the `"tree"` field of [`JsonData`]. #[derive(Debug, Clone, From, TryInto)] #[cfg_attr(feature = "json", derive(Deserialize, Serialize))] #[cfg_attr(feature = "json", serde(tag = "unit", content = "tree"))] -pub enum JsonData { +#[cfg_attr(feature = "json", serde(rename_all = "kebab-case"))] +pub enum UnitAndTree { /// Tree where data is [bytes](Bytes). Bytes(Reflection), /// Tree where data is [blocks](Blocks). Blocks(Reflection), } + +/// Output of the program with `--json-output` flag as well as +/// input of the program with `--json-input` flag. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "json", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "json", serde(rename_all = "kebab-case"))] +pub struct JsonData { + /// The `"schema-version"` field. + pub schema_version: SchemaVersion, + /// The `"pdu"` field. + #[cfg_attr(feature = "json", serde(rename = "pdu"))] + pub binary_version: Option, + /// The `"unit"` field and the `"tree"` field. + #[cfg_attr(feature = "json", serde(flatten))] + pub unit_and_tree: UnitAndTree, +} diff --git a/src/json_data/binary_version.rs b/src/json_data/binary_version.rs new file mode 100644 index 00000000..997f8394 --- /dev/null +++ b/src/json_data/binary_version.rs @@ -0,0 +1,19 @@ +use derive_more::{AsMut, AsRef, From, FromStr, Into}; + +#[cfg(feature = "json")] +use serde::{Deserialize, Serialize}; + +/// Version of the current `pdu` program. +pub const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// Version of the `pdu` program that created the input JSON. +#[derive(Debug, Clone, PartialEq, Eq, AsMut, AsRef, From, FromStr, Into)] +#[cfg_attr(feature = "json", derive(Deserialize, Serialize))] +pub struct BinaryVersion(String); + +impl BinaryVersion { + /// Get version of the current `pdu` program as a `BinaryVersion`. + pub fn current() -> Self { + CURRENT_VERSION.to_string().into() + } +} diff --git a/src/json_data/schema_version.rs b/src/json_data/schema_version.rs new file mode 100644 index 00000000..643f60ed --- /dev/null +++ b/src/json_data/schema_version.rs @@ -0,0 +1,46 @@ +#[cfg(feature = "json")] +use derive_more::Display; +#[cfg(feature = "json")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "json")] +use std::convert::TryFrom; + +/// Content of [`SchemaVersion`]. +pub const SCHEMA_VERSION: &str = "2021-06-05"; + +/// Verifying schema version. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "json", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "json", serde(try_from = "String", into = "&str"))] +pub struct SchemaVersion; + +/// Error when trying to parse [`SchemaVersion`]. +#[cfg(feature = "json")] +#[derive(Debug, Display)] +#[display( + fmt = "InvalidSchema: {:?}: input schema is not {:?}", + input, + SCHEMA_VERSION +)] +pub struct InvalidSchema { + /// The input string. + pub input: String, +} + +#[cfg(feature = "json")] +impl TryFrom for SchemaVersion { + type Error = InvalidSchema; + fn try_from(input: String) -> Result { + if input == SCHEMA_VERSION { + Ok(SchemaVersion) + } else { + Err(InvalidSchema { input }) + } + } +} + +impl<'a> From for &'a str { + fn from(_: SchemaVersion) -> Self { + SCHEMA_VERSION + } +} diff --git a/src/lib.rs b/src/lib.rs index 485bb4c0..dea6f945 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,5 @@ #![deny(warnings)] -mod utils; - #[cfg(feature = "json")] pub use serde; #[cfg(feature = "json")] diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 3e9eed67..00000000 --- a/src/utils.rs +++ /dev/null @@ -1,15 +0,0 @@ -use super::os_string_display::OsStringDisplay; -use std::path::{Component::*, Path}; - -/// Get file name or directory name of a path. -pub fn path_name(path: &Path) -> OsStringDisplay { - match path.components().last() { - None | Some(CurDir) => OsStringDisplay::os_string_from("."), - Some(Normal(name)) => OsStringDisplay::os_string_from(name), - Some(Prefix(prefix)) => OsStringDisplay::os_string_from(prefix.as_os_str()), - Some(RootDir) | Some(ParentDir) => OsStringDisplay::os_string_from(path), - } -} - -#[cfg(test)] -mod test_path_name; diff --git a/src/utils/test_path_name.rs b/src/utils/test_path_name.rs deleted file mode 100644 index 65c0a70b..00000000 --- a/src/utils/test_path_name.rs +++ /dev/null @@ -1,82 +0,0 @@ -use super::path_name; -use crate::os_string_display::OsStringDisplay; -use pretty_assertions::assert_eq; -use std::path::PathBuf; - -#[test] -fn empty() { - let actual = path_name(&PathBuf::new()); - let expected = OsStringDisplay::os_string_from("."); - assert_eq!(actual, expected); -} - -#[test] -fn current_dir() { - let actual = path_name(&PathBuf::from(".")); - let expected = OsStringDisplay::os_string_from("."); - assert_eq!(actual, expected); -} - -#[cfg(unix)] -#[test] -fn root_dir() { - let actual = path_name(&PathBuf::from("/")); - let expected = OsStringDisplay::os_string_from("/"); - assert_eq!(actual, expected); -} - -#[cfg(windows)] -#[test] -fn root_dir() { - let actual = path_name(&PathBuf::from(r"C:\")); - let expected = OsStringDisplay::os_string_from(r"C:\"); - assert_eq!(actual, expected); -} - -#[cfg(windows)] -#[test] -fn prefix() { - let actual = path_name(&PathBuf::from(r"\\prefix")); - let expected = OsStringDisplay::os_string_from("prefix"); - assert_eq!(actual, expected); -} - -#[cfg(unix)] -#[test] -fn normal_relative() { - let actual = path_name(&PathBuf::from("abc/def/ghi")); - let expected = OsStringDisplay::os_string_from("ghi"); - assert_eq!(actual, expected); -} - -#[cfg(unix)] -#[test] -fn normal_absolute() { - let actual = path_name(&PathBuf::from("/abc/def/ghi")); - let expected = OsStringDisplay::os_string_from("ghi"); - assert_eq!(actual, expected); -} - -#[cfg(unix)] -#[test] -fn normal_trailing_separator() { - let actual = path_name(&PathBuf::from("abc/def/ghi/")); - let expected = OsStringDisplay::os_string_from("ghi"); - assert_eq!(actual, expected); -} - -#[cfg(unix)] -#[test] -fn parent_dir() { - let actual = path_name(&PathBuf::from("..")); - let expected = OsStringDisplay::os_string_from(".."); - assert_eq!(actual, expected); -} - -#[cfg(unix)] -#[test] -fn grandparent_dir() { - let actual = path_name(&PathBuf::from("../..")); - let expected = OsStringDisplay::os_string_from("../.."); - assert_eq!(actual, expected); -} diff --git a/tests/_utils.rs b/tests/_utils.rs index 01d56b9c..f94a0bc4 100644 --- a/tests/_utils.rs +++ b/tests/_utils.rs @@ -144,10 +144,12 @@ where .into_reflection() }; + let sub = |suffix: &str| root.join(suffix).pipe(OsStringDisplay::os_string_from); + assert_eq!( measure("flat"), sanitize_tree_reflection(DataTreeReflection { - name: OsStringDisplay::os_string_from("flat"), + name: sub("flat"), data: suffix_size!("flat", "flat/0", "flat/1", "flat/2", "flat/3"), children: vec![ DataTreeReflection { @@ -177,7 +179,7 @@ where assert_eq!( measure("nested"), sanitize_tree_reflection(DataTreeReflection { - name: OsStringDisplay::os_string_from("nested"), + name: sub("nested"), data: suffix_size!("nested", "nested/0", "nested/0/1"), children: vec![DataTreeReflection { name: OsStringDisplay::os_string_from("0"), @@ -194,7 +196,7 @@ where assert_eq!( measure("empty-dir"), sanitize_tree_reflection(DataTreeReflection { - name: OsStringDisplay::os_string_from("empty-dir"), + name: sub("empty-dir"), data: suffix_size!("empty-dir"), children: Vec::new(), }),