changed some structs; fixed correct naming for saving
This commit is contained in:
parent
9023e0b0ed
commit
a7c3c66b2a
87
src/tags.rs
87
src/tags.rs
@ -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,43 +52,52 @@ 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
|
|
||||||
.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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_time_nice(time: chrono::Duration) -> String {
|
pub fn get_images(&self) -> &Vec<Images> {
|
||||||
if time.num_weeks() == 52 {
|
&self.results
|
||||||
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 {
|
impl Images {
|
||||||
format!("{} Tag", time.num_days())
|
pub fn get_name(&self) -> &str {
|
||||||
} else if time.num_days() > 1 {
|
&self.tag_name
|
||||||
format!("{} Tagen", time.num_days())
|
}
|
||||||
} else if time.num_hours() == 1 {
|
}
|
||||||
format!("{} Stunde", time.num_hours())
|
|
||||||
} else if time.num_hours() > 1 {
|
impl fmt::Display for Images {
|
||||||
format!("{} Stunden", time.num_hours())
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
} else if time.num_minutes() == 1 {
|
let now = chrono::Utc::now();
|
||||||
format!("{} Minute", time.num_minutes())
|
let rfc3339 = DateTime::parse_from_rfc3339(&self.last_updated).unwrap();
|
||||||
} else if time.num_minutes() > 1 {
|
let dif = now - rfc3339.with_timezone(&chrono::Utc);
|
||||||
format!("{} Minuten", time.num_minutes())
|
write!(f, "{} vor {}", self.tag_name, format_time_nice(dif))
|
||||||
} else {
|
}
|
||||||
format!("{} Sekunden", time.num_seconds())
|
}
|
||||||
}
|
|
||||||
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
39
src/ui.rs
39
src/ui.rs
@ -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);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user