diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 27784a640..5426d526f 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -14,6 +14,7 @@ - [completions](./referenz/kommandos/completions.md) - [convert](./referenz/kommandos/convert.md) - [count](./referenz/kommandos/count.md) + - [explode](./referenz/kommandos/explode.md) - [filter](./referenz/kommandos/filter.md) - [frequency](./referenz/kommandos/frequency.md) - [hash](./referenz/kommandos/hash.md) diff --git a/docs/book/src/referenz/kommandos/explode.in/COPY.dat.gz b/docs/book/src/referenz/kommandos/explode.in/COPY.dat.gz new file mode 100644 index 000000000..eed04b28a Binary files /dev/null and b/docs/book/src/referenz/kommandos/explode.in/COPY.dat.gz differ diff --git a/docs/book/src/referenz/kommandos/explode.md b/docs/book/src/referenz/kommandos/explode.md new file mode 100644 index 000000000..5b8b7d8d5 --- /dev/null +++ b/docs/book/src/referenz/kommandos/explode.md @@ -0,0 +1,180 @@ +# `explode` + +![stability-badge](https://img.shields.io/badge/stability-unstable-red?style=flat-square) + +Mithilfe des `explode`-Kommandos lassen sich Datensätze in Lokal- bzw. +Exemplardatensätze aufteilen. + +> **Hinweis:** Das `explode`-Kommando befindet sich in der aktiven +> Entwicklung. Funktionalitäten können unvollständig oder fehlerhaft +> sein. Änderungen am _command-line interface_ (CLI) sind nicht +> ausgeschlossen. + +## Beschreibung + + + +Die Verarbeitung und Analyse von Datensätzen auf Lokal- bzw. +Exemplarebene ist mitunter nur unzureichend möglich, da Filterausdrücke +die Grenzen von untergeordneten Ebenen nicht respektiert. Abhilfe kann +das `explode`-Kommando schaffen, das einen Datensatz in einzelne Lokal- +bzw. Exemplardatensätze aufteilen kann. Dabei werden alle Felder der +übergeordneten Ebenen mit in die Datensätze übernommen. + +Das Aufteilen der Datensätze erfolgt durch die Angabe der Ebene +(_level_) an der der Datensatz geteilt werden soll. Es können folgende +Werte ausgewählt werden: + +* `main` (Aufteilen auf Ebene der Titeldaten), +* `local` (Aufteilen auf Ebene der Lokaldaten), +* sowie `copy` (Aufteilen auf Ebene der Exemplardaten). + +Sollen ein Datensatz in alle Lokaldatensätze aufgeteilt werden, muss die +Ebene `local` ausgewählt werden. Die neu erstellten Datensätze enthalten +alle Titeldaten (Felder der Ebene 0), den Identifikator des +Lokaldatensatzes (Feld `101@.a`) sowie alle Exemplare, die diesem +Lokaldatensatz zugeordnet werden. + +Soll darüber hinaus für jedes Exemplar ein eigenständiger Datensatz +erzeugt werden, muss die Ebene `copy` angegeben werden. Jeder erzeugte +Datensatz enthält die Titeldaten (Felder der Ebene 0), den Identifikator +des Lokaldatensatzes (Feld `101@.a`) und nur die Felder, die zum +jeweiligen Exemplar gehören. + +Schließlich kann ein Datensatz auch auf Ebene der Titeldaten (`main`) +aufgeteilt werden. Diese Aufwahl verändert nichts am Datensatz und gibt +den vollständigen Datensatz mit allen Feldern aus. + +Als Beispiel soll folgender (reduzierter) Datensatz dienen: + +```console +$ pica print COPY.dat.gz +003@ $0 123456789 +002@ $0 Abvz +101@ $a 1 +203@/01 $0 0123456789 +203@/02 $0 1234567890 +101@ $a 2 +203@/01 $0 345678901 + + +``` + +Dieser Datensatz lässt sich in zwei Datensätze auf Ebene der Lokaldaten +aufteilen: + +```console +$ pica explode -s local COPY.dat.gz -o local.dat +$ pica print local.dat +003@ $0 123456789 +002@ $0 Abvz +101@ $a 1 +203@/01 $0 0123456789 +203@/02 $0 1234567890 + +003@ $0 123456789 +002@ $0 Abvz +101@ $a 2 +203@/01 $0 345678901 + + +``` + +Soll jedes Exemplar ein eigenständiger Datensatz werden, wird dies durch +Angabe von `copy` erzielt: + +```console +$ pica explode -s copy COPY.dat.gz -o copy.dat +$ pica print copy.dat +003@ $0 123456789 +002@ $0 Abvz +101@ $a 1 +203@/01 $0 0123456789 + +003@ $0 123456789 +002@ $0 Abvz +101@ $a 1 +203@/02 $0 1234567890 + +003@ $0 123456789 +002@ $0 Abvz +101@ $a 2 +203@/01 $0 345678901 + + +``` + + +## Optionen + +* `-s`, `--skip-invalid` — Überspringt jene Zeilen aus der Eingabe, die + nicht dekodiert werden konnten. +* `-i`, `--ignore-case` — Groß- und Kleinschreibung wird bei Vergleichen + ignoriert. +* `--strsim-threshold ` — Festlegen des Schwellenwerts beim + Ähnlichkeitsvergleich von Zeichenketten mittels `=*`. +* `--where` `` — Angabe eines Filters, der auf die erzeugten + Datensätze angewandt wird. +* `--and` `` — Hinzufügen eines zusätzlichen Filters mittels der + booleschen `&&`-Verknüpfung. Der ursprüngliche Filterausdruck + `` wird zum Ausdruck ` && `. +* `--or` `` — Hinzufügen eines zusätzlichen Filters mittels der + booleschen `||`-Verknüpfung. Der ursprüngliche Filterausdruck + `` wird zum Ausdruck ` || `. +* `--not` `` — Hinzufügen eines zusätzlichen Filters. Der + ursprüngliche Filterausdruck `` wird zum Ausdruck ` && + !()`. +* `-g`, `--gzip` — Komprimieren der Ausgabe im [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 + die Ausgabe automatisch im gzip-Format komprimiert. + + +## Konfiguration + + + +Einige Kommandozeilen-Optionen lassen sich per Konfigurationsdatei +(`Pica.toml`) einstellen: + +```toml +[explode] +skip-invalid = true +gzip = true +``` + +## Beispiele + +### Eingrenzen der Datensätze + +Ist nur eine Teilmenge der erzeugten Datensätze von Interesse, lässt +sich die Ergebnismenge durch Hinzufügen eines Filterausdrucks +eingrenzen. + +Werden bspw. nur die Exemplare mit dem Identifikator `101@.a == "1"` +benötigt, kann die Eingrenzung durch Angabe der `--where`-Option +eingegrenzt werden: + +```console +$ pica explode -s copy --where '101@.a == "1"' COPY.dat.gz -o copy.dat +$ pica print copy.dat +003@ $0 123456789 +002@ $0 Abvz +101@ $a 1 +203@/01 $0 0123456789 + +003@ $0 123456789 +002@ $0 Abvz +101@ $a 1 +203@/02 $0 1234567890 + + +``` + + +[Gzip]: https://de.wikipedia.org/wiki/Gzip diff --git a/docs/book/src/referenz/kommandos/index.md b/docs/book/src/referenz/kommandos/index.md index 09d77bca9..bb2cb17ba 100644 --- a/docs/book/src/referenz/kommandos/index.md +++ b/docs/book/src/referenz/kommandos/index.md @@ -8,6 +8,8 @@ * [completions](./completions.md) — Erzeugung von Shell-Skripten zur Autovervollständigung * [count](./count.md) — Zählen von Datensätzen, Feldern und Unterfeldern +* [explode](./explode.md) — Teilt Datensätzen in Lokal- oder + Exemplardatensätze auf * [filter](./filter.md) — Filtert Datensätze anhand eines Kriteriums * [frequency](./frequency.md) — Ermitteln einer Häufigkeitsverteilung über ein oder mehrere Unterfelder diff --git a/pica-record/src/field.rs b/pica-record/src/field.rs index 52cf0cf19..d7f548ad1 100644 --- a/pica-record/src/field.rs +++ b/pica-record/src/field.rs @@ -14,7 +14,7 @@ use crate::occurrence::parse_occurrence; use crate::parser::{ParseResult, RS, SP}; use crate::subfield::parse_subfield; use crate::tag::parse_tag; -use crate::{Occurrence, ParsePicaError, Subfield, Tag}; +use crate::{Level, Occurrence, ParsePicaError, Subfield, Tag}; /// A PICA+ field. #[derive(Clone, Debug, PartialEq, Eq)] @@ -229,6 +229,11 @@ impl<'a, T: AsRef<[u8]> + From<&'a BStr> + Display> Field { } write!(out, "\x1e") } + + #[inline] + pub fn level(&self) -> Level { + self.tag.level() + } } impl<'a, T: AsRef<[u8]>> IntoIterator for &'a Field { diff --git a/pica-record/src/tag.rs b/pica-record/src/tag.rs index c4e7da95c..86408f02a 100644 --- a/pica-record/src/tag.rs +++ b/pica-record/src/tag.rs @@ -9,7 +9,7 @@ use nom::sequence::tuple; use nom::Finish; use crate::parser::ParseResult; -use crate::ParsePicaError; +use crate::{Level, ParsePicaError}; /// A PICA+ tag. #[derive(Eq, Debug, Clone)] @@ -76,6 +76,15 @@ impl<'a, T: AsRef<[u8]> + From<&'a BStr> + Display> Tag { pub fn from_unchecked(value: impl Into) -> Self { Self(value.into()) } + + pub fn level(&self) -> Level { + match self.as_ref().first().expect("valid tag") { + b'0' => Level::Main, + b'1' => Level::Local, + b'2' => Level::Copy, + _ => unreachable!(), + } + } } /// Parse a PICA+ tag. diff --git a/pica-toolkit/src/commands/explode.rs b/pica-toolkit/src/commands/explode.rs new file mode 100644 index 000000000..2fdd789d1 --- /dev/null +++ b/pica-toolkit/src/commands/explode.rs @@ -0,0 +1,328 @@ +use std::ffi::OsString; +use std::io; + +use clap::{value_parser, Parser}; +use pica_matcher::{MatcherOptions, RecordMatcher}; +use pica_record::io::{ + ByteRecordWrite, ReaderBuilder, RecordsIterator, WriterBuilder, +}; +use pica_record::{ByteRecord, Level}; +use serde::{Deserialize, Serialize}; + +use crate::config::Config; +use crate::progress::Progress; +use crate::translit::translit_maybe2; +use crate::util::CliResult; +use crate::{gzip_flag, skip_invalid_flag}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub(crate) struct ExplodeConfig { + /// Skip invalid records that can't be decoded. + pub(crate) skip_invalid: Option, + + /// Compress output in gzip format + pub(crate) gzip: Option, +} + +#[derive(Parser, Debug)] +pub(crate) struct Explode { + /// Skip invalid records that can't be decoded. + #[arg(short, long)] + skip_invalid: bool, + + /// Compress each partition in gzip format + #[arg(long, short)] + gzip: bool, + + /// A filter expression used for searching + #[arg(long = "where")] + filter: Option, + + /// Connects the where clause with additional expressions using the + /// logical AND-operator (conjunction) + /// + /// This option can't be combined with `--or` or `--not`. + #[arg(long, requires = "filter", conflicts_with_all = ["or", "not"])] + and: Vec, + + /// Connects the where clause with additional expressions using the + /// logical OR-operator (disjunction) + /// + /// This option can't be combined with `--and` or `--not`. + #[arg(long, requires = "filter", conflicts_with_all = ["and", "not"])] + or: Vec, + + /// Connects the where clause with additional expressions using the + /// logical NOT-operator (negation) + /// + /// This option can't be combined with `--and` or `--or`. + #[arg(long, requires = "filter", conflicts_with_all = ["and", "or"])] + not: Vec, + + /// When this flag is provided, comparison operations will be + /// search case insensitive + #[arg(long, short)] + ignore_case: bool, + + /// The minimum score for string similarity comparisons + /// (range: 0.0..1.0) + #[arg(long, value_parser = value_parser!(u8).range(0..100), + default_value = "75")] + strsim_threshold: u8, + + /// Show progress bar (requires `-o`/`--output`). + #[arg(short, long, requires = "output")] + progress: bool, + + /// Write output to instead of stdout + #[arg(short, long)] + output: Option, + + /// Split a record by level (main, local, copy). + level: Level, + + /// Read one or more files in normalized PICA+ format. If no + /// filenames where given or a filename is "-", data is read from + /// standard input (stdin) + #[arg(default_value = "-", hide_default_value = true)] + filenames: Vec, +} + +macro_rules! record_bytes { + ($fields:expr) => {{ + let mut buffer = Vec::::new(); + $fields.iter().for_each(|field| { + let _ = field.write_to(&mut buffer); + }); + buffer.push(b'\n'); + buffer + }}; +} + +macro_rules! push_record { + ($records:expr, $main:expr, $local:expr, $acc:expr) => { + if !$acc.is_empty() { + let mut record = $main.clone(); + if let Some(local) = $local { + record.push(local); + } + record.extend_from_slice(&$acc); + + $records.push(record); + $acc.clear(); + } + }; + + ($records:expr, $main:expr, $acc:expr) => { + if !$acc.is_empty() { + let mut record = $main.clone(); + record.extend_from_slice(&$acc); + $records.push(record); + $acc.clear(); + } + }; +} + +fn process_main( + record: &ByteRecord, + matcher: Option<&RecordMatcher>, + options: &MatcherOptions, + writer: &mut Box, +) -> io::Result<()> { + if let Some(matcher) = matcher { + if !matcher.is_match(record, options) { + return Ok(()); + } + } + + writer.write_byte_record(record) +} + +fn process_copy( + record: &ByteRecord, + matcher: Option<&RecordMatcher>, + options: &MatcherOptions, + writer: &mut Box, +) -> io::Result<()> { + let mut main = vec![]; + let mut acc = vec![]; + let mut records = vec![]; + let mut local = None; + let mut count = None; + + for field in record.iter() { + match field.level() { + Level::Main => main.push(field), + Level::Local => { + push_record!(records, main, local, acc); + local = Some(field); + count = None; + } + Level::Copy => { + if count != field.occurrence() { + push_record!(records, main, local, acc); + count = field.occurrence(); + } + + acc.push(field); + } + } + } + + push_record!(records, main, local, acc); + for fields in records { + let data = record_bytes!(fields); + let record = + ByteRecord::from_bytes(&data).expect("valid record"); + + if let Some(matcher) = matcher { + if !matcher.is_match(&record, options) { + continue; + } + } + + writer.write_byte_record(&record)?; + } + + Ok(()) +} + +fn process_local( + record: &ByteRecord, + matcher: Option<&RecordMatcher>, + options: &MatcherOptions, + writer: &mut Box, +) -> io::Result<()> { + let mut main = vec![]; + let mut acc = vec![]; + let mut records = vec![]; + + for field in record.iter() { + match field.level() { + Level::Main => main.push(field), + Level::Copy => acc.push(field), + Level::Local => { + push_record!(records, main, acc); + acc.push(field) + } + } + } + + push_record!(records, main, acc); + + for fields in records.iter() { + let data = record_bytes!(fields); + let record = ByteRecord::from_bytes(&data).unwrap(); + + if let Some(matcher) = matcher { + if !matcher.is_match(&record, options) { + continue; + } + } + + writer.write_byte_record(&record)?; + } + + Ok(()) +} + +impl Explode { + pub(crate) fn run(self, config: &Config) -> CliResult<()> { + let gzip_compression = gzip_flag!(self.gzip, config.explode); + let skip_invalid = skip_invalid_flag!( + self.skip_invalid, + config.explode, + config.global + ); + + let options = MatcherOptions::new() + .strsim_threshold(self.strsim_threshold as f64 / 100.0) + .case_ignore(self.ignore_case); + + let translit = if let Some(ref global) = config.global { + global.translit + } else { + None + }; + + let matcher = if let Some(matcher_str) = self.filter { + let mut matcher = RecordMatcher::new(&translit_maybe2( + &matcher_str, + translit, + ))?; + + for matcher_str in self.and.iter() { + matcher = matcher + & RecordMatcher::new(&translit_maybe2( + matcher_str, + translit, + ))?; + } + + for matcher_str in self.or.iter() { + matcher = matcher + | RecordMatcher::new(&translit_maybe2( + matcher_str, + translit, + ))?; + } + + for matcher_str in self.not.iter() { + matcher = matcher + & !RecordMatcher::new(&translit_maybe2( + matcher_str, + translit, + ))?; + } + + Some(matcher) + } else { + None + }; + + let mut progress = Progress::new(self.progress); + + let mut writer = WriterBuilder::new() + .gzip(gzip_compression) + .from_path_or_stdout(self.output)?; + + let process_record = match self.level { + Level::Main => process_main, + Level::Copy => process_copy, + Level::Local => process_local, + }; + + for filename in self.filenames { + let mut reader = + ReaderBuilder::new().from_path(filename)?; + + while let Some(result) = reader.next() { + match result { + Err(e) => { + if e.is_invalid_record() && skip_invalid { + progress.invalid(); + continue; + } else { + return Err(e.into()); + } + } + Ok(record) => { + progress.record(); + process_record( + &record, + matcher.as_ref(), + &options, + &mut writer, + )?; + } + } + } + } + + progress.record(); + writer.finish()?; + + Ok(()) + } +} diff --git a/pica-toolkit/src/commands/mod.rs b/pica-toolkit/src/commands/mod.rs index 6d2e9b007..a95349b24 100644 --- a/pica-toolkit/src/commands/mod.rs +++ b/pica-toolkit/src/commands/mod.rs @@ -2,6 +2,7 @@ mod cat; mod completions; mod convert; mod count; +mod explode; mod filter; mod frequency; mod hash; @@ -17,6 +18,7 @@ pub(crate) use cat::{Cat, CatConfig}; pub(crate) use completions::Completions; pub(crate) use convert::{Convert, ConvertConfig}; pub(crate) use count::{Count, CountConfig}; +pub(crate) use explode::{Explode, ExplodeConfig}; pub(crate) use filter::{Filter, FilterConfig}; pub(crate) use frequency::{Frequency, FrequencyConfig}; pub(crate) use hash::{Hash, HashConfig}; diff --git a/pica-toolkit/src/config.rs b/pica-toolkit/src/config.rs index 3d907cf42..e9f272f54 100644 --- a/pica-toolkit/src/config.rs +++ b/pica-toolkit/src/config.rs @@ -30,12 +30,13 @@ pub(crate) struct GlobalConfig { pub(crate) struct Config { #[serde(skip)] pub(crate) path: Option, - pub(crate) global: Option, pub(crate) cat: Option, pub(crate) convert: Option, pub(crate) count: Option, + pub(crate) explode: Option, pub(crate) filter: Option, pub(crate) frequency: Option, + pub(crate) global: Option, pub(crate) hash: Option, pub(crate) partition: Option, pub(crate) print: Option, diff --git a/pica-toolkit/src/main.rs b/pica-toolkit/src/main.rs index 47506932b..a1de88270 100644 --- a/pica-toolkit/src/main.rs +++ b/pica-toolkit/src/main.rs @@ -17,8 +17,8 @@ use std::{io, process}; use clap::{CommandFactory, Parser, Subcommand}; use commands::{ - Cat, Completions, Convert, Count, Filter, Frequency, Hash, Invalid, - Partition, Print, Sample, Select, Slice, Split, + Cat, Completions, Convert, Count, Explode, Filter, Frequency, Hash, + Invalid, Partition, Print, Sample, Select, Slice, Split, }; use config::Config; use util::{CliError, CliResult}; @@ -45,6 +45,7 @@ enum Commands { Completions(Completions), Convert(Convert), Count(Count), + Explode(Explode), Filter(Filter), Frequency(Frequency), Hash(Hash), @@ -66,6 +67,7 @@ fn run() -> CliResult<()> { Commands::Completions(cmd) => cmd.run(&mut Cli::command()), Commands::Convert(cmd) => cmd.run(&config), Commands::Count(cmd) => cmd.run(&config), + Commands::Explode(cmd) => cmd.run(&config), Commands::Filter(cmd) => cmd.run(&config), Commands::Frequency(cmd) => cmd.run(&config), Commands::Hash(cmd) => cmd.run(&config), diff --git a/pica-toolkit/tests/snapshot.rs b/pica-toolkit/tests/snapshot.rs index 806a50545..676212b94 100644 --- a/pica-toolkit/tests/snapshot.rs +++ b/pica-toolkit/tests/snapshot.rs @@ -7,6 +7,8 @@ fn cli_tests() { .case("tests/snapshot/completions/*.trycmd") .case("tests/snapshot/count/*.toml") .case("tests/snapshot/count/*.trycmd") + .case("tests/snapshot/explode/*.toml") + .case("tests/snapshot/explode/*.trycmd") .case("tests/snapshot/filter/*.toml") .case("tests/snapshot/filter/*.trycmd") .case("tests/snapshot/frequency/*.toml") @@ -35,6 +37,7 @@ fn doc_tests() { .case("../docs/book/src/referenz/kommandos/count.md") .case("../docs/book/src/referenz/kommandos/filter.md") .case("../docs/book/src/referenz/kommandos/frequency.md") + .case("../docs/book/src/referenz/kommandos/explode.md") .case("../docs/book/src/referenz/kommandos/hash.md") .case("../docs/book/src/referenz/kommandos/invalid.md") .case("../docs/book/src/referenz/kommandos/partition.md") diff --git a/pica-toolkit/tests/snapshot/explode/000-explode-main.in/COPY.dat.gz b/pica-toolkit/tests/snapshot/explode/000-explode-main.in/COPY.dat.gz new file mode 100644 index 000000000..eed04b28a Binary files /dev/null and b/pica-toolkit/tests/snapshot/explode/000-explode-main.in/COPY.dat.gz differ diff --git a/pica-toolkit/tests/snapshot/explode/000-explode-main.out/main.dat b/pica-toolkit/tests/snapshot/explode/000-explode-main.out/main.dat new file mode 100644 index 000000000..f913dc7b5 --- /dev/null +++ b/pica-toolkit/tests/snapshot/explode/000-explode-main.out/main.dat @@ -0,0 +1 @@ +003@ 0123456789002@ 0Abvz101@ a1203@/01 00123456789203@/02 01234567890101@ a2203@/01 0345678901 diff --git a/pica-toolkit/tests/snapshot/explode/000-explode-main.toml b/pica-toolkit/tests/snapshot/explode/000-explode-main.toml new file mode 100644 index 000000000..710618f15 --- /dev/null +++ b/pica-toolkit/tests/snapshot/explode/000-explode-main.toml @@ -0,0 +1,5 @@ +bin.name = "pica" +args = "explode main COPY.dat.gz -o main.dat" +status = "success" +stdout = "" +stderr = "" diff --git a/pica-toolkit/tests/snapshot/explode/001-explode-local.in/COPY.dat.gz b/pica-toolkit/tests/snapshot/explode/001-explode-local.in/COPY.dat.gz new file mode 100644 index 000000000..eed04b28a Binary files /dev/null and b/pica-toolkit/tests/snapshot/explode/001-explode-local.in/COPY.dat.gz differ diff --git a/pica-toolkit/tests/snapshot/explode/001-explode-local.out/local.dat b/pica-toolkit/tests/snapshot/explode/001-explode-local.out/local.dat new file mode 100644 index 000000000..908919ba8 --- /dev/null +++ b/pica-toolkit/tests/snapshot/explode/001-explode-local.out/local.dat @@ -0,0 +1,2 @@ +003@ 0123456789002@ 0Abvz101@ a1203@/01 00123456789203@/02 01234567890 +003@ 0123456789002@ 0Abvz101@ a2203@/01 0345678901 diff --git a/pica-toolkit/tests/snapshot/explode/001-explode-local.toml b/pica-toolkit/tests/snapshot/explode/001-explode-local.toml new file mode 100644 index 000000000..1bfa1ace2 --- /dev/null +++ b/pica-toolkit/tests/snapshot/explode/001-explode-local.toml @@ -0,0 +1,5 @@ +bin.name = "pica" +args = "explode local COPY.dat.gz -o local.dat" +status = "success" +stdout = "" +stderr = "" diff --git a/pica-toolkit/tests/snapshot/explode/002-explode-copy.in/COPY.dat.gz b/pica-toolkit/tests/snapshot/explode/002-explode-copy.in/COPY.dat.gz new file mode 100644 index 000000000..eed04b28a Binary files /dev/null and b/pica-toolkit/tests/snapshot/explode/002-explode-copy.in/COPY.dat.gz differ diff --git a/pica-toolkit/tests/snapshot/explode/002-explode-copy.out/copy.dat b/pica-toolkit/tests/snapshot/explode/002-explode-copy.out/copy.dat new file mode 100644 index 000000000..6e2b9340c --- /dev/null +++ b/pica-toolkit/tests/snapshot/explode/002-explode-copy.out/copy.dat @@ -0,0 +1,3 @@ +003@ 0123456789002@ 0Abvz101@ a1203@/01 00123456789 +003@ 0123456789002@ 0Abvz101@ a1203@/02 01234567890 +003@ 0123456789002@ 0Abvz101@ a2203@/01 0345678901 diff --git a/pica-toolkit/tests/snapshot/explode/002-explode-copy.toml b/pica-toolkit/tests/snapshot/explode/002-explode-copy.toml new file mode 100644 index 000000000..67df6e6e6 --- /dev/null +++ b/pica-toolkit/tests/snapshot/explode/002-explode-copy.toml @@ -0,0 +1,5 @@ +bin.name = "pica" +args = "explode copy COPY.dat.gz -o copy.dat" +status = "success" +stdout = "" +stderr = "" diff --git a/pica-toolkit/tests/snapshot/explode/003-explode-where.in/COPY.dat.gz b/pica-toolkit/tests/snapshot/explode/003-explode-where.in/COPY.dat.gz new file mode 100644 index 000000000..eed04b28a Binary files /dev/null and b/pica-toolkit/tests/snapshot/explode/003-explode-where.in/COPY.dat.gz differ diff --git a/pica-toolkit/tests/snapshot/explode/003-explode-where.out/1.dat b/pica-toolkit/tests/snapshot/explode/003-explode-where.out/1.dat new file mode 100644 index 000000000..455e27d7b --- /dev/null +++ b/pica-toolkit/tests/snapshot/explode/003-explode-where.out/1.dat @@ -0,0 +1,2 @@ +003@ 0123456789002@ 0Abvz101@ a1203@/01 00123456789 +003@ 0123456789002@ 0Abvz101@ a1203@/02 01234567890 diff --git a/pica-toolkit/tests/snapshot/explode/003-explode-where.toml b/pica-toolkit/tests/snapshot/explode/003-explode-where.toml new file mode 100644 index 000000000..dc39f2836 --- /dev/null +++ b/pica-toolkit/tests/snapshot/explode/003-explode-where.toml @@ -0,0 +1,5 @@ +bin.name = "pica" +args = "explode copy --where '101@.a == \"1\"' COPY.dat.gz -o 1.dat" +status = "success" +stdout = "" +stderr = ""