diff --git a/src/tags.rs b/src/tags.rs index 2b4a1b2..47a1a09 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -1,3 +1,5 @@ +use std::fmt; + use chrono::DateTime; use serde::Deserialize; @@ -11,20 +13,20 @@ struct ImageDetails { } #[derive(Deserialize)] -struct Images { +pub struct Images { images: Vec, last_updater_username: String, #[serde(rename(deserialize = "name"))] - tag_name: String, + pub tag_name: String, last_updated: String, } #[derive(Deserialize)] -struct TagList { +pub struct Tags { count: i32, next_page: Option, prev_page: Option, - results: Vec, + pub results: Vec, } pub enum Error { @@ -33,10 +35,8 @@ pub enum Error { Converting(String), } -pub struct Tags {} - impl Tags { - pub fn get_tags(repo: String) -> Result, Error> { + pub fn new(repo: String) -> Result { let request = format!("https://hub.docker.com/v2/repositories/{}/tags", repo); //check for right set of characters @@ -52,43 +52,52 @@ impl Tags { //convert it to json let raw = res.text().unwrap(); - let tags: TagList = match serde_json::from_str(&raw) { + let tags: Self = match serde_json::from_str(&raw) { Ok(result) => result, Err(_) => return Err(Error::Converting(String::from("invalid json"))), }; - let now = chrono::Utc::now(); - - Ok(tags - .results - .iter() - .map(|r| { - let rfc3339 = DateTime::parse_from_rfc3339(&r.last_updated).unwrap(); - let dif = now - rfc3339.with_timezone(&chrono::Utc); - format!("{} vor {}", r.tag_name, Self::format_time_nice(dif)) - }) - .collect()) + Ok(tags) } - fn format_time_nice(time: chrono::Duration) -> String { - if time.num_weeks() == 52 { - format!("{} Jahr", (time.num_weeks() / 52) as i32) - } else if time.num_weeks() > 103 { - format!("{} Jahren", (time.num_weeks() / 52) as i32) - } else if time.num_days() == 1 { - format!("{} Tag", time.num_days()) - } else if time.num_days() > 1 { - format!("{} Tagen", time.num_days()) - } else if time.num_hours() == 1 { - format!("{} Stunde", time.num_hours()) - } else if time.num_hours() > 1 { - format!("{} Stunden", time.num_hours()) - } else if time.num_minutes() == 1 { - format!("{} Minute", time.num_minutes()) - } else if time.num_minutes() > 1 { - format!("{} Minuten", time.num_minutes()) - } else { - format!("{} Sekunden", time.num_seconds()) - } + pub fn get_images(&self) -> &Vec { + &self.results + } +} + +impl Images { + pub fn get_name(&self) -> &str { + &self.tag_name + } +} + +impl fmt::Display for Images { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let now = chrono::Utc::now(); + let rfc3339 = DateTime::parse_from_rfc3339(&self.last_updated).unwrap(); + let dif = now - rfc3339.with_timezone(&chrono::Utc); + write!(f, "{} vor {}", self.tag_name, format_time_nice(dif)) + } +} + +fn format_time_nice(time: chrono::Duration) -> String { + if time.num_weeks() == 52 { + format!("{} Jahr", (time.num_weeks() / 52) as i32) + } else if time.num_weeks() > 103 { + format!("{} Jahren", (time.num_weeks() / 52) as i32) + } else if time.num_days() == 1 { + format!("{} Tag", time.num_days()) + } else if time.num_days() > 1 { + format!("{} Tagen", time.num_days()) + } else if time.num_hours() == 1 { + format!("{} Stunde", time.num_hours()) + } else if time.num_hours() > 1 { + format!("{} Stunden", time.num_hours()) + } else if time.num_minutes() == 1 { + format!("{} Minute", time.num_minutes()) + } else if time.num_minutes() > 1 { + format!("{} Minuten", time.num_minutes()) + } else { + format!("{} Sekunden", time.num_seconds()) } } diff --git a/src/ui.rs b/src/ui.rs index e37064e..18fef50 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -8,7 +8,6 @@ use tui::backend::TermionBackend; use tui::layout::{Constraint, Direction, Layout}; use tui::Terminal; -use crate::tags; use crate::widget::repo_entry; use crate::widget::service_switcher; use crate::widget::tag_list; @@ -42,10 +41,10 @@ impl Ui { let mut ui = Ui { state: State::SelectService, repo: repo_entry::RepoEntry::new(repo_id), - tags: tag_list::TagList::new(vec![String::from("Fetching Tags")]), + tags: tag_list::TagList::new_line("Fetching Tags"), services: service_switcher::ServiceSwitcher::new(), }; - ui.tags = tag_list::TagList::new_with_result(tags::Tags::get_tags(ui.repo.get())); + ui.tags = tag_list::TagList::new(ui.repo.get()); //setup tui let stdout = io::stdout().into_raw_mode().unwrap(); @@ -93,12 +92,12 @@ impl Ui { Ok(Key::Char('\n')) => match ui.state { State::EditRepo => { ui.repo.confirm(); - ui.tags = - tag_list::TagList::new_with_result(tags::Tags::get_tags(ui.repo.get())); + ui.tags = tag_list::TagList::new(ui.repo.get()); } State::SelectTag => { let mut repo = ui.services.extract_repo().unwrap(); - let tag = ui.tags.get().unwrap(); + let tag = ui.tags.get_selected().unwrap(); + repo.push_str(":"); repo.push_str(&tag); ui.services.change_current_line(repo); } @@ -124,29 +123,27 @@ impl Ui { Err(_) => ui.tags = tag_list::TagList::new_line("no image found"), Ok(s) => ui.repo.set(s), } - } else if ui.state == State::SelectTag { - ui.tags.handle_input(&ui.state, Key::Up); - //update repo widget - let mut repo = ui.services.extract_repo().unwrap(); - let tag = ui.tags.get().unwrap(); - repo.push_str(":"); - repo.push_str(&tag); - ui.repo.set(repo); } + ui.tags.handle_input(&ui.state, Key::Up); ui.repo.handle_input(&ui.state, Key::Up); } - Ok(Key::Down) => { - if ui.state == State::SelectService && ui.services.find_next_match() { + Ok(Key::Down) => match ui.state { + State::SelectService if ui.services.find_next_match() => { match ui.services.extract_repo() { Err(_) => ui.tags = tag_list::TagList::new_line("no image found"), - Ok(s) => ui.repo.set(s), + Ok(s) => { + ui.repo.set(s); + ui.tags = tag_list::TagList::new(ui.repo.get()); + } } } - ui.repo.handle_input(&ui.state, Key::Down); - ui.tags.handle_input(&ui.state, Key::Down); - } + _ => { + ui.tags.handle_input(&ui.state, Key::Down); + ui.repo.handle_input(&ui.state, Key::Down); + } + }, Ok(key) => { - ui.repo.handle_input(&ui.state, key); + ui.repo.handle_input(&ui.state, Key::Down); ui.tags.handle_input(&ui.state, key); } _ => (), diff --git a/src/widget/service_switcher.rs b/src/widget/service_switcher.rs index 9c14489..2785552 100644 --- a/src/widget/service_switcher.rs +++ b/src/widget/service_switcher.rs @@ -19,6 +19,7 @@ pub struct ServiceSwitcher { list: Vec, state: ListState, regex: Regex, + changed: bool, } impl ServiceSwitcher { @@ -37,6 +38,7 @@ impl ServiceSwitcher { list, state: ListState::default(), regex: Regex::new(r"( *image *): *(.*):([.*]??) *").unwrap(), + changed: false, } } @@ -47,6 +49,11 @@ impl ServiceSwitcher { Style::default().fg(Color::Gray) }; + let title = match &self.changed { + true => "*docker-compose.yml*", + false => "docker-compose.yml", + }; + let items: Vec = self .list .iter() @@ -60,7 +67,7 @@ impl ServiceSwitcher { let items = List::new(items) .block( Block::default() - .title("Tags") + .title(title) .borders(Borders::ALL) .border_style(border_style), ) @@ -94,7 +101,6 @@ impl ServiceSwitcher { } //nothing found - self.state.select(None); false } @@ -127,7 +133,6 @@ impl ServiceSwitcher { } //nothing found - self.state.select(None); false } @@ -160,15 +165,18 @@ impl ServiceSwitcher { self.list[i] = line; } } + self.changed = true; } - pub fn save(&self) -> Result<(), std::io::Error> { + pub fn save(&mut self) -> Result<(), std::io::Error> { let name = "docker-compose.yml2"; let mut file = File::create(name)?; for line in &self.list { file.write_all(line.as_bytes())?; file.write_all("\n".as_bytes())?; } + + self.changed = false; Ok(()) } } diff --git a/src/widget/tag_list.rs b/src/widget/tag_list.rs index 43b0fb2..b46f925 100644 --- a/src/widget/tag_list.rs +++ b/src/widget/tag_list.rs @@ -2,40 +2,60 @@ use termion::event::Key; use tui::style::{Color, Style}; use tui::widgets::{Block, Borders, List, ListState}; -use crate::tags::Error; +use crate::tags; use crate::ui::State; pub struct TagList { - list: Vec, + tags: Option, + line: String, state: ListState, } +#[derive(Debug)] +pub enum Error { + NoTags, +} + impl TagList { - pub fn new(items: Vec) -> Self { + pub fn new(repo: String) -> Self { + let (tags, line) = match tags::Tags::new(repo) { + Err(_) => (None, String::from("Could not query tags")), + Ok(tags) => (Some(tags), String::new()), + }; + Self { - list: items, + tags, + line, state: ListState::default(), } } pub fn new_line(line: &str) -> Self { Self { - list: vec![String::from(line)], + tags: None, + line: String::from(line), state: ListState::default(), } } - pub fn new_with_result(result: Result, Error>) -> Self { - match result { - Ok(lines) => Self::new(lines), - Err(_) => Self::new_line("Error fetching tags. Is there a typo in the Repository?"), + fn print_lines(&self) -> Vec { + match &self.tags { + None => vec![self.line.clone()], + Some(tags) => tags.results.iter().map(|r| format!("{}", r)).collect(), } } - pub fn get(&self) -> Option { + pub fn get_names(&self) -> Result, Error> { + match &self.tags { + None => Err(Error::NoTags), + Some(tags) => Ok(tags.results.iter().map(|r| r.tag_name.clone()).collect()), + } + } + + pub fn get_selected(&self) -> Result { match self.state.selected() { - None => None, - Some(i) => Some(self.list[i].clone()), + None => Err(Error::NoTags), + Some(i) => Ok(self.get_names().unwrap()[i].clone()), } } @@ -46,11 +66,15 @@ impl TagList { Style::default().fg(Color::Gray) }; - let items: Vec = self - .list + let lines = match &self.tags { + None => vec![self.line.clone()], + Some(_) => self.print_lines(), + }; + + let items: Vec = lines .iter() .map(|l| { - tui::widgets::ListItem::new(l.as_ref()) + tui::widgets::ListItem::new(l.clone()) .style(Style::default().fg(Color::White).bg(Color::Black)) }) .collect(); @@ -84,18 +108,20 @@ impl TagList { pub fn next(&mut self) { match self.state.selected() { - None if self.list.len() > 0 => self.state.select(Some(0)), + None if self.print_lines().len() > 0 => self.state.select(Some(0)), None => (), - Some(i) if i == self.list.len() - 1 => self.state.select(Some(0)), + Some(i) if i == self.print_lines().len() - 1 => self.state.select(Some(0)), Some(i) => self.state.select(Some(i + 1)), } } pub fn previous(&mut self) { match self.state.selected() { - None if self.list.len() > 0 => self.state.select(Some(self.list.len())), + None if self.print_lines().len() > 0 => { + self.state.select(Some(self.print_lines().len())) + } None => (), - Some(i) if i == 0 => self.state.select(Some(self.list.len() - 1)), + Some(i) if i == 0 => self.state.select(Some(self.print_lines().len() - 1)), Some(i) => self.state.select(Some(i - 1)), } }