changed some structs; fixed correct naming for saving

This commit is contained in:
Thomas Eppers 2021-09-01 18:28:17 +02:00
parent 9023e0b0ed
commit a7c3c66b2a
4 changed files with 123 additions and 83 deletions

View File

@ -1,3 +1,5 @@
use std::fmt;
use chrono::DateTime; use chrono::DateTime;
use serde::Deserialize; use serde::Deserialize;
@ -11,20 +13,20 @@ struct ImageDetails {
} }
#[derive(Deserialize)] #[derive(Deserialize)]
struct Images { pub struct Images {
images: Vec<ImageDetails>, images: Vec<ImageDetails>,
last_updater_username: String, last_updater_username: String,
#[serde(rename(deserialize = "name"))] #[serde(rename(deserialize = "name"))]
tag_name: String, pub tag_name: String,
last_updated: String, last_updated: String,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
struct TagList { pub struct Tags {
count: i32, count: i32,
next_page: Option<String>, next_page: Option<String>,
prev_page: Option<String>, prev_page: Option<String>,
results: Vec<Images>, pub results: Vec<Images>,
} }
pub enum Error { pub enum Error {
@ -33,10 +35,8 @@ pub enum Error {
Converting(String), Converting(String),
} }
pub struct Tags {}
impl Tags { impl Tags {
pub fn get_tags(repo: String) -> Result<Vec<String>, Error> { pub fn new(repo: String) -> Result<Self, Error> {
let request = format!("https://hub.docker.com/v2/repositories/{}/tags", repo); let request = format!("https://hub.docker.com/v2/repositories/{}/tags", repo);
//check for right set of characters //check for right set of characters
@ -52,22 +52,32 @@ impl Tags {
//convert it to json //convert it to json
let raw = res.text().unwrap(); 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, Ok(result) => result,
Err(_) => return Err(Error::Converting(String::from("invalid json"))), Err(_) => return Err(Error::Converting(String::from("invalid json"))),
}; };
let now = chrono::Utc::now(); Ok(tags)
}
Ok(tags pub fn get_images(&self) -> &Vec<Images> {
.results &self.results
.iter() }
.map(|r| { }
let rfc3339 = DateTime::parse_from_rfc3339(&r.last_updated).unwrap();
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); let dif = now - rfc3339.with_timezone(&chrono::Utc);
format!("{} vor {}", r.tag_name, Self::format_time_nice(dif)) write!(f, "{} vor {}", self.tag_name, format_time_nice(dif))
}) }
.collect())
} }
fn format_time_nice(time: chrono::Duration) -> String { fn format_time_nice(time: chrono::Duration) -> String {
@ -91,4 +101,3 @@ impl Tags {
format!("{} Sekunden", time.num_seconds()) format!("{} Sekunden", time.num_seconds())
} }
} }
}

View File

@ -8,7 +8,6 @@ use tui::backend::TermionBackend;
use tui::layout::{Constraint, Direction, Layout}; use tui::layout::{Constraint, Direction, Layout};
use tui::Terminal; use tui::Terminal;
use crate::tags;
use crate::widget::repo_entry; use crate::widget::repo_entry;
use crate::widget::service_switcher; use crate::widget::service_switcher;
use crate::widget::tag_list; use crate::widget::tag_list;
@ -42,10 +41,10 @@ impl Ui {
let mut ui = Ui { let mut ui = Ui {
state: State::SelectService, state: State::SelectService,
repo: repo_entry::RepoEntry::new(repo_id), 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(), 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 //setup tui
let stdout = io::stdout().into_raw_mode().unwrap(); let stdout = io::stdout().into_raw_mode().unwrap();
@ -93,12 +92,12 @@ impl Ui {
Ok(Key::Char('\n')) => match ui.state { Ok(Key::Char('\n')) => match ui.state {
State::EditRepo => { State::EditRepo => {
ui.repo.confirm(); ui.repo.confirm();
ui.tags = ui.tags = tag_list::TagList::new(ui.repo.get());
tag_list::TagList::new_with_result(tags::Tags::get_tags(ui.repo.get()));
} }
State::SelectTag => { State::SelectTag => {
let mut repo = ui.services.extract_repo().unwrap(); 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); repo.push_str(&tag);
ui.services.change_current_line(repo); ui.services.change_current_line(repo);
} }
@ -124,29 +123,27 @@ impl Ui {
Err(_) => ui.tags = tag_list::TagList::new_line("no image found"), Err(_) => ui.tags = tag_list::TagList::new_line("no image found"),
Ok(s) => ui.repo.set(s), 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); ui.repo.handle_input(&ui.state, Key::Up);
} }
Ok(Key::Down) => { Ok(Key::Down) => match ui.state {
if ui.state == State::SelectService && ui.services.find_next_match() { State::SelectService if ui.services.find_next_match() => {
match ui.services.extract_repo() { match ui.services.extract_repo() {
Err(_) => ui.tags = tag_list::TagList::new_line("no image found"), 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) => { Ok(key) => {
ui.repo.handle_input(&ui.state, key); ui.repo.handle_input(&ui.state, Key::Down);
ui.tags.handle_input(&ui.state, key); ui.tags.handle_input(&ui.state, key);
} }
_ => (), _ => (),

View File

@ -19,6 +19,7 @@ pub struct ServiceSwitcher {
list: Vec<String>, list: Vec<String>,
state: ListState, state: ListState,
regex: Regex, regex: Regex,
changed: bool,
} }
impl ServiceSwitcher { impl ServiceSwitcher {
@ -37,6 +38,7 @@ impl ServiceSwitcher {
list, list,
state: ListState::default(), state: ListState::default(),
regex: Regex::new(r"( *image *): *(.*):([.*]??) *").unwrap(), regex: Regex::new(r"( *image *): *(.*):([.*]??) *").unwrap(),
changed: false,
} }
} }
@ -47,6 +49,11 @@ impl ServiceSwitcher {
Style::default().fg(Color::Gray) Style::default().fg(Color::Gray)
}; };
let title = match &self.changed {
true => "*docker-compose.yml*",
false => "docker-compose.yml",
};
let items: Vec<tui::widgets::ListItem> = self let items: Vec<tui::widgets::ListItem> = self
.list .list
.iter() .iter()
@ -60,7 +67,7 @@ impl ServiceSwitcher {
let items = List::new(items) let items = List::new(items)
.block( .block(
Block::default() Block::default()
.title("Tags") .title(title)
.borders(Borders::ALL) .borders(Borders::ALL)
.border_style(border_style), .border_style(border_style),
) )
@ -94,7 +101,6 @@ impl ServiceSwitcher {
} }
//nothing found //nothing found
self.state.select(None);
false false
} }
@ -127,7 +133,6 @@ impl ServiceSwitcher {
} }
//nothing found //nothing found
self.state.select(None);
false false
} }
@ -160,15 +165,18 @@ impl ServiceSwitcher {
self.list[i] = line; 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 name = "docker-compose.yml2";
let mut file = File::create(name)?; let mut file = File::create(name)?;
for line in &self.list { for line in &self.list {
file.write_all(line.as_bytes())?; file.write_all(line.as_bytes())?;
file.write_all("\n".as_bytes())?; file.write_all("\n".as_bytes())?;
} }
self.changed = false;
Ok(()) Ok(())
} }
} }

View File

@ -2,40 +2,60 @@ use termion::event::Key;
use tui::style::{Color, Style}; use tui::style::{Color, Style};
use tui::widgets::{Block, Borders, List, ListState}; use tui::widgets::{Block, Borders, List, ListState};
use crate::tags::Error; use crate::tags;
use crate::ui::State; use crate::ui::State;
pub struct TagList { pub struct TagList {
list: Vec<String>, tags: Option<tags::Tags>,
line: String,
state: ListState, state: ListState,
} }
#[derive(Debug)]
pub enum Error {
NoTags,
}
impl TagList { impl TagList {
pub fn new(items: Vec<String>) -> 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 { Self {
list: items, tags,
line,
state: ListState::default(), state: ListState::default(),
} }
} }
pub fn new_line(line: &str) -> Self { pub fn new_line(line: &str) -> Self {
Self { Self {
list: vec![String::from(line)], tags: None,
line: String::from(line),
state: ListState::default(), state: ListState::default(),
} }
} }
pub fn new_with_result(result: Result<Vec<String>, Error>) -> Self { fn print_lines(&self) -> Vec<String> {
match result { match &self.tags {
Ok(lines) => Self::new(lines), None => vec![self.line.clone()],
Err(_) => Self::new_line("Error fetching tags. Is there a typo in the Repository?"), Some(tags) => tags.results.iter().map(|r| format!("{}", r)).collect(),
} }
} }
pub fn get(&self) -> Option<String> { pub fn get_names(&self) -> Result<Vec<String>, 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<String, Error> {
match self.state.selected() { match self.state.selected() {
None => None, None => Err(Error::NoTags),
Some(i) => Some(self.list[i].clone()), Some(i) => Ok(self.get_names().unwrap()[i].clone()),
} }
} }
@ -46,11 +66,15 @@ impl TagList {
Style::default().fg(Color::Gray) Style::default().fg(Color::Gray)
}; };
let items: Vec<tui::widgets::ListItem> = self let lines = match &self.tags {
.list None => vec![self.line.clone()],
Some(_) => self.print_lines(),
};
let items: Vec<tui::widgets::ListItem> = lines
.iter() .iter()
.map(|l| { .map(|l| {
tui::widgets::ListItem::new(l.as_ref()) tui::widgets::ListItem::new(l.clone())
.style(Style::default().fg(Color::White).bg(Color::Black)) .style(Style::default().fg(Color::White).bg(Color::Black))
}) })
.collect(); .collect();
@ -84,18 +108,20 @@ impl TagList {
pub fn next(&mut self) { pub fn next(&mut self) {
match self.state.selected() { 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 => (), 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)), Some(i) => self.state.select(Some(i + 1)),
} }
} }
pub fn previous(&mut self) { pub fn previous(&mut self) {
match self.state.selected() { 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 => (), 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)), Some(i) => self.state.select(Some(i - 1)),
} }
} }