rust_bf/commands/
write.rs

1use clap::Args;
2use std::fs;
3use std::io::{self, Read, Write};
4
5use crate::BrainfuckWriter;
6
7#[derive(Args, Debug)]
8#[command(disable_help_flag = true)]
9pub struct WriteArgs {
10    /// Treat input as raw bytes (no UTF-8 required)
11    #[arg(long = "bytes")]
12    pub bytes: bool,
13
14    /// Read input from file at PATH (otherwise reads from TEXT or STDIN)
15    #[arg(short = 'f', long = "file")]
16    pub file: Option<String>,
17
18    /// Positional text (UTF-8). If omitted, reads from STDIN.
19    #[arg(value_name = "TEXT", trailing_var_arg = true)]
20    pub text: Vec<String>,
21
22    /// Show this help
23    #[arg(short = 'h', long = "help", action = clap::ArgAction::SetTrue)]
24    pub help: bool,
25}
26
27pub fn run(program: &str, args: WriteArgs) -> i32 {
28    if args.help {
29        usage_and_exit(program, 0);
30    }
31
32    let WriteArgs {
33        bytes,
34        file,
35        text,
36        ..
37    } = args;
38
39    if file.is_some() && !text.is_empty() {
40        eprintln!("{program}: cannot use positional TEXT together with --file");
41        usage_and_exit(program, 2);
42    }
43
44    let input_bytes: Vec<u8> = match file {
45        Some(path) => {
46            if bytes {
47                match fs::read(&path) {
48                    Ok(b) => b,
49                    Err(e) => {
50                        eprintln!("{program}: failed to read file: {e}");
51                        let _ = io::stderr().flush();
52                        return 1;
53                    }
54                }
55            } else {
56                match fs::read_to_string(&path) {
57                    Ok(s) => s.into_bytes(),
58                    Err(e) => {
59                        eprintln!(
60                            "{program}: failed to read file as UTF-8 (use --bytes for binary): {e}"
61                        );
62                        let _ = io::stderr().flush();
63                        return 1;
64                    }
65                }
66            }
67        }
68        None => {
69            if !text.is_empty() {
70                text.join(" ").into_bytes()
71            } else if bytes {
72                let mut buf = Vec::new();
73                if let Err(e) = io::stdin().lock().read_to_end(&mut buf) {
74                    eprintln!("{program}: failed reading stdin: {e}");
75                    let _ = io::stderr().flush();
76                    return 1;
77                }
78                buf
79            } else {
80                let mut s = String::new();
81                if let Err(e) = io::stdin().read_to_string(&mut s) {
82                    eprintln!(
83                        "{program}: failed reading UTF-8 from stdin (use --bytes for binary): {e}"
84                    );
85                    let _ = io::stderr().flush();
86                    return 1;
87                }
88                s.into_bytes()
89            }
90        }
91    };
92
93    let writer = BrainfuckWriter::new(&input_bytes);
94    match writer.generate() {
95        Ok(code) => {
96            println!("{}", code);
97            let _ = io::stdout().flush();
98            0
99        }
100        Err(err) => {
101            eprintln!("{program}: error generating Brainfuck: {:?}", err);
102            let _ = io::stderr().flush();
103            1
104        }
105    }
106}
107
108fn usage_and_exit(program: &str, code: i32) -> ! {
109    eprintln!(
110        r#"Usage:
111  {0} write [--bytes] [TEXT...]           # Read UTF-8 TEXT args (preferred) or from STDIN if no TEXT is given
112  {0} write [--bytes] --file <PATH>       # Read from file instead of STDIN
113
114Options:
115  --file,  -f <PATH>  Read input from file at PATH (otherwise reads from TEXT or STDIN)
116  --bytes             Treat input as raw bytes (no UTF-8 required)
117  --help,   -h        Show this help
118
119Description:
120  Generates Brainfuck code that, when executed, will output the provided input bytes.
121
122Input modes:
123  - Default (string-like): expects UTF-8 text from positional TEXT, STDIN, or file and uses its bytes.
124  - --bytes (byte-like): reads raw bytes from STDIN or file; positional TEXT is still accepted as UTF-8 and used as bytes.
125
126Notes:
127  - Output is Brainfuck code printed to stdout followed by a newline.
128"#,
129        program
130    );
131    let _ = io::stderr().flush();
132    std::process::exit(code);
133}