1use clap::Parser;
2use thiserror::Error;
3
4use crate::{
5 image_generator::{self, SaveError},
6 image_renderer::{ImageRenderer, ImageRendererError},
7 os_command::{ExecutorError, PlatformExecutor},
8 screen_builder::{ScreenBuilder, ScreenBuilderError},
9 window_decoration::{create_window_decoration, WindowDecorationType},
10 Executor,
11};
12
13#[derive(Error, Debug)]
15pub enum ShellshotError {
16 #[error("Failed to execute command: {0}")]
17 CommandExecution(#[from] ExecutorError),
18
19 #[error("Failed to build screen from output: {0}")]
20 ScreenBuild(#[from] ScreenBuilderError),
21
22 #[error("Failed to render image: {0}")]
23 ImageRender(#[from] ImageRendererError),
24
25 #[error("Failed to save image to file: {0}")]
26 Save(#[from] SaveError),
27}
28
29#[derive(Parser, Debug)]
31#[command(
32 name = "shellshot",
33 about = "Transform command-line output into stunning, shareable images",
34 version,
35 long_about = None
36)]
37pub struct Args {
38 #[arg(trailing_var_arg = true)]
40 pub command: Vec<String>,
41
42 #[arg(long)]
44 pub no_decoration: bool,
45
46 #[arg(long, short = 'd', default_value = "classic")]
48 pub decoration: WindowDecorationType,
49
50 #[arg(long, short = 'f', default_value = "out.png")]
52 pub filename: Option<String>,
53
54 #[arg(long)]
56 pub clipboard: bool,
57}
58
59pub fn run_shellshot(args: Args) -> Result<(), ShellshotError> {
69 println!("Starting shellshot v{}", env!("CARGO_PKG_VERSION"));
70
71 let command_str = args.command.join(" ");
72 let output = PlatformExecutor::execute_command(&command_str)?;
73
74 let decoration = (!args.no_decoration).then_some(args.decoration);
75 let window_decoration = create_window_decoration(decoration.as_ref());
76
77 let screen = ScreenBuilder::from_output(&output, &command_str, window_decoration.as_ref())?;
78 let image_data = ImageRenderer::render_image(&screen, window_decoration)?;
79
80 if args.clipboard {
81 image_generator::save_to_clipboard(&image_data)?;
82 println!("✅ Screenshot saved to clipboard");
83 }
84
85 let filename = args.filename.unwrap_or_else(|| "out.png".to_string());
86 image_generator::save_to_file(&image_data, &filename)?;
87 println!("✅ Screenshot saved to {filename}");
88
89 Ok(())
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_execute_command_with_file() {
98 let args = Args {
99 command: vec!["echo".into(), "hello".into()],
100 no_decoration: false,
101 decoration: WindowDecorationType::Classic,
102 filename: Some("test.png".into()),
103 clipboard: false,
104 };
105
106 let result = run_shellshot(args);
107 assert!(result.is_ok());
108 }
109}