1use std::env;
18use std::error::Error;
19use std::fs::File;
20use std::io::Write;
21use std::path::{Path, PathBuf};
22use std::process::Command;
23use std::{fs, process};
24
25use clap::{Parser, Subcommand};
26use gazetta_core::model::Source;
27use gazetta_core::render::Gazetta;
28use slug::slugify;
29
30use chrono::offset::Local as Date;
31
32trait RenderPaths {
34 fn render_paths(&self, source_path: &Path, dest_path: &Path) -> Result<(), Box<dyn Error>>;
35}
36
37impl<G: Gazetta> RenderPaths for G {
38 fn render_paths(&self, source_path: &Path, dest_path: &Path) -> Result<(), Box<dyn Error>> {
39 let source = Source::new(source_path)?;
40 self.render(&source, dest_path)?;
41 Ok(())
42 }
43}
44
45pub fn run<G: Gazetta>(gazetta: G) -> ! {
47 process::exit(_run(&gazetta).unwrap_or_else(|e| {
48 eprintln!("{}", e);
49 1
50 }))
51}
52
53#[derive(Parser)]
54#[command(version, about, long_about = None)]
55pub struct Cli {
56 #[arg(short, long, value_name = "DIRECTORY")]
58 source: Option<PathBuf>,
59 #[command(subcommand)]
60 commands: Commands,
61}
62
63#[derive(Subcommand)]
64enum Commands {
65 Render {
66 #[arg(short, long)]
68 force: bool,
69 destination: PathBuf,
71 },
72 New {
73 #[arg(short, long)]
75 edit: bool,
76 directory: PathBuf,
78 title: String,
80 },
81}
82
83fn _run(render_paths: &dyn RenderPaths) -> Result<i32, Box<dyn Error>> {
84 let cli = Cli::parse();
85 let source_path = cli
86 .source
87 .or_else(|| {
88 let mut path = PathBuf::new();
89 path.push(".");
90 while path.exists() {
91 path.push("gazetta.yaml");
92 let is_root = path.exists();
93 path.pop();
94 if is_root {
95 return Some(path);
96 }
97 path.push("..");
98 }
99 None
100 })
101 .ok_or("Could not find a gazetta config in this directory or any parent directories.")?;
102
103 match cli.commands {
104 Commands::Render { force, destination } => {
105 if fs::metadata(&destination).is_ok() {
106 if force {
107 fs::remove_dir_all(&destination).map_err(|e| {
108 format!("Failed to remove '{}': {}", destination.display(), e)
109 })?;
110 } else {
111 return Err(format!("Target '{}' exists.", destination.display()).into());
112 }
113 }
114 render_paths.render_paths(&source_path, &destination)?;
115 Ok(0)
116 }
117 Commands::New {
118 edit,
119 directory,
120 title,
121 } => {
122 let mut path = directory;
123 path.push(slugify(&title));
124 if path.exists() {
125 return Err(format!("Directory '{}' exists.", path.display()).into());
126 }
127 fs::create_dir(&path)
128 .map_err(|e| format!("Failed to create directory '{}': {}", path.display(), e))?;
129
130 path.push("index.md");
131 let mut file = File::create(&path)?;
132 writeln!(file, "---")?;
133 writeln!(file, "title: {}", &title)?;
134 writeln!(file, "date: {}", Date::now().format("%Y-%m-%d"))?;
135 writeln!(file, "---")?;
136 println!("Created page: {}", path.display());
137 if edit {
138 path.pop();
139 match Command::new(
140 env::var_os("EDITOR")
141 .as_deref()
142 .unwrap_or_else(|| "vim".as_ref()),
143 )
144 .arg("index.md")
145 .current_dir(path)
146 .status()
147 {
148 Ok(status) => match status.code() {
149 Some(code) => Ok(code),
150 None => Err("Editor was killed.".into()),
151 },
152 Err(e) => Err(format!("Failed to spawn editor: {}", e).into()),
153 }
154 } else {
155 Ok(0)
156 }
157 }
158 }
159}