ietf

CLI for reading IETF RFCs.
git clone https://gitlab.com/kwatafana/p2p/ietf.git
Log | Files | Refs | README | LICENSE

main.rs (7053B)


      1 extern crate pager;
      2 use clap::{App, Arg, SubCommand};
      3 use cursive::align::HAlign;
      4 use cursive::event::{EventResult, Key};
      5 use cursive::traits::With;
      6 use cursive::traits::*;
      7 use cursive::views::{Dialog, EditView, LinearLayout, OnEventView, SelectView};
      8 use cursive::Cursive;
      9 use pager::Pager;
     10 
     11 mod fetch;
     12 mod storage;
     13 
     14 use std::fs::File;
     15 use std::io::{BufReader, Read};
     16 
     17 fn main() -> Result<(), std::io::Error> {
     18     let matches = App::new("ietf")
     19         .version("0.2.1")
     20         .about("CLI for reading IETF RFCs.")
     21         .before_help("██▄██ ▄▄█▄ ▄█ ▄▄\n██ ▄█ ▄▄██ ██ ▄█\n█▄▄▄█▄▄▄██▄██▄██\n▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀")
     22         .arg(
     23             Arg::with_name("Number")
     24                 .short("n")
     25                 .long("number")
     26                 .value_name("serial")
     27                 .help("RFC Serial Number")
     28                 .takes_value(true),
     29         )
     30         .arg(
     31             Arg::with_name("Remove")
     32                 .short("r")
     33                 .long("remove")
     34                 .value_name("serial")
     35                 .help("RFC Serial Number")
     36                 .takes_value(true),
     37         )
     38         .subcommand(SubCommand::with_name("update").about("Update RFC Index"))
     39         .subcommand(SubCommand::with_name("clean").about("Remove the rfc directory"))
     40         .get_matches();
     41 
     42     let storage = storage::Storage::new();
     43 
     44     // Read RFC by rfcnumber
     45     if let Some(n) = matches.value_of("Number") {
     46         let rfc_number = n.parse::<u32>().unwrap();
     47 
     48         // check if RFC is downloaded
     49         if !storage.is_rfc_downloaded(rfc_number).unwrap() {
     50             println!("Fetching RFC...");
     51 
     52             // download RFC
     53             let rfc_data = fetch::rfc(rfc_number).unwrap();
     54 
     55             // persist RFC
     56             storage.persist_rfc(rfc_number, &rfc_data);
     57         }
     58 
     59         let rfc_file_path = format!("{}{}", storage.rfc_dir_path, rfc_number);
     60 
     61         let mut rfc_data = String::new();
     62         let index_file = File::open(&rfc_file_path).expect("Unable to open file");
     63         let mut buffer_reader = BufReader::new(index_file);
     64         buffer_reader
     65             .read_to_string(&mut rfc_data)
     66             .expect("Unable to read RFC");
     67 
     68         Pager::with_pager("less -r").setup();
     69         println!("{}", rfc_data);
     70         return Ok(());
     71     }
     72 
     73     // Removes RFC by serial number
     74     if let Some(n) = matches.value_of("Remove") {
     75         storage.remove(
     76             n.parse::<u32>()
     77                 .expect("RFC Serial Number should be a numeric value!"),
     78         );
     79         return Ok(());
     80     }
     81 
     82     // Update RFC index
     83     if let Some(_matches) = matches.subcommand_matches("update") {
     84         storage.update_index();
     85         return Ok(());
     86     }
     87 
     88     // Remove the ietf directory
     89     if let Some(_matches) = matches.subcommand_matches("clean") {
     90         storage.clean();
     91         return Ok(());
     92     }
     93 
     94     // ---------- Display RFC list view ------------
     95     let mut siv = cursive::default();
     96     siv.set_theme(cursive::theme::Theme::default().with(|theme| {
     97         use cursive::theme::{BaseColor::*, Color::*, PaletteColor::*};
     98         theme.palette[Background] = TerminalDefault;
     99         theme.palette[Primary] = Dark(Black);
    100         // theme.palette[Secondary] = Rgb(255, 12, 42);
    101     }));
    102 
    103     let mut index_data = String::new();
    104     let index_file = File::open(&storage.index_file_path).expect("Unable to open file");
    105     let mut buffer_reader = BufReader::new(index_file);
    106     let mut _read_more_dots = "";
    107 
    108     buffer_reader
    109         .read_to_string(&mut index_data)
    110         .expect("Unable to read INDEX");
    111 
    112     siv.set_user_data(index_data);
    113     let index_lines = siv.user_data::<String>().unwrap().lines();
    114 
    115     let select = SelectView::new()
    116         .with_all_str(index_lines)
    117         // Sets the callback for when "Enter" is pressed.
    118         .on_submit(show_next_window)
    119         // Center the text horizontally
    120         .h_align(HAlign::Center)
    121         .with_name("select");
    122 
    123     // Let's override the `p` and `n` keys for navigation
    124     let select = OnEventView::new(select)
    125         .on_pre_event_inner('j', |s, _| {
    126             let cb = s.get_mut().select_down(1);
    127             Some(EventResult::Consumed(Some(cb)))
    128         })
    129         .on_pre_event_inner('k', |s, _| {
    130             let cb = s.get_mut().select_up(1);
    131             Some(EventResult::Consumed(Some(cb)))
    132         })
    133         .on_pre_event_inner('p', |s, _| {
    134             let cb = s.get_mut().select_up(1);
    135             Some(EventResult::Consumed(Some(cb)))
    136         })
    137         .on_pre_event_inner('n', |s, _| {
    138             let cb = s.get_mut().select_down(1);
    139             Some(EventResult::Consumed(Some(cb)))
    140         });
    141 
    142     // search window
    143     let search = EditView::new().on_edit(on_edit).on_submit(on_submit);
    144     let search = search.fixed_width(40);
    145 
    146     // verticla layout
    147     let linear_layout = LinearLayout::vertical()
    148         .child(Dialog::around(search))
    149         .child(Dialog::around(select.scrollable().fixed_size((40, 15))).title("IETF RFC INDEX"));
    150     siv.add_global_callback(Key::Esc, |s| s.quit());
    151     siv.add_layer(linear_layout);
    152     siv.run();
    153     Ok(())
    154 }
    155 
    156 // update select list when input modified
    157 fn on_edit(siv: &mut Cursive, query: &str, _size: usize) {
    158     let mut select = siv.find_name::<SelectView>("select").unwrap();
    159     // clear first
    160     select.clear();
    161     let filtered_lines = siv.user_data::<String>().unwrap().lines().filter(|&line| {
    162         let line = line.to_lowercase();
    163         let query = query.to_lowercase();
    164         line.contains(&query)
    165     });
    166     select.add_all_str(filtered_lines);
    167 }
    168 
    169 // show next window using the first match
    170 fn on_submit(siv: &mut Cursive, _query: &str) {
    171     let select = siv.find_name::<SelectView>("select").unwrap();
    172     if !select.is_empty() {
    173         let rfc_title = &*select.selection().unwrap();
    174         show_next_window(siv, rfc_title);
    175     }
    176     // no-op
    177 }
    178 // Let's put the callback in a separate function to keep it clean,
    179 // but it's not required.
    180 fn show_next_window(siv: &mut Cursive, rfc_title: &str) {
    181     let storage = storage::Storage::new();
    182     let rfc_title: Vec<&str> = rfc_title.split(' ').collect();
    183 
    184     let rfc_number = rfc_title[0]
    185         .parse::<u32>()
    186         .expect("Could not parse RFC number");
    187 
    188     // check if RFC is downloaded
    189     if !storage.is_rfc_downloaded(rfc_number).unwrap() {
    190         println!("Fetching RFC...");
    191         // download RFC
    192         let rfc_data = fetch::rfc(rfc_number).unwrap();
    193 
    194         // persist RFC
    195         storage.persist_rfc(rfc_number, &rfc_data);
    196     }
    197 
    198     let rfc_file_path = format!("{}{}", storage.rfc_dir_path, rfc_number);
    199 
    200     let mut rfc_data = String::new();
    201     let index_file = File::open(&rfc_file_path).expect("Unable to open file");
    202     let mut buffer_reader = BufReader::new(index_file);
    203     buffer_reader
    204         .read_to_string(&mut rfc_data)
    205         .expect("Unable to read RFC");
    206 
    207     siv.dump();
    208     Pager::with_pager("less -r").setup();
    209     println!("{}", rfc_data);
    210     siv.quit();
    211 }