bbc_news_cli/
cli.rs

1use clap::{Parser, Subcommand};
2use anyhow::Result;
3use crate::{api, article_fetcher, date_utils, feeds};
4
5#[derive(Parser)]
6#[command(name = "bbcli")]
7#[command(about = "Browse BBC News like a hacker", long_about = None)]
8#[command(version)]
9pub struct Cli {
10    #[command(subcommand)]
11    pub command: Option<Commands>,
12
13    /// Specify feed name (e.g., world, technology, business)
14    #[arg(short, long, global = true)]
15    pub feed: Option<String>,
16}
17
18#[derive(Subcommand)]
19pub enum Commands {
20    /// List headlines from feed
21    List,
22
23    /// Open article in browser by index
24    Open {
25        /// Article index (1-based)
26        index: usize,
27    },
28
29    /// Show article in terminal
30    Show {
31        /// Article index (1-based)
32        index: usize,
33    },
34}
35
36pub fn run_cli(cli: Cli) -> Result<()> {
37    // Get feed URL
38    let feed = if let Some(feed_name) = &cli.feed {
39        feeds::get_feed_by_name(feed_name)?
40    } else {
41        feeds::get_default_feed()
42    };
43
44    match cli.command {
45        Some(Commands::List) => list_headlines(&feed),
46        Some(Commands::Open { index }) => open_article(&feed, index),
47        Some(Commands::Show { index }) => show_article(&feed, index),
48        None => {
49            // No subcommand provided, default to listing
50            list_headlines(&feed)
51        }
52    }
53}
54
55fn list_headlines(feed: &feeds::Feed) -> Result<()> {
56    let stories = api::fetch_stories(&feed.url)?;
57
58    if stories.is_empty() {
59        println!("No stories available.");
60        return Ok(());
61    }
62
63    println!("# {}\n", feed.name);
64
65    for (i, story) in stories.iter().enumerate() {
66        let humanized_date = date_utils::humanize_time(&story.pub_date);
67        println!("{}. {} ({})", i + 1, story.title, humanized_date);
68    }
69
70    Ok(())
71}
72
73fn open_article(feed: &feeds::Feed, index: usize) -> Result<()> {
74    let stories = api::fetch_stories(&feed.url)?;
75
76    if index == 0 || index > stories.len() {
77        anyhow::bail!("Invalid article index: {}. Available: 1-{}", index, stories.len());
78    }
79
80    let story = &stories[index - 1];
81    println!("Opening: {}", story.title);
82    webbrowser::open(&story.link)?;
83    println!("Launched in browser.");
84
85    Ok(())
86}
87
88fn show_article(feed: &feeds::Feed, index: usize) -> Result<()> {
89    let stories = api::fetch_stories(&feed.url)?;
90
91    if index == 0 || index > stories.len() {
92        anyhow::bail!("Invalid article index: {}. Available: 1-{}", index, stories.len());
93    }
94
95    let story = &stories[index - 1];
96
97    // Fetch full article content
98    let article_text = article_fetcher::fetch_article_content(&story.link)?;
99
100    // Print to terminal
101    println!("{}", article_text);
102
103    Ok(())
104}