diff --git a/CHANGELOG.md b/CHANGELOG.md index 81552fbc8..27ba4396b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +* #693 Add progress bar + ## [0.19.0] - 2023-08-23 diff --git a/docs/book/src/referenz/kommandos/cat.md b/docs/book/src/referenz/kommandos/cat.md index 1c3254372..b86fd3410 100644 --- a/docs/book/src/referenz/kommandos/cat.md +++ b/docs/book/src/referenz/kommandos/cat.md @@ -42,6 +42,10 @@ $ pica cat ger.dat eng.dat -o ger_eng.dat * `--tee ` — Abzweigen der Ausgabe in eine zusätzliche Datei. * `-g`, `--gzip` — Komprimieren der Ausgabe im [gzip](https://de.wikipedia.org/wiki/Gzip)-Format. +* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der + eingelesenen gültigen sowie invaliden Datensätze anzeigt. Das + Aktivieren der Option erfordert das Schreiben der Datensätze in eine + Datei mittels `-o` bzw. `--output`. * `-o`, `--output` — Angabe, in welche Datei die Ausgabe geschrieben werden soll. Standardmäßig wird die Ausgabe in die Standardausgabe `stdout` geschrieben. Endet der Dateiname mit dem Suffix `.gz`, wird diff --git a/docs/book/src/referenz/kommandos/convert.md b/docs/book/src/referenz/kommandos/convert.md index 6eff40ac4..9fa8ebabb 100644 --- a/docs/book/src/referenz/kommandos/convert.md +++ b/docs/book/src/referenz/kommandos/convert.md @@ -42,11 +42,12 @@ $ pica convert --from plus --to xml DUMP.dat.gz -o dump.xml * `-s`, `--skip-invalid` — überspringt jene Zeilen aus der Eingabe, die nicht dekodiert werden konnten. - * `-f`, `--from` — Auswahl des Datenformats der Eingabe. - * `-t`, `--to` — Auswahl des Datenformats der Ausgabe. - +* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der + eingelesenen gültigen sowie invaliden Datensätze anzeigt. Das + Aktivieren der Option erfordert das Schreiben der Datensätze in eine + Datei mittels `-o` bzw. `--output`. * `-o`, `--output` — Angabe, in welche Datei die Ausgabe geschrieben werden soll. Standardmäßig wird die Ausgabe in die Standardausgabe `stdout` geschrieben. diff --git a/docs/book/src/referenz/kommandos/count.md b/docs/book/src/referenz/kommandos/count.md index 2b8eb7ac9..16c8ddce4 100644 --- a/docs/book/src/referenz/kommandos/count.md +++ b/docs/book/src/referenz/kommandos/count.md @@ -41,6 +41,10 @@ subfields: 3973 * `--csv` — die Ausgabe erfolgt im CSV-Format. * `--tsv` — die Ausgabe erfolgt im TSV-Format. * `--no-header` — es wird keine Kopfzeile in die Ausgabe geschrieben. +* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der + eingelesenen gültigen sowie invaliden Datensätze anzeigt. Das + Aktivieren der Option erfordert das Schreiben der Datensätze in eine + Datei mittels `-o` bzw. `--output`. * `-o`, `--output` — Angabe, in welche Datei die Ausgabe geschrieben werden soll. Standardmäßig wird die Ausgabe in die Standardausgabe `stdout` geschrieben. diff --git a/docs/book/src/referenz/kommandos/filter.md b/docs/book/src/referenz/kommandos/filter.md index 355faf8bc..7d4d2acd3 100644 --- a/docs/book/src/referenz/kommandos/filter.md +++ b/docs/book/src/referenz/kommandos/filter.md @@ -78,13 +78,16 @@ $ pica print goethe.dat Ausgabe an die Datei angehangen. Ist das Flag nicht gesetzt, wird eine bestehende Datei überschrieben. * `--tee ` — Abzweigen der Ausgabe in eine zusätzliche Datei. +* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der + eingelesenen gültigen sowie invaliden Datensätze anzeigt. Das + Aktivieren der Option erfordert das Schreiben der Datensätze in eine + Datei mittels `-o` bzw. `--output`. * `-o`, `--output` — Angabe, in welche Datei die Ausgabe geschrieben werden soll. Standardmäßig wird die Ausgabe in die Standardausgabe `stdout` geschrieben. Endet der Dateiname mit dem Suffix `.gz`, wird die Ausgabe automatisch im gzip-Format komprimiert. - ## Konfiguration diff --git a/docs/book/src/referenz/kommandos/frequency.md b/docs/book/src/referenz/kommandos/frequency.md index ef6987c53..64f7b3280 100644 --- a/docs/book/src/referenz/kommandos/frequency.md +++ b/docs/book/src/referenz/kommandos/frequency.md @@ -43,6 +43,10 @@ Ts1,1 * `-t`, `--tsv` — Ausgabe erfolgt im TSV-Format. * `--translit` `` — Ausgabe wird in die angegebene Normalform transliteriert. Mögliche Werte: `nfd`, `nfkd`, `nfc` und `nfkc`. +* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der + eingelesenen gültigen sowie invaliden Datensätze anzeigt. Das + Aktivieren der Option erfordert das Schreiben der Datensätze in eine + Datei mittels `-o` bzw. `--output`. * `-o`, `--output` — Angabe, in welche Datei die Ausgabe geschrieben werden soll. Standardmäßig wird die Ausgabe in die Standardausgabe `stdout` geschrieben. diff --git a/docs/book/src/referenz/kommandos/hash.md b/docs/book/src/referenz/kommandos/hash.md index 4353a7aa1..4d082f2c3 100644 --- a/docs/book/src/referenz/kommandos/hash.md +++ b/docs/book/src/referenz/kommandos/hash.md @@ -35,6 +35,10 @@ idn,sha256 * `-H`, `--header` `
` — Kopfzeile, die den Ergebnissen vorangestellt wird. * `-t`, `--tsv` — Ausgabe erfolgt im TSV-Format. +* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der + eingelesenen gültigen sowie invaliden Datensätze anzeigt. Das + Aktivieren der Option erfordert das Schreiben der Datensätze in eine + Datei mittels `-o` bzw. `--output`. * `-o`, `--output` — Angabe, in welche Datei die Ausgabe geschrieben werden soll. diff --git a/docs/book/src/referenz/kommandos/invalid.md b/docs/book/src/referenz/kommandos/invalid.md index 71656450e..12cf8f58a 100644 --- a/docs/book/src/referenz/kommandos/invalid.md +++ b/docs/book/src/referenz/kommandos/invalid.md @@ -24,6 +24,10 @@ $ pica invalid DUMP.dat.gz -o invalid.dat ## Optionen +* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der + eingelesenen gültigen sowie invaliden Datensätze anzeigt. Das + Aktivieren der Option erfordert das Schreiben der Datensätze in eine + Datei mittels `-o` bzw. `--output`. * `-o`, `--output` — Angabe, in welche Datei die Ausgabe geschrieben werden soll. Standardmäßig wird die Ausgabe in die Standardausgabe `stdout` geschrieben. diff --git a/docs/book/src/referenz/kommandos/partition.md b/docs/book/src/referenz/kommandos/partition.md index db65ddf8a..976120540 100644 --- a/docs/book/src/referenz/kommandos/partition.md +++ b/docs/book/src/referenz/kommandos/partition.md @@ -41,6 +41,8 @@ out * `-g`, `--gzip` — Komprimieren der Ausgabe im [Gzip]-Format. * `-t`, `--template` — Template für die Dateinamen. Der Platzhalter `{}` wird durch den Namen der Partition ersetzt. +* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der + eingelesenen gültigen sowie invaliden Datensätze anzeigt. * `-o`, `--outdir` — Angabe, in welches Verzeichnis die Partitionen geschrieben werden sollen. Standardmäßig wird das aktuelle Verzeichnis verwendet. diff --git a/docs/book/src/referenz/kommandos/print.md b/docs/book/src/referenz/kommandos/print.md index 86883b06a..a72cfa80b 100644 --- a/docs/book/src/referenz/kommandos/print.md +++ b/docs/book/src/referenz/kommandos/print.md @@ -55,6 +55,10 @@ $ pica print -s -l1 DUMP.dat.gz unterstützt oder die Umgebungsvariable `NO_COLOR` definiert ist. Schließlich wird mit der Einstellung `never` die Farbausgabe deaktiviert. +* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der + eingelesenen gültigen sowie invaliden Datensätze anzeigt. Das + Aktivieren der Option erfordert das Schreiben der Datensätze in eine + Datei mittels `-o` bzw. `--output`. * `-o`, `--output` — Angabe, in welche Datei die Ausgabe geschrieben werden soll. Standardmäßig wird die Ausgabe in die Standardausgabe `stdout` geschrieben. diff --git a/docs/book/src/referenz/kommandos/slice.md b/docs/book/src/referenz/kommandos/slice.md index 1746f0a97..829adbf0f 100644 --- a/docs/book/src/referenz/kommandos/slice.md +++ b/docs/book/src/referenz/kommandos/slice.md @@ -45,6 +45,10 @@ $ pica count --records slice.dat * `--append` — Wenn die Ausgabedatei bereits existiert, wird die Ausgabe an die Datei angehangen. Ist das Flag nicht gesetzt, wird eine bestehende Datei standardmäßig überschrieben. +* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der + eingelesenen gültigen sowie invaliden Datensätze anzeigt. Das + Aktivieren der Option erfordert das Schreiben der Datensätze in eine + Datei mittels `-o` bzw. `--output`. * `-o`, `--output` — Angabe, in welche Datei die Ausgabe geschrieben werden soll. Standardmäßig wird die Ausgabe in die Standardausgabe `stdout` geschrieben. diff --git a/docs/book/src/referenz/kommandos/split.md b/docs/book/src/referenz/kommandos/split.md index 934c830f4..c36b6d080 100644 --- a/docs/book/src/referenz/kommandos/split.md +++ b/docs/book/src/referenz/kommandos/split.md @@ -37,6 +37,8 @@ $ tree * `-g`, `--gzip` — Komprimieren der Ausgabe im [Gzip]-Format. * `--template` — Template für die Dateinamen. Der Platzhalter `{}` wird durch eine fortlaufende Nummer ersetzt. +* `-p`, `--progress` — Anzeige des Fortschritts, der die Anzahl der + eingelesenen gültigen sowie invaliden Datensätze anzeigt. * `-o`, `--outdir` — Angabe, in welches Verzeichnis die Ausgabe geschrieben werden soll. Standardmäßig wird das aktuelle Verzeichnis verwendet. diff --git a/pica-toolkit/Cargo.toml b/pica-toolkit/Cargo.toml index 5f4393e87..31121d9a3 100644 --- a/pica-toolkit/Cargo.toml +++ b/pica-toolkit/Cargo.toml @@ -15,6 +15,7 @@ clap_complete = { workspace = true } csv = { workspace = true } directories = { version = "5.0" } flate2 = { workspace = true } +indicatif = { version = "0.17" } nom = { workspace = true } pica-matcher = { workspace = true } pica-path = { workspace = true } diff --git a/pica-toolkit/src/commands/cat.rs b/pica-toolkit/src/commands/cat.rs index 6d5c02888..70510a674 100644 --- a/pica-toolkit/src/commands/cat.rs +++ b/pica-toolkit/src/commands/cat.rs @@ -10,6 +10,7 @@ use pica_record::ByteRecord; use serde::{Deserialize, Serialize}; use crate::config::Config; +use crate::progress::Progress; use crate::util::CliResult; use crate::{gzip_flag, skip_invalid_flag}; @@ -84,6 +85,10 @@ pub(crate) struct Cat { #[arg(short, long, requires = "output")] gzip: bool, + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + /// Write output to instead of stdout #[arg(short, long)] output: Option, @@ -145,6 +150,8 @@ impl Cat { None => None, }; + let mut progress = Progress::new(self.progress); + for filename in self.filenames { let mut reader = ReaderBuilder::new().from_path(filename)?; @@ -153,12 +160,15 @@ impl Cat { match result { Err(e) => { if e.is_invalid_record() && skip_invalid { + progress.invalid(); continue; } else { return Err(e.into()); } } Ok(record) => { + progress.record(); + if self.unique { let k = key(&record); @@ -178,6 +188,7 @@ impl Cat { } } + progress.finish(); writer.finish()?; if let Some(ref mut writer) = tee_writer { writer.finish()?; diff --git a/pica-toolkit/src/commands/convert/mod.rs b/pica-toolkit/src/commands/convert/mod.rs index 6bd66d0b6..f059369bb 100644 --- a/pica-toolkit/src/commands/convert/mod.rs +++ b/pica-toolkit/src/commands/convert/mod.rs @@ -17,6 +17,7 @@ use self::import::ImportWriter; use self::json::JsonWriter; use self::plain::PlainWriter; use self::xml::XmlWriter; +use crate::progress::Progress; use crate::util::CliError; use crate::{skip_invalid_flag, CliResult, Config}; @@ -66,6 +67,10 @@ pub(crate) struct Convert { )] to: Format, + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + /// Write output to instead of stdout #[arg(short, long, value_name = "filename")] output: Option, @@ -101,6 +106,8 @@ impl Convert { Format::Xml => Box::new(XmlWriter::new(self.output)?), }; + let mut progress = Progress::new(self.progress); + for filename in self.filenames { let mut reader = ReaderBuilder::new().from_path(filename)?; @@ -109,19 +116,23 @@ impl Convert { match result { Err(e) => { if e.is_invalid_record() && skip_invalid { + progress.invalid(); continue; } else { return Err(e.into()); } } Ok(record) => { + progress.record(); writer.write_byte_record(&record)?; } } } } + progress.finish(); writer.finish()?; + Ok(()) } } diff --git a/pica-toolkit/src/commands/count.rs b/pica-toolkit/src/commands/count.rs index e8b7c7078..ddedbddde 100644 --- a/pica-toolkit/src/commands/count.rs +++ b/pica-toolkit/src/commands/count.rs @@ -7,6 +7,7 @@ use pica_record::io::{ReaderBuilder, RecordsIterator}; use serde::{Deserialize, Serialize}; use crate::config::Config; +use crate::progress::Progress; use crate::skip_invalid_flag; use crate::util::CliResult; @@ -55,6 +56,10 @@ pub(crate) struct Count { #[arg(long)] no_header: bool, + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + /// Write output to instead of stdout #[arg(short, long, value_name = "filename")] output: Option, @@ -88,6 +93,8 @@ impl Count { let mut fields = 0; let mut subfields = 0; + let mut progress = Progress::new(self.progress); + for filename in self.filenames { let mut reader = ReaderBuilder::new().from_path(filename)?; @@ -96,12 +103,15 @@ impl Count { match result { Err(e) => { if e.is_invalid_record() && skip_invalid { + progress.invalid(); continue; } else { return Err(e.into()); } } Ok(record) => { + progress.record(); + records += 1; fields += record.iter().len(); subfields += record @@ -135,6 +145,7 @@ impl Count { writeln!(writer, "subfields: {subfields}")?; } + progress.finish(); writer.flush()?; Ok(()) } diff --git a/pica-toolkit/src/commands/filter.rs b/pica-toolkit/src/commands/filter.rs index ca50d49d1..8128d6c3b 100644 --- a/pica-toolkit/src/commands/filter.rs +++ b/pica-toolkit/src/commands/filter.rs @@ -11,6 +11,7 @@ use pica_record::io::{ReaderBuilder, RecordsIterator, WriterBuilder}; use serde::{Deserialize, Serialize}; use crate::common::FilterList; +use crate::progress::Progress; use crate::translit::translit_maybe2; use crate::util::{CliError, CliResult}; use crate::{gzip_flag, skip_invalid_flag, Config}; @@ -122,6 +123,10 @@ pub(crate) struct Filter { #[arg(long, value_name = "filename", conflicts_with = "output")] tee: Option, + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + /// Write output to instead of stdout #[arg(short, long, value_name = "filename")] output: Option, @@ -231,6 +236,8 @@ impl Filter { FilterList::default() }; + let mut progress = Progress::new(self.progress); + let mut count = 0; let options = MatcherOptions::new() .strsim_threshold(self.strsim_threshold as f64 / 100.0) @@ -244,12 +251,15 @@ impl Filter { match result { Err(e) => { if e.is_invalid_record() && skip_invalid { + progress.invalid(); continue; } else { return Err(e.into()); } } Ok(mut record) => { + progress.record(); + if !allow_list.is_empty() && !allow_list.check(&record) { @@ -315,6 +325,7 @@ impl Filter { } } + progress.finish(); writer.finish()?; if let Some(ref mut writer) = tee_writer { writer.finish()?; diff --git a/pica-toolkit/src/commands/frequency.rs b/pica-toolkit/src/commands/frequency.rs index bc52a9dc9..9aafa7ce0 100644 --- a/pica-toolkit/src/commands/frequency.rs +++ b/pica-toolkit/src/commands/frequency.rs @@ -11,6 +11,7 @@ use pica_select::{Query, QueryExt, QueryOptions}; use serde::{Deserialize, Serialize}; use crate::config::Config; +use crate::progress::Progress; use crate::skip_invalid_flag; use crate::translit::{translit_maybe, translit_maybe2}; use crate::util::CliResult; @@ -90,6 +91,10 @@ pub(crate) struct Frequency { )] translit: Option, + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + /// Write output to instead of stdout. #[arg(short, long, value_name = "filename")] output: Option, @@ -136,6 +141,8 @@ impl Frequency { .delimiter(if self.tsv { b'\t' } else { b',' }) .from_writer(writer); + let mut progress = Progress::new(self.progress); + for filename in self.filenames { let mut reader = ReaderBuilder::new().from_path(filename)?; @@ -144,12 +151,15 @@ impl Frequency { match result { Err(e) => { if e.is_invalid_record() && skip_invalid { + progress.invalid(); continue; } else { return Err(e.into()); } } Ok(record) => { + progress.record(); + let outcome = record.query(&query, &options); for key in outcome.clone().into_iter() { if key.iter().any(|e| !e.is_empty()) { @@ -199,6 +209,7 @@ impl Frequency { writer.write_record(record)?; } + progress.finish(); writer.flush()?; Ok(()) } diff --git a/pica-toolkit/src/commands/hash.rs b/pica-toolkit/src/commands/hash.rs index 92e229fdd..5f2e58c13 100644 --- a/pica-toolkit/src/commands/hash.rs +++ b/pica-toolkit/src/commands/hash.rs @@ -9,6 +9,7 @@ use pica_record::io::{ReaderBuilder, RecordsIterator}; use serde::{Deserialize, Serialize}; use crate::config::Config; +use crate::progress::Progress; use crate::skip_invalid_flag; use crate::util::CliResult; @@ -34,6 +35,10 @@ pub(crate) struct Hash { #[arg(long, short)] tsv: bool, + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + /// Write output to instead of stdout #[arg(short, long)] output: Option, @@ -71,6 +76,8 @@ impl Hash { writer .write_record(self.header.split(',').map(|s| s.trim()))?; + let mut progress = Progress::new(self.progress); + for filename in self.filenames { let mut reader = ReaderBuilder::new().from_path(filename)?; @@ -79,12 +86,15 @@ impl Hash { match result { Err(e) => { if e.is_invalid_record() && skip_invalid { + progress.invalid(); continue; } else { return Err(e.into()); } } Ok(record) => { + progress.record(); + if let Some(idn) = record.idn() { let hash = record.sha256().iter().fold( String::new(), @@ -104,7 +114,9 @@ impl Hash { } } + progress.finish(); writer.flush()?; + Ok(()) } } diff --git a/pica-toolkit/src/commands/invalid.rs b/pica-toolkit/src/commands/invalid.rs index d92746adc..bdbbd87e0 100644 --- a/pica-toolkit/src/commands/invalid.rs +++ b/pica-toolkit/src/commands/invalid.rs @@ -6,6 +6,7 @@ use pica_record::io::{ReadPicaError, ReaderBuilder, RecordsIterator}; use pica_record::ParsePicaError; use crate::config::Config; +use crate::progress::Progress; use crate::util::CliResult; /// Write input lines, which can't be decoded as normalized PICA+ @@ -15,6 +16,10 @@ use crate::util::CliResult; /// order. #[derive(Parser, Debug)] pub(crate) struct Invalid { + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + /// Write output to instead of stdout #[arg(short, long, value_name = "filename")] output: Option, @@ -27,6 +32,7 @@ pub(crate) struct Invalid { impl Invalid { pub(crate) fn run(self, config: &Config) -> CliResult<()> { let mut writer = config.writer(self.output)?; + let mut progress = Progress::new(self.progress); for filename in self.filenames { let mut reader = @@ -38,15 +44,21 @@ impl Invalid { msg: _, err: ParsePicaError::InvalidRecord(data), }) => { + progress.invalid(); writer.write_all(&data)?; } Err(e) => return Err(e.into()), - _ => continue, + _ => { + progress.record(); + continue; + } } } } + progress.finish(); writer.flush()?; + Ok(()) } } diff --git a/pica-toolkit/src/commands/partition.rs b/pica-toolkit/src/commands/partition.rs index 659f62de1..6620f4892 100644 --- a/pica-toolkit/src/commands/partition.rs +++ b/pica-toolkit/src/commands/partition.rs @@ -14,6 +14,7 @@ use pica_record::io::{ use serde::{Deserialize, Serialize}; use crate::config::Config; +use crate::progress::Progress; use crate::util::CliResult; use crate::{gzip_flag, skip_invalid_flag, template_opt}; @@ -51,6 +52,10 @@ pub(crate) struct Partition { #[arg(long, short)] gzip: bool, + /// Show progress bar + #[arg(short, long)] + progress: bool, + /// Write partitions into /// /// If the directory doesn't exists, it will be created @@ -100,6 +105,8 @@ impl Partition { let mut writers: HashMap, Box> = HashMap::new(); + let mut progress = Progress::new(self.progress); + for filename in self.filenames { let mut reader = ReaderBuilder::new().from_path(filename)?; @@ -108,12 +115,15 @@ impl Partition { match result { Err(e) => { if e.is_invalid_record() && skip_invalid { + progress.invalid(); continue; } else { return Err(e.into()); } } Ok(record) => { + progress.record(); + let mut values = record.path(&path, &Default::default()); values.sort_unstable(); @@ -155,6 +165,8 @@ impl Partition { } } + progress.finish(); + for (_, mut writer) in writers { writer.finish()?; } diff --git a/pica-toolkit/src/commands/print.rs b/pica-toolkit/src/commands/print.rs index 2650c96f2..2a3f81a43 100644 --- a/pica-toolkit/src/commands/print.rs +++ b/pica-toolkit/src/commands/print.rs @@ -12,6 +12,7 @@ use termcolor::{ }; use crate::config::Config; +use crate::progress::Progress; use crate::skip_invalid_flag; use crate::translit::translit_maybe; use crate::util::{CliError, CliResult}; @@ -96,6 +97,10 @@ pub(crate) struct Print { )] color: String, + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + /// Write output to instead of stdout #[arg(short, long, value_name = "filename")] output: Option, @@ -159,6 +164,7 @@ impl Print { None => Box::new(StandardStream::stdout(choice)), }; + let mut progress = Progress::new(self.progress); let mut count = 0; 'outer: for filename in self.filenames { @@ -169,12 +175,15 @@ impl Print { match result { Err(e) => { if e.is_invalid_record() && skip_invalid { + progress.invalid(); continue; } else { return Err(e.into()); } } Ok(record) => { + progress.record(); + for field in record.iter() { writer.set_color(&tag_color)?; write!(writer, "{}", field.tag())?; @@ -215,7 +224,9 @@ impl Print { } } + progress.finish(); writer.flush()?; + Ok(()) } } diff --git a/pica-toolkit/src/commands/sample.rs b/pica-toolkit/src/commands/sample.rs index c043b938c..9383dd7d9 100644 --- a/pica-toolkit/src/commands/sample.rs +++ b/pica-toolkit/src/commands/sample.rs @@ -8,6 +8,7 @@ use rand::{thread_rng, Rng, SeedableRng}; use serde::{Deserialize, Serialize}; use crate::config::Config; +use crate::progress::Progress; use crate::util::CliResult; use crate::{gzip_flag, skip_invalid_flag}; @@ -30,6 +31,10 @@ pub(crate) struct Sample { #[arg(long, short)] gzip: bool, + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + /// Write output to instead of stdout #[arg(short, long, value_name = "filename")] output: Option, @@ -72,6 +77,7 @@ impl Sample { let mut reservoir: Vec> = Vec::with_capacity(sample_size); + let mut progress = Progress::new(self.progress); let mut i = 0; for filename in self.filenames { @@ -82,12 +88,15 @@ impl Sample { match result { Err(e) => { if e.is_invalid_record() && skip_invalid { + progress.invalid(); continue; } else { return Err(e.into()); } } Ok(record) => { + progress.record(); + let mut data = Vec::::new(); record.write_to(&mut data)?; @@ -111,7 +120,9 @@ impl Sample { writer.write_byte_record(&record)?; } + progress.finish(); writer.finish()?; + Ok(()) } } diff --git a/pica-toolkit/src/commands/select.rs b/pica-toolkit/src/commands/select.rs index 149b2e694..7a45951f8 100644 --- a/pica-toolkit/src/commands/select.rs +++ b/pica-toolkit/src/commands/select.rs @@ -15,6 +15,7 @@ use serde::{Deserialize, Serialize}; use crate::common::FilterList; use crate::config::Config; +use crate::progress::Progress; use crate::skip_invalid_flag; use crate::translit::{translit_maybe, translit_maybe2}; use crate::util::CliResult; @@ -136,6 +137,10 @@ pub(crate) struct Select { #[arg(long, short = 'D')] deny_list: Vec, + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + /// Write output to instead of stdout #[arg(short, long, value_name = "filename")] output: Option, @@ -253,6 +258,8 @@ impl Select { writer.write_record(header.split(',').map(|s| s.trim()))?; } + let mut progess = Progress::new(self.progress); + for filename in self.filenames { let mut reader = ReaderBuilder::new().from_path(filename)?; @@ -261,12 +268,15 @@ impl Select { match result { Err(e) => { if e.is_invalid_record() && skip_invalid { + progess.invalid(); continue; } else { return Err(e.into()); } } Ok(record) => { + progess.record(); + if !allow_list.is_empty() && !allow_list.check(&record) { @@ -331,6 +341,7 @@ impl Select { } } + progess.finish(); writer.flush()?; Ok(()) } diff --git a/pica-toolkit/src/commands/slice.rs b/pica-toolkit/src/commands/slice.rs index f66a8ba70..02ae931f1 100644 --- a/pica-toolkit/src/commands/slice.rs +++ b/pica-toolkit/src/commands/slice.rs @@ -5,6 +5,7 @@ use pica_record::io::{ReaderBuilder, RecordsIterator, WriterBuilder}; use serde::{Deserialize, Serialize}; use crate::config::Config; +use crate::progress::Progress; use crate::util::CliResult; use crate::{gzip_flag, skip_invalid_flag}; @@ -74,6 +75,10 @@ pub(crate) struct Slice { #[arg(long, short)] append: bool, + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + /// Write output to instead of stdout #[arg(short, long, value_name = "filename")] output: Option, @@ -108,6 +113,7 @@ impl Slice { self.start..::std::usize::MAX }; + let mut progress = Progress::new(self.progress); let mut i = 0; 'outer: for filename in self.filenames { @@ -117,6 +123,8 @@ impl Slice { while let Some(result) = reader.next() { match result { Ok(record) => { + progress.record(); + if range.contains(&i) { writer.write_byte_record(&record)?; } else if i < range.start { @@ -128,6 +136,8 @@ impl Slice { } Err(e) => { if e.is_invalid_record() && skip_invalid { + progress.invalid(); + if self.length > 0 && range.end < std::usize::MAX { @@ -143,7 +153,9 @@ impl Slice { } } + progress.finish(); writer.finish()?; + Ok(()) } } diff --git a/pica-toolkit/src/commands/split.rs b/pica-toolkit/src/commands/split.rs index b364ef3cf..550701ec3 100644 --- a/pica-toolkit/src/commands/split.rs +++ b/pica-toolkit/src/commands/split.rs @@ -7,6 +7,7 @@ use pica_record::io::{ReaderBuilder, RecordsIterator, WriterBuilder}; use serde::{Deserialize, Serialize}; use crate::config::Config; +use crate::progress::Progress; use crate::util::CliResult; use crate::{gzip_flag, skip_invalid_flag, template_opt}; @@ -34,6 +35,10 @@ pub(crate) struct Split { #[arg(long, short)] gzip: bool, + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long)] + progress: bool, + /// Write partitions into #[arg(long, short, value_name = "outdir", default_value = ".")] outdir: PathBuf, @@ -79,6 +84,7 @@ impl Split { let mut chunks: u32 = 0; let mut count = 0; + let mut progress = Progress::new(self.progress); let mut writer = WriterBuilder::new().gzip(gzip_compression).from_path( self.outdir @@ -98,12 +104,15 @@ impl Split { match result { Err(e) => { if e.is_invalid_record() && skip_invalid { + progress.invalid(); continue; } else { return Err(e.into()); } } Ok(record) => { + progress.record(); + if count > 0 && count as u32 % self.chunk_size == 0 { @@ -132,7 +141,9 @@ impl Split { } } + progress.finish(); writer.finish()?; + Ok(()) } } diff --git a/pica-toolkit/src/main.rs b/pica-toolkit/src/main.rs index f64ebb109..47506932b 100644 --- a/pica-toolkit/src/main.rs +++ b/pica-toolkit/src/main.rs @@ -4,11 +4,11 @@ extern crate regex; extern crate serde; extern crate termcolor; -// mod cli; mod commands; mod common; mod config; mod macros; +mod progress; mod translit; mod util; diff --git a/pica-toolkit/src/progress.rs b/pica-toolkit/src/progress.rs new file mode 100644 index 000000000..5ff421fc2 --- /dev/null +++ b/pica-toolkit/src/progress.rs @@ -0,0 +1,59 @@ +use indicatif::{HumanCount, ProgressBar, ProgressStyle}; + +pub(crate) struct Progress { + bar: ProgressBar, + records: u64, + invalid: u64, +} + +impl Progress { + pub(crate) fn new(enable: bool) -> Self { + let bar = if enable { + ProgressBar::new_spinner() + } else { + ProgressBar::hidden() + }; + + bar.set_style( + ProgressStyle::with_template( + "{spinner} {msg}, elapsed: {elapsed_precise}", + ) + .unwrap(), + ); + + Self { + bar, + records: 0, + invalid: 0, + } + } + + #[inline] + pub(crate) fn record(&mut self) { + self.records += 1; + self.update(); + } + + #[inline] + pub(crate) fn invalid(&mut self) { + self.invalid += 1; + self.update(); + } + + pub(crate) fn update(&mut self) { + self.bar.inc(1); + let per_sec = self.bar.per_sec(); + + self.bar.set_message(format!( + "records: {}, invalid: {} | {} records/s", + HumanCount(self.records), + HumanCount(self.invalid), + per_sec.round() as i64, + )); + } + + #[inline] + pub(crate) fn finish(&self) { + self.bar.finish(); + } +}