1use std::sync::{Arc, Mutex};
2
3use config::StartTogetherOptions;
4use errors::TogetherResult;
5use manager::ProcessAction;
6use terminal_ext::TerminalExt;
7
8pub mod config;
9pub mod errors;
10pub mod kb;
11pub mod manager;
12pub mod process;
13pub mod terminal;
14pub mod terminal_ext;
15
16pub fn start(options: StartTogetherOptions) -> TogetherResult<()> {
17 let StartTogetherOptions {
18 config,
19 working_directory,
20 ..
21 } = &options;
22
23 let manager = manager::ProcessManager::new()
24 .with_raw_mode(config.start_options.raw)
25 .with_exit_on_error(config.start_options.exit_on_error)
26 .with_quit_on_completion(config.start_options.quit_on_completion)
27 .with_working_directory(working_directory.to_owned())
28 .start();
29
30 let sender = manager.subscribe();
31 handle_ctrl_signal(sender);
32
33 let selected_commands = collect_together_commands(&manager, &options)?;
34
35 if config.start_options.no_init {
36 log!("Skipping startup commands...");
37 } else {
38 execute_startup_commands(&manager, &config)?;
39 }
40
41 if config.start_options.init_only {
42 log!("Finished running startup commands, waiting for user input... (press '?' for help)");
43 } else {
44 execute_together_commands(&manager, selected_commands)?;
45 }
46
47 let sender = manager.subscribe();
48 kb::block_for_user_input(&options, sender)?;
49
50 std::mem::drop(manager);
51 Ok(())
52}
53
54pub fn handle_ctrl_signal(sender: manager::ProcessManagerHandle) {
55 let state = Arc::new(Mutex::new(false));
56 let handler = ctrlc::set_handler(move || {
57 {
58 let mut state = state.lock().unwrap();
59 if *state {
60 log!("Ctrl-C pressed again, exiting immediately...");
61 std::process::exit(1);
62 }
63 *state = true;
64 }
65
66 log!("Ctrl-C pressed, stopping all processes...");
67 sender
68 .send(ProcessAction::KillAll)
69 .expect("Could not send signal on channel.");
70 });
71 handler.expect("Error setting Ctrl-C handler");
72}
73
74fn collect_together_commands(
75 manager: &manager::ProcessManagerHandle,
76 options: &StartTogetherOptions,
77) -> TogetherResult<Vec<String>> {
78 if let Some(recipes) = &options.active_recipes {
79 log!("Running commands from recipes...");
80 let config_opts = &options.config.start_options;
81
82 let selected_commands = config::collect_commands_by_recipes(&config_opts, recipes);
83
84 log!("Commands selected by recipes:");
85 for command in &selected_commands {
86 log!(" - {}", command);
87 }
88
89 return Ok(selected_commands);
90 }
91
92 let config = &options.config;
93 let selected_commands = match &config.running_commands() {
94 Some(commands) => {
95 log!("Running commands from configuration...");
96 commands.into_iter().map(|c| c.to_string()).collect()
97 }
98 None if config.start_options.all => {
99 log!("Running all commands...");
100 config.start_options.as_commands()
101 }
102 None => {
103 let all_commands = config.start_options.as_commands();
104 let sender = manager.subscribe();
105 let commands = terminal::Terminal::select_multiple_commands(
106 "Select commands to run together",
107 &sender,
108 &all_commands,
109 )?;
110 commands.into_iter().cloned().collect()
111 }
112 };
113 Ok(selected_commands)
114}
115
116fn execute_startup_commands(
117 manager: &manager::ProcessManagerHandle,
118 config: &config::TogetherConfigFile,
119) -> TogetherResult<()> {
120 let Some(startup) = &config.startup else {
121 return Ok(());
122 };
123
124 log!("Running startup commands...");
125 let sender = manager.subscribe();
126
127 let commands = startup
128 .iter()
129 .flat_map(|index| index.retrieve(&config.start_options.commands))
130 .map(|c| c.as_str().to_string())
131 .collect::<Vec<_>>();
132
133 let opts = if config.start_options.quiet_startup {
134 manager::CreateOptions::default().with_stderr_only()
135 } else {
136 manager::CreateOptions::default()
137 };
138
139 for command in commands {
140 let id = sender.spawn_advanced(&command, &opts)?;
141 sender.wait(id)?;
142 log!("Startup command '{}' completed", command);
143 }
144
145 Ok(())
146}
147
148fn execute_together_commands(
149 manager: &manager::ProcessManagerHandle,
150 selected_commands: Vec<String>,
151) -> TogetherResult<()> {
152 let sender = manager.subscribe();
153 for command in selected_commands {
154 sender.send(ProcessAction::Create(command.clone()))?;
155 }
156 Ok(())
157}