rain_metadata/cli/
generate.rs1use std::path::PathBuf;
2use std::io::{self, Read};
3use std::fs;
4use clap::{Args, Subcommand};
5
6use crate::error::Error;
7use crate::metaboard::{DotrainSourceEmitData, generate_dotrain_source_emit_tx_data};
8
9#[derive(Args)]
11pub struct Generate {
12 #[command(subcommand)]
13 pub command: GenerateCommand,
14}
15
16#[derive(Subcommand)]
18pub enum GenerateCommand {
19 Source(SourceArgs),
21}
22
23#[derive(Args)]
25pub struct SourceArgs {
26 #[arg(short = 'i', long = "input-path")]
28 input_path: Option<PathBuf>,
29
30 #[arg(short = 'o', long = "output-path")]
32 output_path: Option<PathBuf>,
33}
34
35fn read_input_content(input_path: Option<PathBuf>) -> Result<String, Error> {
37 match input_path {
38 Some(path) => {
39 fs::read_to_string(&path).map_err(|e| {
41 Error::InvalidInput(format!("Failed to read file '{}': {}", path.display(), e))
42 })
43 }
44 None => {
45 let mut buffer = String::new();
47 io::stdin()
48 .read_to_string(&mut buffer)
49 .map_err(|e| Error::InvalidInput(format!("Failed to read from stdin: {}", e)))?;
50 Ok(buffer)
51 }
52 }
53}
54
55fn write_output(data: &DotrainSourceEmitData, output_path: Option<PathBuf>) -> Result<(), Error> {
57 let json_output = serde_json::to_string_pretty(data).map_err(Error::SerdeJsonError)?;
59
60 match output_path {
61 Some(path) => {
62 if let Some(parent) = path.parent() {
64 if !parent.as_os_str().is_empty() {
65 if let Err(e) = fs::create_dir_all(parent) {
66 return Err(Error::InvalidInput(format!(
67 "Failed to create output directory '{}': {}",
68 parent.display(),
69 e
70 )));
71 }
72 }
73 }
74 fs::write(&path, json_output).map_err(|e| {
76 Error::InvalidInput(format!("Failed to write file '{}': {}", path.display(), e))
77 })
78 }
79 None => {
80 println!("{}", json_output);
82 Ok(())
83 }
84 }
85}
86
87pub fn generate(args: Generate) -> anyhow::Result<()> {
89 match args.command {
90 GenerateCommand::Source(source_args) => generate_source(source_args),
91 }
92}
93
94fn generate_source(args: SourceArgs) -> anyhow::Result<()> {
96 let content = read_input_content(args.input_path)?;
98
99 let tx_data = generate_dotrain_source_emit_tx_data(&content)?;
100
101 write_output(&tx_data, args.output_path)?;
103
104 Ok(())
105}
106
107#[cfg(all(test, not(target_family = "wasm")))]
108mod tests {
109 use super::*;
110 use std::io::Write;
111 use tempfile::NamedTempFile;
112
113 #[test]
114 fn test_read_input_content_from_file() {
115 let mut temp_file = NamedTempFile::new().unwrap();
117 let test_content = "#main _ _: int-add(1 2)";
118 writeln!(temp_file, "{}", test_content).unwrap();
119
120 let content = read_input_content(Some(temp_file.path().to_path_buf())).unwrap();
121 assert_eq!(content.trim(), test_content);
122 }
123
124 #[test]
125 fn test_read_input_content_nonexistent_file() {
126 let result = read_input_content(Some(PathBuf::from("/nonexistent/file.rain")));
127 assert!(result.is_err());
128 }
129
130 #[test]
131 fn test_write_output_to_file() {
132 let deployment_data = DotrainSourceEmitData {
133 subject: "0x1234567890abcdef".to_string(),
134 meta_bytes: "0xdeadbeef".to_string(),
135 calldata: "0xcafebabe".to_string(),
136 };
137
138 let temp_file = NamedTempFile::new().unwrap();
139 let temp_path = temp_file.path().to_path_buf();
140
141 write_output(&deployment_data, Some(temp_path.clone())).unwrap();
142
143 let written_content = fs::read_to_string(&temp_path).unwrap();
144 assert!(written_content.contains("0x1234567890abcdef"));
145 assert!(written_content.contains("0xdeadbeef"));
146 assert!(written_content.contains("0xcafebabe"));
147 }
148
149 #[test]
150 fn test_full_generate_flow() {
151 let mut input_file = NamedTempFile::new().unwrap();
153 let test_content = "#main _ _: int-add(1 2) int-add(2 3)";
154 writeln!(input_file, "{}", test_content).unwrap();
155
156 let output_file = NamedTempFile::new().unwrap();
158 let output_path = output_file.path().to_path_buf();
159
160 let args = Generate {
162 command: GenerateCommand::Source(SourceArgs {
163 input_path: Some(input_file.path().to_path_buf()),
164 output_path: Some(output_path.clone()),
165 }),
166 };
167
168 generate(args).unwrap();
169
170 let output_content = fs::read_to_string(&output_path).unwrap();
172 let parsed: DotrainSourceEmitData = serde_json::from_str(&output_content).unwrap();
173
174 assert!(parsed.subject.starts_with("0x"));
175 assert!(parsed.meta_bytes.starts_with("0x"));
176 assert!(parsed.calldata.starts_with("0x"));
177 assert_eq!(parsed.subject.len(), 66); }
179}