1use clap::{Parser, Subcommand, ValueEnum};
2use std::path::PathBuf;
3
4#[derive(Parser)]
6#[command(name = "fastpaper", version, about, long_about = None)]
7pub struct Cli {
8 #[command(subcommand)]
9 pub command: Commands,
10
11 #[command(flatten)]
12 pub global: GlobalOpts,
13}
14
15#[derive(clap::Args)]
16pub struct GlobalOpts {
17 #[arg(short, long, global = true, action = clap::ArgAction::Count)]
19 pub verbose: u8,
20
21 #[arg(short, long, global = true)]
23 pub quiet: bool,
24
25 #[arg(short, long, global = true, default_value = "table")]
27 pub format: OutputFormat,
28}
29
30#[derive(Subcommand)]
31pub enum Commands {
32 Search(SearchArgs),
34
35 Download(DownloadArgs),
37
38 Read(ReadArgs),
40
41 Get(GetArgs),
43
44 Sources(SourcesArgs),
46
47 Completions {
49 shell: clap_complete::Shell,
50 },
51}
52
53#[derive(clap::Args)]
56pub struct SearchArgs {
57 pub source: Source,
59
60 pub query: String,
62
63 #[arg(short = 'n', long, default_value = "10")]
65 pub limit: u32,
66
67 #[arg(long, default_value = "0")]
69 pub offset: u32,
70
71 #[arg(long, default_value = "relevance")]
73 pub sort: SortField,
74
75 #[arg(long, default_value = "desc")]
77 pub order: SortOrder,
78
79 #[arg(long)]
81 pub author: Option<String>,
82
83 #[arg(long)]
85 pub after: Option<String>,
86
87 #[arg(long)]
89 pub before: Option<String>,
90
91 #[arg(long)]
93 pub year: Option<u16>,
94
95 #[arg(long)]
97 pub field: Option<String>,
98
99 #[arg(long)]
101 pub open_access: bool,
102
103 #[arg(long)]
105 pub peer_reviewed: bool,
106
107 #[arg(long)]
109 pub fields: Option<String>,
110
111 #[arg(long)]
113 pub with_abstract: bool,
114
115 #[arg(short, long)]
117 pub output: Option<PathBuf>,
118}
119
120#[derive(clap::Args)]
123pub struct DownloadArgs {
124 pub source: Source,
126
127 pub identifier: String,
129
130 #[arg(short, long, env = "FASTPAPER_DOWNLOAD_DIR", default_value = "./papers")]
132 pub dir: PathBuf,
133
134 #[arg(long, default_value = "{id}.{title}")]
136 pub filename: String,
137
138 #[arg(long)]
140 pub overwrite: bool,
141
142 #[arg(long)]
144 pub source_files: bool,
145}
146
147#[derive(clap::Args)]
150pub struct ReadArgs {
151 pub source: Source,
153
154 pub identifier: String,
156
157 #[arg(long, default_value = "full")]
159 pub section: Section,
160
161 #[arg(long)]
163 pub metadata_only: bool,
164
165 #[arg(long)]
167 pub raw: bool,
168
169 #[arg(long)]
171 pub max_length: Option<usize>,
172
173 #[arg(short, long)]
175 pub output: Option<PathBuf>,
176}
177
178#[derive(clap::Args)]
181pub struct GetArgs {
182 pub identifier: String,
184
185 #[arg(long)]
187 pub resolve: bool,
188
189 #[arg(long)]
191 pub with_citations: bool,
192
193 #[arg(long)]
195 pub with_abstract: bool,
196
197 #[arg(long)]
199 pub with_related: bool,
200}
201
202#[derive(clap::Args)]
205pub struct SourcesArgs {
206 #[arg(long)]
208 pub check: bool,
209
210 #[arg(long)]
212 pub capabilities: bool,
213}
214
215#[derive(ValueEnum, Clone, Debug)]
218pub enum Source {
219 Arxiv,
220 Biorxiv,
221 Medrxiv,
222 Pubmed,
223 Pmc,
224 Europepmc,
225 Scholar,
226 Semantic,
227 Crossref,
228 Openalex,
229 Dblp,
230 Core,
231 Openaire,
232 Doaj,
233 Unpaywall,
234 Zenodo,
235 Hal,
236 Local,
237}
238
239impl Source {
240 pub fn supports_search(&self) -> bool {
241 !matches!(self, Source::Local)
242 }
243
244 pub fn supports_download(&self) -> bool {
245 matches!(
246 self,
247 Source::Arxiv
248 | Source::Biorxiv
249 | Source::Medrxiv
250 | Source::Pmc
251 | Source::Semantic
252 | Source::Core
253 | Source::Doaj
254 | Source::Zenodo
255 | Source::Hal
256 | Source::Local
257 )
258 }
259
260 pub fn supports_read(&self) -> bool {
261 self.supports_download()
262 }
263
264 pub fn name(&self) -> &'static str {
265 match self {
266 Source::Arxiv => "arxiv",
267 Source::Biorxiv => "biorxiv",
268 Source::Medrxiv => "medrxiv",
269 Source::Pubmed => "pubmed",
270 Source::Pmc => "pmc",
271 Source::Europepmc => "europepmc",
272 Source::Scholar => "scholar",
273 Source::Semantic => "semantic",
274 Source::Crossref => "crossref",
275 Source::Openalex => "openalex",
276 Source::Dblp => "dblp",
277 Source::Core => "core",
278 Source::Openaire => "openaire",
279 Source::Doaj => "doaj",
280 Source::Unpaywall => "unpaywall",
281 Source::Zenodo => "zenodo",
282 Source::Hal => "hal",
283 Source::Local => "local",
284 }
285 }
286
287 pub fn download_hint(&self) -> Option<&'static str> {
288 match self {
289 Source::Pubmed => Some("Try: fastpaper download pmc <PMC_ID>"),
290 Source::Scholar => Some("Google Scholar does not provide PDFs directly"),
291 Source::Crossref | Source::Openalex | Source::Dblp => {
292 Some("This source only provides metadata. Try: fastpaper get <ID> --resolve")
293 }
294 Source::Openaire | Source::Unpaywall => {
295 Some("This source only provides metadata. Try: fastpaper get <ID> --resolve")
296 }
297 _ => None,
298 }
299 }
300}
301
302#[derive(ValueEnum, Clone, Debug)]
303pub enum OutputFormat {
304 Table,
305 Json,
306 Jsonl,
307 Csv,
308 Bibtex,
309}
310
311#[derive(ValueEnum, Clone, Debug)]
312pub enum SortField {
313 Relevance,
314 Date,
315 Citations,
316}
317
318#[derive(ValueEnum, Clone, Debug)]
319pub enum SortOrder {
320 Asc,
321 Desc,
322}
323
324#[derive(ValueEnum, Clone, Debug)]
325pub enum Section {
326 Abstract,
327 Introduction,
328 Methods,
329 Results,
330 Discussion,
331 Conclusion,
332 References,
333 Full,
334}