From 88cd2122e4fcdf4dd67e59505f135ed62a32941b Mon Sep 17 00:00:00 2001 From: Thomas Eppers Date: Mon, 30 Aug 2021 00:28:12 +0200 Subject: [PATCH] added view of yaml files (dummy view for now); possible switching between tags --- Cargo.lock | 27 +++++++ Cargo.toml | 1 + src/ui.rs | 46 +++++++++-- src/widget/mod.rs | 1 + src/widget/repo_entry.rs | 5 ++ src/widget/service_switcher.rs | 141 +++++++++++++++++++++++++++++++++ 6 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 src/widget/service_switcher.rs diff --git a/Cargo.lock b/Cargo.lock index 66904bf..651671a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -532,6 +541,7 @@ name = "query-docker-tags" version = "0.1.0" dependencies = [ "chrono", + "regex", "reqwest", "serde", "serde_json", @@ -606,6 +616,23 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "remove_dir_all" version = "0.5.3" diff --git a/Cargo.toml b/Cargo.toml index e379cad..05abd7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,4 @@ reqwest = { version = "0.11.4", features = ["blocking", "json"] } chrono = "0.4.19" tui = "0.16" termion = "1.5" +regex = "1.5.4" diff --git a/src/ui.rs b/src/ui.rs index 4f7718c..498caf2 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -10,26 +10,30 @@ use tui::Terminal; use crate::tags; use crate::widget::repo_entry; +use crate::widget::service_switcher; use crate::widget::tag_list; pub struct Ui { state: State, repo: crate::widget::repo_entry::RepoEntry, tags: crate::widget::tag_list::TagList, + services: crate::widget::service_switcher::ServiceSwitcher, } #[derive(PartialEq, Clone)] pub enum State { EditRepo, SelectTag, + SelectService, } impl Ui { pub fn run(repo_id: &str) { let mut ui = Ui { - state: State::EditRepo, + state: State::SelectService, repo: repo_entry::RepoEntry::new(repo_id), tags: tag_list::TagList::new(vec![String::from("Fetching Tags")]), + services: service_switcher::ServiceSwitcher::new(), }; ui.tags = tag_list::TagList::new_with_result(tags::Tags::get_tags(ui.repo.get())); @@ -48,12 +52,21 @@ impl Ui { .draw(|rect| { let chunks = Layout::default() .direction(Direction::Vertical) - .constraints([Constraint::Length(3), Constraint::Min(1)].as_ref()) + .constraints( + [ + Constraint::Min(3), + Constraint::Length(3), + Constraint::Max(7), + ] + .as_ref(), + ) .split(rect.size()); - rect.render_widget(ui.repo.render(&ui.state), chunks[0]); + let (list, state) = ui.services.render(&ui.state); + rect.render_stateful_widget(list, chunks[0], state); + rect.render_widget(ui.repo.render(&ui.state), chunks[1]); let (list, state) = ui.tags.render(&ui.state); - rect.render_stateful_widget(list, chunks[1], state); + rect.render_stateful_widget(list, chunks[2], state); }) .unwrap(); @@ -62,7 +75,8 @@ impl Ui { Ok(Key::Char('\t')) => { ui.state = match ui.state { State::EditRepo => State::SelectTag, - State::SelectTag => State::EditRepo, + State::SelectTag => State::SelectService, + State::SelectService => State::EditRepo, }; } Ok(Key::Ctrl('q')) => break 'core, //quit program without saving @@ -92,6 +106,26 @@ impl Ui { ui.repo.handle_input(&ui.state, Key::Backspace); ui.tags.handle_input(&ui.state, Key::Backspace); } + Ok(Key::Up) => { + if ui.state == State::SelectService && ui.services.find_previous_match() { + match ui.services.extract_repo() { + Err(_) => (), //TODO handle + Ok(s) => ui.repo.set(s), + } + } + ui.repo.handle_input(&ui.state, Key::Up); + ui.tags.handle_input(&ui.state, Key::Up); + } + Ok(Key::Down) => { + if ui.state == State::SelectService && ui.services.find_next_match() { + match ui.services.extract_repo() { + Err(_) => (), //TODO handle + Ok(s) => ui.repo.set(s), + } + } + ui.repo.handle_input(&ui.state, Key::Down); + ui.tags.handle_input(&ui.state, Key::Down); + } Ok(key) => { ui.repo.handle_input(&ui.state, key); ui.tags.handle_input(&ui.state, key); @@ -99,7 +133,7 @@ impl Ui { _ => (), } - //sleep for 64ms (15 fps) + //sleep for 32ms (30 fps) thread::sleep(std::time::Duration::from_millis(32)); } } diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 7d2cab9..6d86048 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -1,2 +1,3 @@ pub mod repo_entry; +pub mod service_switcher; pub mod tag_list; diff --git a/src/widget/repo_entry.rs b/src/widget/repo_entry.rs index e3bb313..ed33fa4 100644 --- a/src/widget/repo_entry.rs +++ b/src/widget/repo_entry.rs @@ -24,6 +24,11 @@ impl RepoEntry { self.text.clone() } + pub fn set(&mut self, entry: String) { + self.text = entry.clone(); + self.old_text = entry; + } + pub fn render(&self, state: &crate::ui::State) -> Paragraph { let title = match self.changed { true => "Repository*", diff --git a/src/widget/service_switcher.rs b/src/widget/service_switcher.rs new file mode 100644 index 0000000..435daea --- /dev/null +++ b/src/widget/service_switcher.rs @@ -0,0 +1,141 @@ +// use std::fs::File; +// use std::io::BufWriter; + +use regex::Regex; +use termion::event::Key; +use tui::style::{Color, Style}; +use tui::widgets::{Block, Borders, List, ListState}; + +use crate::ui::State; + +pub enum Error { + NoneSelected, + Parsing(String), +} + +pub struct ServiceSwitcher { + list: Vec, + state: ListState, + regex: Regex, +} + +impl ServiceSwitcher { + pub fn new() -> Self { + let list: Vec = vec![ + String::from("dies"), + String::from("ist"), + String::from(" image: rocketchat/rocket.chat:latest"), + String::from("ein"), + String::from("test"), + String::from(" image: sdfsfdsf:latest"), + ]; + Self { + list, + state: ListState::default(), + regex: Regex::new(r"^ *image *:.*").unwrap(), + } + } + + pub fn render(&mut self, state: &State) -> (List, &mut ListState) { + let border_style = if state == &State::SelectService { + Style::default().fg(Color::Green) + } else { + Style::default().fg(Color::Gray) + }; + + let items: Vec = self + .list + .iter() + .map(|l| { + tui::widgets::ListItem::new(l.as_ref()) + .style(Style::default().fg(Color::White).bg(Color::Black)) + }) + .collect(); + + // Create a List from all list items and highlight the currently selected one + let items = List::new(items) + .block( + Block::default() + .title("Tags") + .borders(Borders::ALL) + .border_style(border_style), + ) + .style(Style::default().fg(Color::White).bg(Color::Black)) + .highlight_style(Style::default().bg(Color::Black)) + .highlight_symbol(">>"); + + (items, &mut self.state) + } + + pub fn find_next_match(&mut self) -> bool { + let current_line: usize = match self.state.selected() { + None => 0, + Some(i) => i, + }; + + let mut i = (current_line + 1) % self.list.len(); + loop { + if i == current_line { + //looped through the list + break; + } + + //check if line matches + if self.regex.is_match(&self.list[i]) { + self.state.select(Some(i)); + return true; + } + + i = (i + 1) % self.list.len(); //iterate + } + + //nothing found + self.state.select(None); + false + } + + pub fn find_previous_match(&mut self) -> bool { + let current_line: usize = match self.state.selected() { + None => 0, + Some(i) => i, + }; + + let mut i: usize = if current_line == 0 { + self.list.len() - 1 + } else { + current_line - 1 + }; + + loop { + if i == current_line { + //looped through the list + break; + } + + //check if line matches + if self.regex.is_match(&self.list[i]) { + self.state.select(Some(i)); + return true; + } + + //iterate + i = if i == 0 { self.list.len() - 1 } else { i - 1 } + } + + //nothing found + self.state.select(None); + false + } + + pub fn extract_repo(&self) -> Result { + let regex = Regex::new(r"( *image *): *(.*[:.*]?) *").unwrap(); + match self.state.selected() { + None => return Err(Error::NoneSelected), + Some(i) => { + let caps = regex.captures(&self.list[i]).unwrap(); + let result: String = caps.get(2).unwrap().as_str().to_string(); + return Ok(result); + } + } + } +}