1mod dto;
2mod error;
3mod est;
4mod live;
5mod midi;
6mod mos;
7mod mts;
8mod portable;
9mod scala;
10mod scale;
11
12use std::{
13 fmt::{self, Display},
14 fs::File,
15 io::{self, ErrorKind, Write},
16 path::PathBuf,
17};
18
19use clap::Parser;
20use error::ResultExt;
21use est::EstOptions;
22use futures::executor;
23use io::Read;
24use live::LiveOptions;
25use mos::MosCommand;
26use mts::MtsOptions;
27use scala::{KbmCommand, SclOptions};
28use scale::{DiffOptions, DumpOptions, ScaleCommand};
29
30#[doc(hidden)]
31pub mod shared;
32
33#[derive(Parser)]
34#[command(version)]
35struct MainOptions {
36 #[arg(long = "of")]
38 output_file: Option<PathBuf>,
39
40 #[command(subcommand)]
41 command: MainCommand,
42}
43
44#[derive(Parser)]
45enum MainCommand {
46 #[command(name = "scl")]
48 Scl(SclOptions),
49
50 #[command(subcommand, name = "kbm")]
52 Kbm(KbmCommand),
53
54 #[command(name = "est")]
56 Est(EstOptions),
57
58 #[command(subcommand, name = "mos")]
60 Mos(MosCommand),
61
62 #[command(subcommand, name = "scale")]
64 Scale(ScaleCommand),
65
66 #[command(name = "dump")]
68 Dump(DumpOptions),
69
70 #[command(name = "diff")]
72 Diff(DiffOptions),
73
74 #[command(name = "mts")]
76 Mts(MtsOptions),
77
78 #[command(name = "live")]
82 Live(LiveOptions),
83
84 #[command(name = "devices")]
86 Devices,
87}
88
89impl MainOptions {
90 async fn run(self) -> Result<(), CliError> {
91 let output: Box<dyn Write> = match self.output_file {
92 Some(output_file) => Box::new(File::create(output_file)?),
93 None => Box::new(io::stdout()),
94 };
95
96 let mut app = App {
97 input: Box::new(io::stdin()),
98 output,
99 error: Box::new(io::stderr()),
100 };
101
102 self.command.run(&mut app).await
103 }
104}
105
106impl MainCommand {
107 async fn run(self, app: &mut App<'_>) -> CliResult {
108 match self {
109 MainCommand::Scl(options) => options.run(app),
110 MainCommand::Kbm(options) => options.run(app),
111 MainCommand::Est(options) => options.run(app),
112 MainCommand::Mos(options) => options.run(app),
113 MainCommand::Scale(options) => options.run(app),
114 MainCommand::Dump(options) => options.run(app),
115 MainCommand::Diff(options) => options.run(app),
116 MainCommand::Mts(options) => options.run(app),
117 MainCommand::Live(options) => options.run(app).await,
118 MainCommand::Devices => midi::print_midi_devices(&mut app.output, "tune-cli")
119 .handle_error("Could not print MIDI devices"),
120 }
121 }
122}
123
124pub fn run_in_shell_env() {
125 let options = match MainOptions::try_parse() {
126 Err(err) => {
127 if err.use_stderr() {
128 eprintln!("{err}")
129 } else {
130 println!("{err}");
131 };
132 return;
133 }
134 Ok(options) => options,
135 };
136
137 match executor::block_on(options.run()) {
138 Ok(()) => {}
139 Err(CliError::IoError(err)) if err.kind() == ErrorKind::BrokenPipe => {}
142 Err(err) => eprintln!("{err}"),
143 }
144}
145
146pub fn run_in_wasm_env(
147 args: impl IntoIterator<Item = String>,
148 input: impl Read,
149 output: impl Write,
150 error: impl Write,
151) {
152 let mut app = App {
153 input: Box::new(input),
154 output: Box::new(output),
155 error: Box::new(error),
156 };
157
158 let command = match MainCommand::try_parse_from(args) {
159 Err(err) => {
160 if err.use_stderr() {
161 app.errln(err).unwrap()
162 } else {
163 app.writeln(err).unwrap()
164 };
165 return;
166 }
167 Ok(command) => command,
168 };
169
170 match executor::block_on(command.run(&mut app)) {
171 Ok(()) => {}
172 Err(err) => app.errln(err).unwrap(),
173 }
174}
175
176struct App<'a> {
177 input: Box<dyn 'a + Read>,
178 output: Box<dyn 'a + Write>,
179 error: Box<dyn 'a + Write>,
180}
181
182impl App<'_> {
183 pub fn write(&mut self, message: impl Display) -> io::Result<()> {
184 write!(self.output, "{message}")
185 }
186
187 pub fn writeln(&mut self, message: impl Display) -> io::Result<()> {
188 writeln!(self.output, "{message}")
189 }
190
191 pub fn errln(&mut self, message: impl Display) -> io::Result<()> {
192 writeln!(self.error, "{message}")
193 }
194
195 pub fn read(&mut self) -> &mut dyn Read {
196 &mut self.input
197 }
198}
199
200pub type CliResult<T = ()> = Result<T, CliError>;
201
202pub enum CliError {
203 CommandError(String),
204 IoError(io::Error),
205}
206
207impl Display for CliError {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 match self {
210 CliError::CommandError(err) => write!(f, "error: {err}"),
211 CliError::IoError(err) => write!(f, "I/O error: {err}"),
212 }
213 }
214}
215
216impl From<String> for CliError {
217 fn from(v: String) -> Self {
218 CliError::CommandError(v)
219 }
220}
221
222impl From<io::Error> for CliError {
223 fn from(v: io::Error) -> Self {
224 CliError::IoError(v)
225 }
226}