Skip to content

Commit 67c9bbd

Browse files
authored
Merge pull request #23 from KSXGitHub/json
JSON import/export
2 parents c9065c7 + 6ff7743 commit 67c9bbd

19 files changed

+541
-31
lines changed

Cargo.lock

+25
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+10-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ required-features = ["cli-completions"]
4343

4444
[features]
4545
default = ["cli"]
46-
cli = ["structopt", "structopt-utilities"]
46+
json = ["serde/derive", "serde_json"]
47+
cli = ["structopt", "structopt-utilities", "json"]
4748
cli-completions = ["cli"]
4849

4950
[dependencies]
@@ -67,6 +68,14 @@ optional = true
6768
version = "^0.0.8"
6869
optional = true
6970

71+
[dependencies.serde]
72+
version = "^1.0.126"
73+
optional = true
74+
75+
[dependencies.serde_json]
76+
version = "^1.0.64"
77+
optional = true
78+
7079
[dependencies.strum]
7180
version = "^0.20.0"
7281
features = ["derive"]

exports/completion.bash

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ _pdu() {
2020

2121
case "${cmd}" in
2222
pdu)
23-
opts=" -h -V --top-down --no-sort --silent-errors --progress --help --version --bytes-format --quantity --max-depth --total-width --column-width --min-ratio <files>... "
23+
opts=" -h -V --json-input --json-output --top-down --no-sort --silent-errors --progress --help --version --bytes-format --quantity --max-depth --total-width --column-width --min-ratio <files>... "
2424
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
2525
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
2626
return 0

exports/completion.elv

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ edit:completion:arg-completer[pdu] = [@words]{
2121
cand --total-width 'Width of the visualization'
2222
cand --column-width 'Maximum widths of the tree column and width of the bar column'
2323
cand --min-ratio 'Minimal size proportion required to appear'
24+
cand --json-input 'Read JSON data from stdin'
25+
cand --json-output 'Print JSON data instead of an ASCII chart'
2426
cand --top-down 'Print the tree top-down instead of bottom-up'
2527
cand --no-sort 'Preserve order of entries'
2628
cand --silent-errors 'Prevent filesystem error messages from appearing in stderr'

exports/completion.fish

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ complete -c pdu -n "__fish_use_subcommand" -l max-depth -d 'Maximum depth to dis
44
complete -c pdu -n "__fish_use_subcommand" -l total-width -d 'Width of the visualization'
55
complete -c pdu -n "__fish_use_subcommand" -l column-width -d 'Maximum widths of the tree column and width of the bar column'
66
complete -c pdu -n "__fish_use_subcommand" -l min-ratio -d 'Minimal size proportion required to appear'
7+
complete -c pdu -n "__fish_use_subcommand" -l json-input -d 'Read JSON data from stdin'
8+
complete -c pdu -n "__fish_use_subcommand" -l json-output -d 'Print JSON data instead of an ASCII chart'
79
complete -c pdu -n "__fish_use_subcommand" -l top-down -d 'Print the tree top-down instead of bottom-up'
810
complete -c pdu -n "__fish_use_subcommand" -l no-sort -d 'Preserve order of entries'
911
complete -c pdu -n "__fish_use_subcommand" -l silent-errors -d 'Prevent filesystem error messages from appearing in stderr'

exports/completion.ps1

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Register-ArgumentCompleter -Native -CommandName 'pdu' -ScriptBlock {
2626
[CompletionResult]::new('--total-width', 'total-width', [CompletionResultType]::ParameterName, 'Width of the visualization')
2727
[CompletionResult]::new('--column-width', 'column-width', [CompletionResultType]::ParameterName, 'Maximum widths of the tree column and width of the bar column')
2828
[CompletionResult]::new('--min-ratio', 'min-ratio', [CompletionResultType]::ParameterName, 'Minimal size proportion required to appear')
29+
[CompletionResult]::new('--json-input', 'json-input', [CompletionResultType]::ParameterName, 'Read JSON data from stdin')
30+
[CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'Print JSON data instead of an ASCII chart')
2931
[CompletionResult]::new('--top-down', 'top-down', [CompletionResultType]::ParameterName, 'Print the tree top-down instead of bottom-up')
3032
[CompletionResult]::new('--no-sort', 'no-sort', [CompletionResultType]::ParameterName, 'Preserve order of entries')
3133
[CompletionResult]::new('--silent-errors', 'silent-errors', [CompletionResultType]::ParameterName, 'Prevent filesystem error messages from appearing in stderr')

exports/completion.zsh

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ _pdu() {
2121
'(--column-width)--total-width=[Width of the visualization]' \
2222
'*--column-width=[Maximum widths of the tree column and width of the bar column]' \
2323
'--min-ratio=[Minimal size proportion required to appear]' \
24+
'(--quantity)--json-input[Read JSON data from stdin]' \
25+
'--json-output[Print JSON data instead of an ASCII chart]' \
2426
'--top-down[Print the tree top-down instead of bottom-up]' \
2527
'--no-sort[Preserve order of entries]' \
2628
'--silent-errors[Prevent filesystem error messages from appearing in stderr]' \

src/app.rs

+48-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ pub use sub::Sub;
44

55
use crate::{
66
args::{Args, Quantity},
7+
json_data::JsonData,
78
reporter::{ErrorOnlyReporter, ErrorReport, ProgressAndErrorReporter, ProgressReport},
89
runtime_error::RuntimeError,
910
size::{Bytes, Size},
1011
size_getters::GET_APPARENT_SIZE,
11-
visualizer::Direction,
12+
visualizer::{Direction, Visualizer},
1213
};
13-
use std::time::Duration;
14+
use pipe_trait::Pipe;
15+
use std::{io::stdin, time::Duration};
1416
use structopt_utilities::StructOptUtils;
1517

1618
#[cfg(unix)]
@@ -44,6 +46,48 @@ impl App {
4446

4547
let column_width_distribution = self.args.column_width_distribution();
4648

49+
if self.args.json_input {
50+
if !self.args.files.is_empty() {
51+
return Err(RuntimeError::JsonInputArgConflict);
52+
}
53+
54+
let Args {
55+
bytes_format,
56+
top_down,
57+
max_depth,
58+
..
59+
} = self.args;
60+
let direction = Direction::from_top_down(top_down);
61+
62+
let json_data = stdin()
63+
.pipe(serde_json::from_reader::<_, JsonData>)
64+
.map_err(RuntimeError::DeserializationFailure)?;
65+
66+
macro_rules! visualize {
67+
($reflection:expr, $bytes_format: expr) => {{
68+
let data_tree = $reflection
69+
.par_try_into_tree()
70+
.map_err(|error| RuntimeError::InvalidInputReflection(error.to_string()))?;
71+
Visualizer {
72+
data_tree: &data_tree,
73+
bytes_format: $bytes_format,
74+
column_width_distribution,
75+
direction,
76+
max_depth,
77+
}
78+
.to_string()
79+
}};
80+
}
81+
82+
let visualization = match json_data {
83+
JsonData::Bytes(reflection) => visualize!(reflection, bytes_format),
84+
JsonData::Blocks(reflection) => visualize!(reflection, ()),
85+
};
86+
87+
print!("{}", visualization); // it already ends with "\n", println! isn't needed here.
88+
return Ok(());
89+
}
90+
4791
let report_error = if self.args.silent_errors {
4892
ErrorReport::SILENT
4993
} else {
@@ -80,6 +124,7 @@ impl App {
80124
quantity: Quantity::$quantity,
81125
progress: $progress,
82126
files,
127+
json_output,
83128
bytes_format,
84129
top_down,
85130
max_depth,
@@ -94,6 +139,7 @@ impl App {
94139
reporter: $create_reporter::<$data>(report_error),
95140
bytes_format: $format(bytes_format),
96141
files,
142+
json_output,
97143
column_width_distribution,
98144
max_depth,
99145
min_ratio,

src/app/sub.rs

+21-4
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
use crate::{
22
args::Fraction,
3-
data_tree::DataTree,
3+
data_tree::{DataTree, DataTreeReflection},
44
fs_tree_builder::FsTreeBuilder,
5+
json_data::JsonData,
56
os_string_display::OsStringDisplay,
67
reporter::ParallelReporter,
78
runtime_error::RuntimeError,
89
size::Size,
910
status_board::GLOBAL_STATUS_BOARD,
1011
visualizer::{ColumnWidthDistribution, Direction, Visualizer},
1112
};
12-
use std::{fs::Metadata, iter::once, num::NonZeroUsize, path::PathBuf};
13+
use serde::Serialize;
14+
use std::{fs::Metadata, io::stdout, iter::once, num::NonZeroUsize, path::PathBuf};
1315

1416
/// The sub program of the main application.
1517
pub struct Sub<Data, GetData, Report>
1618
where
17-
Data: Size + Into<u64> + Send + Sync,
19+
Data: Size + Into<u64> + Serialize + Send + Sync,
1820
Report: ParallelReporter<Data> + Sync,
1921
GetData: Fn(&Metadata) -> Data + Copy + Sync,
22+
DataTreeReflection<String, Data>: Into<JsonData>,
2023
{
2124
/// List of files and/or directories.
2225
pub files: Vec<PathBuf>,
26+
/// Print JSON data instead of an ASCII chart.
27+
pub json_output: bool,
2328
/// Format to be used to [`display`](Size::display) the data.
2429
pub bytes_format: Data::DisplayFormat,
2530
/// The direction of the visualization.
@@ -40,14 +45,16 @@ where
4045

4146
impl<Data, GetData, Report> Sub<Data, GetData, Report>
4247
where
43-
Data: Size + Into<u64> + Send + Sync,
48+
Data: Size + Into<u64> + Serialize + Send + Sync,
4449
Report: ParallelReporter<Data> + Sync,
4550
GetData: Fn(&Metadata) -> Data + Copy + Sync,
51+
DataTreeReflection<String, Data>: Into<JsonData>,
4652
{
4753
/// Run the sub program.
4854
pub fn run(self) -> Result<(), RuntimeError> {
4955
let Sub {
5056
files,
57+
json_output,
5158
bytes_format,
5259
direction,
5360
column_width_distribution,
@@ -108,6 +115,16 @@ where
108115
data_tree
109116
};
110117

118+
if json_output {
119+
let json_data: JsonData = data_tree
120+
.into_reflection() // I really want to use std::mem::transmute here but can't.
121+
.par_convert_names_to_utf8() // TODO: allow non-UTF8 somehow.
122+
.expect("convert all names from raw string to UTF-8")
123+
.into();
124+
return serde_json::to_writer(stdout(), &json_data)
125+
.map_err(RuntimeError::SerializationFailure);
126+
}
127+
111128
let visualizer = Visualizer {
112129
data_tree: &data_tree,
113130
bytes_format,

src/args.rs

+14
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,27 @@ use text_block_macros::text_block;
4848
""
4949
" Only show disk usage chart of entries whose size is at least 5% of total"
5050
" $ pdu --min-ratio=0.05"
51+
""
52+
" Show disk usage data as JSON instead of chart"
53+
" $ pdu --min-ratio=0 --json-output | jq"
54+
""
55+
" Visualize existing JSON representation of disk usage data"
56+
" $ pdu --min-ratio=0 < disk-usage.json"
5157
},
5258
)]
5359
pub struct Args {
5460
/// List of files and/or directories.
5561
#[structopt(name = "files")]
5662
pub files: Vec<PathBuf>,
5763

64+
/// Read JSON data from stdin.
65+
#[structopt(long, conflicts_with = "quantity")]
66+
pub json_input: bool,
67+
68+
/// Print JSON data instead of an ASCII chart.
69+
#[structopt(long)]
70+
pub json_output: bool,
71+
5872
/// How to display the numbers of bytes.
5973
#[structopt(long, possible_values = BytesFormat::VARIANTS, default_value = BytesFormat::default_value())]
6074
pub bytes_format: BytesFormat,

0 commit comments

Comments
 (0)