Skip to content

Commit

Permalink
CSV output
Browse files Browse the repository at this point in the history
Fix #42
  • Loading branch information
Canop committed Mar 15, 2022
1 parent d7dec23 commit bb9e496
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 7 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### next
- with `--csv`, the table is written in CSV. The `--csv-separator` argument lets you change the separator. Filters, sorting, and column choices work for CSV output too - Fix #42

<a name="v2.4.0"></a>
### v2.4.0 - 2022/03/04
- 'unreachable' information available in JSON and in the table (in the 'use' column). This mostly concerns disconnected remote filesystems.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lfs"
version = "2.4.0"
version = "2.5.0-dev"
authors = ["dystroy <denys.seguret@gmail.com>"]
edition = "2021"
keywords = ["linux", "filesystem", "fs"]
Expand All @@ -9,7 +9,7 @@ categories = ["filesystem", "command-line-utilities"]
description = "give information on mounted filesystems"
repository = "https://github.com/Canop/lfs"
readme = "README.md"
rust-version = "1.56"
rust-version = "1.59"

[dependencies]
argh = "0.1.7"
Expand All @@ -21,6 +21,9 @@ serde = "1.0"
serde_json = "1.0"
termimad = "0.20.0"

[profile.release]
strip = true

[patch.crates-io]
# minimad = { path = "../minimad" }
# termimad = { path = "../termimad" }
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@ Complete documentation lives at **[https://dystroy.org/lfs](https://dystroy.org/

![screenshot](website/docs/img/json-jq-tour.png)

(you can output the table as CSV too)

### Filters

![screenshot](website/docs/img/filters.png)

### Sort

![screenshot](website/docs/img/s=free-d.png)



1 change: 0 additions & 1 deletion compile-all-targets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ echo " build cleaned"
target="x86_64-linux"
echo -e "${H2}Compiling the linux version - $target${EH}"
cargo build --release
strip target/release/lfs
mkdir "build/$target/"
cp target/release/lfs "build/$target/"

Expand Down
8 changes: 8 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ pub struct Args {
#[argh(option, default = "Default::default()", short = 's')]
pub sort: Sorting,

/// output as CSV
#[argh(switch)]
pub csv: bool,

/// CSV separator (default: ',')
#[argh(option, default = "','")]
pub csv_separator: char,

/// output as JSON
#[argh(switch, short = 'j')]
pub json: bool,
Expand Down
4 changes: 2 additions & 2 deletions src/col.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ col_enum!(
Remote "remote" "rem": "remote",
Disk "disk" "dsk": "disk" default,
Used "used": "used" default,
Use "use": "use%" default,
Use "use": "use" default,
UsePercent "use_percent": "use%",
Free "free": "free" default,
Size "size": "size" default,
InodesUsed "inodes_used" "iused": "used inodes",
InodesUse "inodes" "ino" "inodes_use" "iuse": "inodes%",
InodesUse "inodes" "ino" "inodes_use" "iuse": "inodes",
InodesUsePercent "inodes_use_percent" "iuse_percent": "inodes%",
InodesFree "inodes_free" "ifree": "free inodes",
InodesCount "inodes_total" "inodes_count" "itotal": "inodes total",
Expand Down
2 changes: 1 addition & 1 deletion src/col_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl ColExpr {
&self.value,
),
Col::Label => self.operator.eval_option_str(
mount.fs_label.as_ref().map(|s| s.as_str()),
mount.fs_label.as_deref(),
&self.value,
),
Col::Type => self.operator.eval_str(
Expand Down
103 changes: 103 additions & 0 deletions src/csv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use {
crate::{
Args, col::Col,
},
lfs_core::*,
std::{
fmt::Display,
io::Write,
},
};

/// Utility to write in CSV
struct Csv<W: Write> {
separator: char,
w: W,
}

impl<W: Write> Csv<W> {
pub fn new(separator: char, w: W) -> Self {
Self { separator, w }
}
pub fn cell<D: Display>(&mut self, content: D) -> Result<(), std::io::Error> {
let s = content.to_string();
let needs_quotes = s.contains(self.separator) || s.contains('"') || s.contains('\n');
if needs_quotes {
write!(self.w, "\"")?;
for c in s.chars() {
if c == '"' {
write!(self.w, "\"\"")?;
} else {
write!(self.w, "{}", c)?;
}
}
write!(self.w, "\"")?;
} else {
write!(self.w, "{}", s)?;
}
write!(self.w, "{}", self.separator)
}
pub fn cell_opt<D: Display>(&mut self, content: Option<D>) -> Result<(), std::io::Error> {
if let Some(c) = content {
self.cell(c)
} else {
write!(self.w, "{}", self.separator)
}
}
pub fn end_line(&mut self) -> Result<(), std::io::Error> {
writeln!(self.w)
}
}

pub fn print(mounts: &[&Mount], args: &Args) -> Result<(), std::io::Error> {
let units = args.units;
let mut csv = Csv::new(args.csv_separator, std::io::stdout());
for col in args.cols.cols() {
csv.cell(col.title())?;
}
csv.end_line()?;
for mount in mounts {
for col in args.cols.cols() {
match col {
Col::Id => csv.cell(&mount.info.id),
Col::Dev => csv.cell(format!("{}:{}", mount.info.dev.major, mount.info.dev.minor)),
Col::Filesystem => csv.cell(&mount.info.fs),
Col::Label => csv.cell_opt(mount.fs_label.as_ref()),
Col::Type => csv.cell(&mount.info.fs_type),
Col::Remote => csv.cell(if mount.info.is_remote() { "yes" } else { "no" }),
Col::Disk => csv.cell_opt(mount.disk.as_ref().map(|d| d.disk_type())),
Col::Used => csv.cell_opt(mount.stats().map(|s| units.fmt(s.used()))),
Col::Use => csv.cell_opt(mount.stats().map(|s| s.use_share())),
Col::UsePercent => csv.cell_opt(mount.stats().map(|s| format!("{:.0}%", 100.0 * s.use_share()))),
Col::Free => csv.cell_opt(mount.stats().map(|s| units.fmt(s.available()))),
Col::Size => csv.cell_opt(mount.stats().map(|s| units.fmt(s.size()))),
Col::InodesUsed => csv.cell_opt(mount.inodes().map(|i| i.used())),
Col::InodesUse => csv.cell_opt(mount.inodes().map(|i| i.use_share())),
Col::InodesUsePercent => csv.cell_opt(mount.inodes().map(|i| format!("{:.0}%", 100.0 * i.use_share()))),
Col::InodesFree => csv.cell_opt(mount.inodes().map(|i| i.favail)),
Col::InodesCount => csv.cell_opt(mount.inodes().map(|i| i.files)),
Col::MountPoint => csv.cell(&mount.info.mount_point.to_string_lossy()),
}?;
}
csv.end_line()?;
}
Ok(())
}

#[test]
fn test_csv() {
use std::io::Cursor;
let mut w = Cursor::new(Vec::new());
let mut csv = Csv::new(';', &mut w);
csv.cell("1;2;3").unwrap();
csv.cell("\"").unwrap();
csv.cell("").unwrap();
csv.end_line().unwrap();
csv.cell(3).unwrap();
let s = String::from_utf8(w.into_inner()).unwrap();
assert_eq!(
s,
r#""1;2;3";"""";;
3;"#,
);
}
5 changes: 5 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod args;
mod col;
mod col_expr;
mod cols;
mod csv;
mod filter;
mod json;
mod list_cols;
Expand Down Expand Up @@ -66,6 +67,10 @@ fn main() {
return;
}
};
if args.csv {
csv::print(&mounts, &args).expect("writing csv failed");
return;
}
if args.json {
println!(
"{}",
Expand Down
Binary file added website/docs/img/csv.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions website/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ The default display of **lfs** is a table, which can be configured with the colu

See [Table](./table) for the definition of the columns and the syntax for choosing them, or on how to [sort rows](./table#sort).

The table can also be exported in [CSV](./table#csv).

# JSON

`lfs --json` outputs the result as JSON which can be used for your own scripts or programs.
Expand Down
20 changes: 20 additions & 0 deletions website/docs/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,23 @@ For example, sorting on the device id:
Or sorting on the remaining free space, in descending order:

![screen](img/s=free-d.png)

# CSV

With the `--csv` argument, you can ask lfs to output the table in CSV:

```bash
lfs --csv > mounts.csv
```

You may choose the separator with the `--csv-separator` argument.

Filters, sorting, and column selection work the same than for standard tables so you may do this:

```bash
lfs --csv -f 'size>100G' -c remote+default+inodes > mounts.csv
```
which would give something like this:

![screen](img/csv.png)

0 comments on commit bb9e496

Please sign in to comment.