Skip to main content

draw/
cli.rs

1use anyhow::Result;
2use clap::Parser;
3use std::path::PathBuf;
4
5use draw_core::document::Document;
6
7#[derive(Parser, Debug)]
8#[command(name = "draw")]
9#[command(about = "Local-first drawing tool")]
10#[command(version)]
11pub struct Args {
12    /// Open the desktop app
13    #[cfg(feature = "app")]
14    #[arg(short = 'a', long)]
15    pub app: bool,
16
17    /// Open the webapp
18    #[cfg(feature = "webapp")]
19    #[arg(short = 'w', long)]
20    pub webapp: bool,
21
22    #[command(subcommand)]
23    pub command: Option<Command>,
24}
25
26#[derive(clap::Subcommand, Debug)]
27pub enum Command {
28    /// Create a new drawing
29    New {
30        /// Drawing name
31        #[arg(default_value = "untitled")]
32        name: String,
33    },
34    /// Open an existing drawing
35    Open {
36        /// Path to .draw.json file
37        file: PathBuf,
38    },
39    /// List saved drawings
40    List,
41    /// Export drawing to SVG
42    ExportSvg {
43        /// Path to .draw.json file
44        file: PathBuf,
45        /// Output SVG path (defaults to same name with .svg extension)
46        #[arg(short, long)]
47        output: Option<PathBuf>,
48    },
49    /// Export drawing to PNG
50    ExportPng {
51        /// Path to .draw.json file
52        file: PathBuf,
53        /// Output PNG path (defaults to same name with .png extension)
54        #[arg(short, long)]
55        output: Option<PathBuf>,
56        /// Scale factor (default: 2x for retina)
57        #[arg(short, long, default_value_t = 2.0)]
58        scale: f32,
59    },
60}
61
62pub fn run_cli(argv: impl IntoIterator<Item = impl Into<String>>) -> Result<()> {
63    let argv: Vec<String> = argv.into_iter().map(Into::into).collect();
64
65    // No args = help
66    if argv.len() <= 1 {
67        Args::parse_from(["draw", "--help"]);
68    }
69
70    let args = Args::parse_from(&argv);
71
72    #[cfg(feature = "app")]
73    if args.app {
74        return draw_app::run_app(None);
75    }
76
77    #[cfg(feature = "webapp")]
78    if args.webapp {
79        return draw_webapp::run_webapp(None);
80    }
81
82    match args.command {
83        Some(Command::New { name }) => {
84            let doc = Document::new(name);
85            let path = draw_core::storage::save_to_storage(&doc)?;
86            println!("Created: {} ({})", doc.name, path.display());
87
88            #[cfg(feature = "webapp")]
89            return draw_webapp::run_webapp(Some(doc.id));
90
91            #[cfg(not(feature = "webapp"))]
92            {
93                println!("Run with --webapp to open in browser");
94                Ok(())
95            }
96        }
97        Some(Command::Open { file }) => {
98            let doc = draw_core::storage::load(&file)?;
99            println!("Loaded: {} ({} elements)", doc.name, doc.elements.len());
100
101            #[cfg(feature = "webapp")]
102            return draw_webapp::run_webapp(Some(doc.id));
103
104            #[cfg(not(feature = "webapp"))]
105            {
106                println!("Run with --webapp to open in browser");
107                Ok(())
108            }
109        }
110        Some(Command::List) => {
111            let drawings = draw_core::storage::list_drawings()?;
112            if drawings.is_empty() {
113                println!("No saved drawings.");
114            } else {
115                for (name, path) in drawings {
116                    println!("  {} ({})", name, path.display());
117                }
118            }
119            Ok(())
120        }
121        Some(Command::ExportSvg { file, output }) => {
122            let doc = draw_core::storage::load(&file)?;
123            let svg = draw_core::export_svg(&doc);
124            let out_path = output.unwrap_or_else(|| file.with_extension("svg"));
125            std::fs::write(&out_path, &svg)?;
126            println!("Exported SVG to {}", out_path.display());
127            Ok(())
128        }
129        Some(Command::ExportPng {
130            file,
131            output,
132            scale,
133        }) => {
134            let doc = draw_core::storage::load(&file)?;
135            let png = draw_core::export_png_with_scale(&doc, scale)?;
136            let out_path = output.unwrap_or_else(|| file.with_extension("png"));
137            std::fs::write(&out_path, &png)?;
138            println!("Exported PNG to {}", out_path.display());
139            Ok(())
140        }
141        None => {
142            // No subcommand but also no flags = help
143            Args::parse_from(["draw", "--help"]);
144            unreachable!()
145        }
146    }
147}