rust_bf/commands/
write.rs1use 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 #[arg(long = "bytes")]
12 pub bytes: bool,
13
14 #[arg(short = 'f', long = "file")]
16 pub file: Option<String>,
17
18 #[arg(value_name = "TEXT", trailing_var_arg = true)]
20 pub text: Vec<String>,
21
22 #[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}