1use crate::cli::{groups, help};
4
5use clap::{Args, Parser, Subcommand, ValueEnum};
6
7use crate::endpoint::{ANNUAL_PERIOD, QUARTER_PERIOD};
8
9fn parse_date(s: &str) -> Result<String, clap::Error> {
15 jiff::civil::Date::strptime("%Y-%m-%d", s)
16 .map(|_| s.to_string())
17 .map_err(|_| {
18 clap::Error::raw(
19 clap::error::ErrorKind::ValueValidation,
20 format!("invalid date '{s}': expected YYYY-MM-DD"),
21 )
22 })
23}
24
25const DEFAULT_BASE_URL: &str = "https://financialmodelingprep.com/stable/";
26
27#[derive(Debug, Parser)]
29#[command(
30 name = "fmp-agent",
31 version,
32 about = help::CLI_ABOUT,
33 after_help = help::EXIT_CODES,
34 disable_help_subcommand = true
35)]
36pub struct Cli {
37 #[arg(long, global = true, env = "FMP_API_KEY", hide_env_values = true, help = help::API_KEY)]
39 pub api_key: Option<String>,
40
41 #[arg(long, global = true, env = "FMP_BASE_URL", default_value = DEFAULT_BASE_URL, help = help::BASE_URL)]
43 pub base_url: String,
44
45 #[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
47 pub verbose: u8,
48
49 #[arg(long, global = true, help = help::STRICT_EMPTY)]
51 pub strict_empty: bool,
52
53 #[command(subcommand)]
55 pub(crate) command: Command,
56}
57
58#[derive(Debug, Subcommand)]
60pub(crate) enum Command {
61 #[command(
63 about = help::HELP_GROUP_ABOUT,
64 long_about = help::HELP_GROUP_LONG,
65 disable_help_subcommand = true
66 )]
67 Help {
68 #[command(subcommand)]
70 command: Option<HelpTopic>,
71 },
72
73 #[command(about = help::SEARCH_ABOUT, long_about = help::SEARCH_LONG)]
75 Search {
76 #[arg(help = help::SEARCH_QUERY)]
78 query: String,
79 },
80
81 #[command(about = help::SCHEMA_ABOUT, long_about = help::SCHEMA_LONG)]
83 Schema,
84
85 #[command(about = help::COMMANDS_ABOUT, long_about = help::COMMANDS_LONG)]
87 Commands {
88 #[arg(long, help = help::COMMANDS_GROUPED)]
90 grouped: bool,
91 },
92
93 #[command(about = help::COMPLETIONS_ABOUT, long_about = help::COMPLETIONS_LONG)]
95 Completions {
96 #[arg(value_enum)]
98 shell: clap_complete::Shell,
99 },
100
101 #[command(about = help::DOCTOR_ABOUT, long_about = help::DOCTOR_LONG)]
103 Doctor,
104
105 #[command(about = help::QUOTE_ALIAS_ABOUT, long_about = help::QUOTE_ALIAS_LONG)]
107 Quote(SymbolArgs),
108
109 #[command(
111 about = help::HISTORICAL_ALIAS_ABOUT,
112 long_about = help::HISTORICAL_ALIAS_LONG
113 )]
114 Historical(SymbolDateRangeArgs),
115
116 #[command(about = help::PROFILE_ALIAS_ABOUT, long_about = help::PROFILE_ALIAS_LONG)]
118 Profile(SymbolArgs),
119
120 #[command(about = help::EARNINGS_ALIAS_ABOUT, long_about = help::EARNINGS_ALIAS_LONG)]
122 Earnings(DateRangeArgs),
123
124 #[command(about = help::COMPANY_GROUP_ABOUT, long_about = help::COMPANY_GROUP_LONG)]
126 Company {
127 #[command(subcommand)]
129 command: Option<groups::company::Cmd>,
130 },
131
132 #[command(about = help::MARKET_GROUP_ABOUT, long_about = help::MARKET_GROUP_LONG)]
134 Market {
135 #[command(subcommand)]
137 command: Option<groups::market::Cmd>,
138 },
139
140 #[command(
142 about = help::FUNDAMENTALS_GROUP_ABOUT,
143 long_about = help::FUNDAMENTALS_GROUP_LONG
144 )]
145 Fundamentals {
146 #[command(subcommand)]
148 command: Option<groups::fundamentals::Cmd>,
149 },
150
151 #[command(about = help::ANALYST_GROUP_ABOUT, long_about = help::ANALYST_GROUP_LONG)]
153 Analyst {
154 #[command(subcommand)]
156 command: Option<groups::analyst::Cmd>,
157 },
158
159 #[command(about = help::INSIDER_GROUP_ABOUT, long_about = help::INSIDER_GROUP_LONG)]
161 Insider {
162 #[command(subcommand)]
164 command: Option<groups::insider::Cmd>,
165 },
166
167 #[command(about = help::CALENDAR_GROUP_ABOUT, long_about = help::CALENDAR_GROUP_LONG)]
169 Calendar {
170 #[command(subcommand)]
172 command: Option<groups::calendar::Cmd>,
173 },
174
175 #[command(
177 name = "macro",
178 about = help::MACRO_GROUP_ABOUT,
179 long_about = help::MACRO_GROUP_LONG
180 )]
181 MacroEcon {
182 #[command(subcommand)]
184 command: Option<groups::macro_econ::Cmd>,
185 },
186
187 #[command(about = help::TECHNICAL_GROUP_ABOUT, long_about = help::TECHNICAL_GROUP_LONG)]
189 Technical {
190 #[command(subcommand)]
192 command: Option<groups::technical::Cmd>,
193 },
194
195 #[command(about = help::SEC_GROUP_ABOUT, long_about = help::SEC_GROUP_LONG)]
197 Sec {
198 #[command(subcommand)]
200 command: Option<groups::sec::Cmd>,
201 },
202
203 #[command(about = help::ETF_GROUP_ABOUT, long_about = help::ETF_GROUP_LONG)]
205 Etf {
206 #[command(subcommand)]
208 command: Option<groups::etf::Cmd>,
209 },
210
211 #[command(about = help::CRYPTO_GROUP_ABOUT, long_about = help::CRYPTO_GROUP_LONG)]
213 Crypto {
214 #[command(subcommand)]
216 command: Option<groups::crypto::Cmd>,
217 },
218
219 #[command(about = help::FOREX_GROUP_ABOUT, long_about = help::FOREX_GROUP_LONG)]
221 Forex {
222 #[command(subcommand)]
224 command: Option<groups::forex::Cmd>,
225 },
226
227 #[command(about = help::NEWS_GROUP_ABOUT, long_about = help::NEWS_GROUP_LONG)]
229 News {
230 #[command(subcommand)]
232 command: Option<groups::news::Cmd>,
233 },
234}
235
236#[derive(Debug, Subcommand)]
238pub(crate) enum HelpTopic {
239 #[command(about = help::HELP_ENVIRONMENT_ABOUT, long_about = help::HELP_ENVIRONMENT_LONG)]
241 Environment,
242
243 #[command(about = help::HELP_EXIT_CODES_ABOUT, long_about = help::HELP_EXIT_CODES_LONG)]
245 ExitCodes,
246
247 #[command(about = help::HELP_SCHEMA_ABOUT, long_about = help::HELP_SCHEMA_LONG)]
249 Schema,
250
251 #[command(
253 about = help::HELP_TROUBLESHOOTING_ABOUT,
254 long_about = help::HELP_TROUBLESHOOTING_LONG
255 )]
256 Troubleshooting,
257
258 #[command(about = help::HELP_EXAMPLES_ABOUT, long_about = help::HELP_EXAMPLES_LONG)]
260 Examples,
261}
262
263#[derive(Debug, Args)]
265pub struct SymbolArgs {
266 #[arg(help = help::SYMBOL)]
268 pub symbol: String,
269}
270
271#[derive(Debug, Args)]
273pub struct SymbolsArgs {
274 #[arg(required = true, num_args = 1.., value_name = "SYMBOLS", help = help::SYMBOLS)]
276 pub symbols: Vec<String>,
277}
278
279#[derive(Debug, Args)]
281pub struct SymbolLimitArgs {
282 #[arg(help = help::STOCK_SYMBOL)]
284 pub symbol: String,
285
286 #[arg(long, default_value_t = 10, help = help::LIMIT_ROWS)]
288 pub limit: u16,
289}
290
291#[derive(Debug, Args)]
293pub struct AnnualReportFormArgs {
294 #[arg(help = help::STOCK_SYMBOL)]
296 pub symbol: String,
297
298 #[arg(long, help = help::YEAR)]
300 pub year: u16,
301
302 #[arg(long, default_value = "FY", help = help::PERIOD)]
304 pub period: String,
305}
306
307#[derive(Debug, Args)]
309pub struct SymbolDateRangeArgs {
310 #[arg(help = help::SYMBOL)]
312 pub symbol: String,
313
314 #[arg(long, help = help::FROM, value_parser = parse_date)]
316 pub from: Option<String>,
317
318 #[arg(long, help = help::TO, value_parser = parse_date)]
320 pub to: Option<String>,
321}
322
323#[derive(Debug, Args)]
325pub struct DateRangeArgs {
326 #[arg(long, help = help::FROM, value_parser = parse_date)]
328 pub from: Option<String>,
329
330 #[arg(long, help = help::TO, value_parser = parse_date)]
332 pub to: Option<String>,
333}
334
335#[derive(Debug, Args)]
337pub struct NameDateRangeArgs {
338 #[arg(help = help::ECONOMIC_INDICATOR_NAME)]
340 pub name: String,
341
342 #[arg(long, help = help::FROM, value_parser = parse_date)]
344 pub from: Option<String>,
345
346 #[arg(long, help = help::TO, value_parser = parse_date)]
348 pub to: Option<String>,
349}
350
351#[derive(Debug, Args)]
353pub struct AnnualArgs {
354 #[arg(help = help::STOCK_SYMBOL)]
356 pub symbol: String,
357
358 #[arg(long, default_value_t = 5, help = help::ANNUAL_LIMIT)]
360 pub limit: u16,
361}
362
363#[derive(Debug, Args)]
365pub struct StatementArgs {
366 #[arg(help = help::STOCK_SYMBOL)]
368 pub symbol: String,
369
370 #[arg(long, value_enum, default_value_t = StatementPeriod::Annual, help = help::STATEMENT_PERIOD)]
372 pub period: StatementPeriod,
373
374 #[arg(long, default_value_t = 5, help = help::ANNUAL_LIMIT)]
376 pub limit: u16,
377}
378
379#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)]
381pub enum StatementPeriod {
382 #[default]
384 Annual,
385 Quarter,
387}
388
389impl StatementPeriod {
390 #[must_use]
392 pub const fn as_str(self) -> &'static str {
393 match self {
394 Self::Annual => ANNUAL_PERIOD,
395 Self::Quarter => QUARTER_PERIOD,
396 }
397 }
398}
399
400impl std::fmt::Display for StatementPeriod {
401 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 formatter.write_str(self.as_str())
403 }
404}
405
406#[derive(Debug, Args)]
408pub struct TechnicalSmaArgs {
409 #[arg(help = help::STOCK_SYMBOL)]
411 pub symbol: String,
412
413 #[arg(long, default_value_t = 10, help = help::PERIOD_LENGTH)]
415 pub period_length: u16,
416
417 #[arg(long, default_value = "1day", help = help::TIMEFRAME)]
419 pub timeframe: String,
420}
421
422#[derive(Debug, Args)]
424pub struct StockNewsArgs {
425 #[arg(help = help::STOCK_SYMBOL)]
427 pub symbol: String,
428
429 #[arg(long, default_value_t = 10, help = help::LIMIT_ROWS)]
431 pub limit: u16,
432}
433
434#[derive(Debug, Args)]
436pub struct PagedArgs {
437 #[arg(long, default_value_t = 0, help = help::PAGE)]
439 pub page: u16,
440
441 #[arg(long, default_value_t = 10, help = help::PAGE_LIMIT)]
443 pub limit: u16,
444}
445
446#[cfg(test)]
447mod tests {
448 use super::StatementPeriod;
449
450 #[test]
451 fn statement_period_displays_api_values() {
452 assert_eq!(StatementPeriod::Annual.to_string(), "annual");
453 assert_eq!(StatementPeriod::Quarter.to_string(), "quarter");
454 }
455}