sack is fully implemented
[pspp] / rust / tests / sack.rs
1 use std::fs::read_to_string;
2 use std::path::PathBuf;
3
4 use anyhow::{anyhow, Result};
5 use clap::Parser;
6 use pspp::endian::Endian;
7 use pspp::sack::sack;
8
9 /// SAv Construction Kit
10 ///
11 /// The input is a sequence of data items, each followed by a semicolon.  Each
12 /// data item is converted to the output format and written on stdout.  A data
13 /// item is one of the following:
14 ///
15 ///   - An integer in decimal, in hexadecimal prefixed by `0x`, or in octal
16 ///     prefixed by `0`.  Output as a 32-bit binary integer.
17 ///
18 ///   - A floating-point number.  Output in 64-bit IEEE 754 format.
19 ///
20 ///   - A string enclosed in double quotes.  Output literally.  There is no
21 ///     syntax for "escapes".  Strings may not contain new-lines.
22 ///
23 ///   - A literal of the form `s<number>` followed by a quoted string as above.
24 ///     Output as the string's contents followed by enough spaces to fill up
25 ///     `<number>` bytes.  For example, `s8 "foo"` is output as `foo` followed
26 ///     by 5 spaces.
27 ///
28 ///   - The literal `i8`, `i16`, or `i64` followed by an integer.  Output
29 ///     as a binary integer with the specified number of bits.
30 ///
31 ///   - One of the literals `SYSMIS`, `LOWEST`, or `HIGHEST`.  Output as a
32 ///     64-bit IEEE 754 float of the appropriate PSPP value.
33 ///
34 ///   - `PCSYSMIS`.  Output as SPSS/PC+ system-missing value.
35 ///
36 ///   - The literal `ENDIAN`.  Output as a 32-bit binary integer, either with
37 ///     value 1 if `--be` is in effect or 2 if `--le` is in effect.
38 ///
39 ///   - A pair of parentheses enclosing a sequence of data items, each followed
40 ///     by a semicolon (the last semicolon is optional).  Output as the enclosed
41 ///     data items in sequence.
42 ///
43 ///   - The literal `COUNT` or `COUNT8` followed by a sequence of parenthesized
44 ///     data items, as above.  Output as a 32-bit or 8-bit binary integer whose
45 ///     value is the number of bytes enclosed within the parentheses, followed
46 ///     by the enclosed data items themselves.
47 ///
48 /// optionally followed by an asterisk and a positive integer, which specifies a
49 /// repeat count for the data item.
50 #[derive(Parser, Debug)]
51 struct Args {
52     /// Big-endian output format (default)
53     #[arg(long = "be")]
54     be: bool,
55
56     /// Little-endian output format
57     #[arg(long = "le")]
58     le: bool,
59
60     /// Input file.
61     #[arg(required = true, name = "input")]
62     input_file_name: PathBuf,
63
64     /// Output file.
65     #[arg(required = true, name = "output")]
66     output_file_name: PathBuf,
67 }
68
69 fn main() -> Result<()> {
70     let Args {
71         be,
72         le,
73         input_file_name,
74         output_file_name,
75     } = Args::parse();
76     let endian = match (be, le) {
77         (false, false) | (true, false) => Endian::Big,
78         (false, true) => Endian::Little,
79         (true, true) => return Err(anyhow!("can't use both `--be` and `--le`")),
80     };
81
82     let input_file_str = input_file_name.to_string_lossy();
83     let input = read_to_string(&input_file_name)
84         .map_err(|err| anyhow!("{input_file_str}: read failed ({err})"))?;
85
86     let output = sack(&input, Some(&input_file_str), endian)?;
87
88     let output_file_str = output_file_name.to_string_lossy();
89     std::fs::write(&output_file_name, &output)
90         .map_err(|err| anyhow!("{output_file_str}: write failed ({err})"))?;
91
92     Ok(())
93 }