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 }