1use anyhow::{Result, anyhow};
2use clap::Parser;
3
4use crate::bear::{join_tags, maybe_push, maybe_push_bool, open_bear_action};
5use crate::cli::Cli;
6use crate::cli::Commands;
7use crate::config::{encode_file, load_token, resolve_database_path, save_token};
8use crate::db::BearDb;
9
10pub fn run() -> Result<()> {
11 let cli = Cli::parse();
12 let db = match &cli.command {
13 Commands::OpenNote(_)
14 | Commands::Tags
15 | Commands::OpenTag(_)
16 | Commands::Search(_)
17 | Commands::Untagged(_)
18 | Commands::Todo(_)
19 | Commands::Today(_)
20 | Commands::Locked(_) => Some(BearDb::open(resolve_database_path(
21 cli.database.as_deref(),
22 )?)?),
23 _ => None,
24 };
25
26 match cli.command {
27 Commands::Auth(cmd) => {
28 save_token(&cmd.token)?;
29 println!("Saved API token.");
30 }
31 Commands::OpenNote(cmd) => {
32 let note = db
33 .as_ref()
34 .expect("db available for read command")
35 .find_note(cmd.id.as_deref(), cmd.title.as_deref(), cmd.exclude_trashed)?;
36 println!("{}", note.text);
37 }
38 Commands::Tags => {
39 for tag in db.as_ref().expect("db available for read command").tags()? {
40 println!("{tag}");
41 }
42 }
43 Commands::OpenTag(cmd) => {
44 for note in db
45 .as_ref()
46 .expect("db available for read command")
47 .notes_for_tags(&split_csv(&cmd.name), false)?
48 {
49 println!("{}\t{}", note.identifier, note.title);
50 }
51 }
52 Commands::Search(cmd) => {
53 for note in db.as_ref().expect("db available for read command").search(
54 cmd.term.as_deref(),
55 cmd.tag.as_deref(),
56 false,
57 )? {
58 println!("{}\t{}", note.identifier, note.title);
59 }
60 }
61 Commands::Untagged(cmd) => {
62 for note in db
63 .as_ref()
64 .expect("db available for read command")
65 .untagged(cmd.search.as_deref())?
66 {
67 println!("{}\t{}", note.identifier, note.title);
68 }
69 }
70 Commands::Todo(cmd) => {
71 for note in db
72 .as_ref()
73 .expect("db available for read command")
74 .todo(cmd.search.as_deref())?
75 {
76 println!("{}\t{}", note.identifier, note.title);
77 }
78 }
79 Commands::Today(cmd) => {
80 for note in db
81 .as_ref()
82 .expect("db available for read command")
83 .today(cmd.search.as_deref())?
84 {
85 println!("{}\t{}", note.identifier, note.title);
86 }
87 }
88 Commands::Locked(cmd) => {
89 for note in db
90 .as_ref()
91 .expect("db available for read command")
92 .locked(cmd.search.as_deref())?
93 {
94 println!("{}\t{}", note.identifier, note.title);
95 }
96 }
97 Commands::Create(cmd) => {
98 let mut query = Vec::new();
99 maybe_push(&mut query, "title", cmd.title);
100 maybe_push(&mut query, "text", cmd.text);
101 maybe_push(&mut query, "tags", join_tags(&cmd.tag));
102 maybe_push_bool(&mut query, "open_note", cmd.open_note);
103 maybe_push_bool(&mut query, "new_window", cmd.new_window);
104 maybe_push_bool(&mut query, "float", cmd.float);
105 maybe_push_bool(&mut query, "show_window", cmd.show_window);
106 maybe_push_bool(&mut query, "pin", cmd.pin);
107 maybe_push_bool(&mut query, "edit", cmd.edit);
108 maybe_push_bool(&mut query, "timestamp", cmd.timestamp);
109 maybe_push(&mut query, "type", cmd.kind);
110 maybe_push(&mut query, "url", cmd.url);
111
112 if let Some(file) = cmd.file {
113 let filename = cmd
114 .filename
115 .or_else(|| {
116 file.file_name()
117 .map(|name| name.to_string_lossy().into_owned())
118 })
119 .ok_or_else(|| {
120 anyhow!("--filename is required when the file path has no file name")
121 })?;
122 let encoded = encode_file(&file)?;
123 maybe_push(&mut query, "filename", Some(filename));
124 maybe_push(&mut query, "file", Some(encoded));
125 }
126
127 open_bear_action("create", &query)?;
128 }
129 Commands::AddText(cmd) => {
130 let mut query = Vec::new();
131 maybe_push(&mut query, "id", cmd.id);
132 maybe_push(&mut query, "title", cmd.title);
133 maybe_push(&mut query, "text", cmd.text);
134 maybe_push(&mut query, "header", cmd.header);
135 maybe_push(&mut query, "mode", Some(cmd.mode));
136 maybe_push(&mut query, "tags", join_tags(&cmd.tag));
137 maybe_push_bool(&mut query, "exclude_trashed", cmd.exclude_trashed);
138 maybe_push_bool(&mut query, "new_line", cmd.new_line);
139 maybe_push_bool(&mut query, "open_note", cmd.open_note);
140 maybe_push_bool(&mut query, "new_window", cmd.new_window);
141 maybe_push_bool(&mut query, "show_window", cmd.show_window);
142 maybe_push_bool(&mut query, "edit", cmd.edit);
143 maybe_push_bool(&mut query, "timestamp", cmd.timestamp);
144 open_bear_action("add-text", &query)?;
145 }
146 Commands::AddFile(cmd) => {
147 let mut query = Vec::new();
148 maybe_push(&mut query, "id", cmd.id);
149 maybe_push(&mut query, "title", cmd.title);
150 maybe_push(&mut query, "header", cmd.header);
151 maybe_push(&mut query, "mode", Some(cmd.mode));
152 maybe_push_bool(&mut query, "open_note", cmd.open_note);
153 maybe_push_bool(&mut query, "new_window", cmd.new_window);
154 maybe_push_bool(&mut query, "show_window", cmd.show_window);
155 maybe_push_bool(&mut query, "edit", cmd.edit);
156 maybe_push(
157 &mut query,
158 "filename",
159 cmd.filename.or_else(|| {
160 cmd.file
161 .file_name()
162 .map(|name| name.to_string_lossy().into_owned())
163 }),
164 );
165 maybe_push(&mut query, "file", Some(encode_file(&cmd.file)?));
166 open_bear_action("add-file", &query)?;
167 }
168 Commands::GrabUrl(cmd) => {
169 let mut query = Vec::new();
170 maybe_push(&mut query, "url", Some(cmd.url));
171 maybe_push(&mut query, "tags", join_tags(&cmd.tag));
172 maybe_push_bool(&mut query, "pin", cmd.pin);
173 maybe_push_bool(&mut query, "wait", cmd.wait);
174 open_bear_action("grab-url", &query)?;
175 }
176 Commands::Trash(cmd) => {
177 let mut query = Vec::new();
178 maybe_push(&mut query, "id", cmd.id);
179 maybe_push(&mut query, "search", cmd.search);
180 maybe_push_bool(&mut query, "show_window", cmd.show_window);
181 open_bear_action("trash", &query)?;
182 }
183 Commands::Archive(cmd) => {
184 let mut query = Vec::new();
185 maybe_push(&mut query, "id", cmd.id);
186 maybe_push(&mut query, "search", cmd.search);
187 maybe_push_bool(&mut query, "show_window", cmd.show_window);
188 open_bear_action("archive", &query)?;
189 }
190 Commands::RenameTag(cmd) => {
191 let mut query = Vec::new();
192 maybe_push(&mut query, "name", Some(cmd.name));
193 maybe_push(&mut query, "new_name", Some(cmd.new_name));
194 maybe_push_bool(&mut query, "show_window", cmd.show_window);
195 open_bear_action("rename-tag", &query)?;
196 }
197 Commands::DeleteTag(cmd) => {
198 let mut query = Vec::new();
199 maybe_push(&mut query, "name", Some(cmd.name));
200 maybe_push_bool(&mut query, "show_window", cmd.show_window);
201 open_bear_action("delete-tag", &query)?;
202 }
203 Commands::Raw(cmd) => {
204 let mut params = cmd.params;
205 let has_token = params.iter().any(|(key, _)| key == "token");
206 if !has_token {
207 if let Some(token) = cmd.token {
208 params.push(("token".into(), token));
209 } else if cmd.use_saved_token {
210 if let Some(token) = load_token()? {
211 params.push(("token".into(), token));
212 }
213 }
214 }
215 open_bear_action(&cmd.action, ¶ms)?;
216 }
217 }
218
219 Ok(())
220}
221
222fn split_csv(input: &str) -> Vec<String> {
223 input
224 .split(',')
225 .map(str::trim)
226 .filter(|part| !part.is_empty())
227 .map(ToOwned::to_owned)
228 .collect()
229}