Compare commits
8 Commits
8a35904c79
...
a41197562b
Author | SHA1 | Date | |
---|---|---|---|
|
a41197562b | ||
|
0ace41f545 | ||
|
cefe57b980 | ||
|
fe3f0579ad | ||
|
cb1a1c24b7 | ||
|
bc09317d4b | ||
|
c0d376c79f | ||
|
cdb6babd48 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -673,7 +673,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reel-moby"
|
name = "reel-moby"
|
||||||
version = "0.10.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "reel-moby"
|
name = "reel-moby"
|
||||||
version = "0.11.0"
|
version = "1.0.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -2,7 +2,7 @@ use std::path::PathBuf;
|
|||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
mod repo;
|
mod repo;
|
||||||
mod tags;
|
mod repository;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod widget;
|
mod widget;
|
||||||
|
|
||||||
|
72
src/repository/dockerhub.rs
Normal file
72
src/repository/dockerhub.rs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::repository::Error;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
struct ImageDetails {
|
||||||
|
architecture: String,
|
||||||
|
size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
pub struct Images {
|
||||||
|
images: Vec<ImageDetails>,
|
||||||
|
#[serde(rename(deserialize = "name"))]
|
||||||
|
tag_name: String,
|
||||||
|
last_updated: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Images {
|
||||||
|
pub fn convert(&self) -> super::Tag {
|
||||||
|
super::Tag {
|
||||||
|
name: self.tag_name.clone(),
|
||||||
|
last_updated: Some(self.last_updated.clone()),
|
||||||
|
details: self
|
||||||
|
.images
|
||||||
|
.iter()
|
||||||
|
.map(|d| super::TagDetails {
|
||||||
|
arch: Some(d.architecture.clone()),
|
||||||
|
size: Some(d.size),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct DockerHub {
|
||||||
|
#[serde(rename(deserialize = "next"))]
|
||||||
|
next_page: Option<String>,
|
||||||
|
results: Vec<Images>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DockerHub {
|
||||||
|
/// fetches tag information with a repository name in the form of organization/repository or library/repository in the case of official images from docker
|
||||||
|
pub fn create_repo(repo: &str) -> Result<super::Repo, Error> {
|
||||||
|
let request = format!("https://hub.docker.com/v2/repositories/{}/tags", repo);
|
||||||
|
Self::with_url(&request)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// fetches tag information from a url
|
||||||
|
pub fn with_url(url: &str) -> Result<super::Repo, Error> {
|
||||||
|
let response = match reqwest::blocking::get(url) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(e) => return Err(Error::Fetching(format!("reqwest error: {}", e))),
|
||||||
|
};
|
||||||
|
|
||||||
|
//convert it to json
|
||||||
|
let tags = match response.json::<Self>() {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(e) => return Err(Error::Converting(format!("invalid json: {}", e))),
|
||||||
|
};
|
||||||
|
|
||||||
|
if tags.results.is_empty() {
|
||||||
|
return Err(Error::NoTagsFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(super::Repo {
|
||||||
|
tags: tags.results.iter().map(|t| t.convert()).collect(),
|
||||||
|
next_page: tags.next_page,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
73
src/repository/ghcr.rs
Normal file
73
src/repository/ghcr.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::repository::Error;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Token {
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Ghcr {
|
||||||
|
tags: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ghcr {
|
||||||
|
/// fetches tag information with a repository name in the form of organization/repository or library/repository in the case of official images from docker
|
||||||
|
pub fn create_repo(repo: &str) -> Result<super::Repo, Error> {
|
||||||
|
let request_token = format!("https://ghcr.io/token?scope=repository:{}:pull", repo);
|
||||||
|
let response = match reqwest::blocking::get(request_token) {
|
||||||
|
Err(e) => return Err(Error::Fetching(format!("reqwest error: {}", e))),
|
||||||
|
Ok(response) => response,
|
||||||
|
};
|
||||||
|
|
||||||
|
let token = match response.json::<Token>() {
|
||||||
|
Err(e) => return Err(Error::Converting(format!("invalid token json: {}", e))),
|
||||||
|
Ok(token) => token.token,
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = format!("https://ghcr.io/v2/{}/tags/list?n=100", repo);
|
||||||
|
let client = reqwest::blocking::Client::new();
|
||||||
|
let response = match client
|
||||||
|
.get(request)
|
||||||
|
.header(reqwest::header::AUTHORIZATION, format!("Bearer {}", token))
|
||||||
|
.send()
|
||||||
|
{
|
||||||
|
// let response = match reqwest::blocking::get(url) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(e) => return Err(Error::Fetching(format!("reqwest error: {}", e))),
|
||||||
|
};
|
||||||
|
|
||||||
|
//convert it to json
|
||||||
|
let tags = match response.json::<Self>() {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(e) => return Err(Error::Converting(format!("invalid json: {}", e))),
|
||||||
|
};
|
||||||
|
|
||||||
|
if tags.tags.is_empty() {
|
||||||
|
return Err(Error::NoTagsFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(super::Repo {
|
||||||
|
tags: tags
|
||||||
|
.tags
|
||||||
|
.iter()
|
||||||
|
.map(|t| super::Tag {
|
||||||
|
name: t.clone(),
|
||||||
|
details: vec![],
|
||||||
|
last_updated: None,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
next_page: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Ghcr;
|
||||||
|
#[test]
|
||||||
|
fn test_ghcr() {
|
||||||
|
Ghcr::create_repo("ghcr.io/linuxserver/beets").unwrap();
|
||||||
|
}
|
||||||
|
}
|
183
src/repository/mod.rs
Normal file
183
src/repository/mod.rs
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
mod dockerhub;
|
||||||
|
mod ghcr;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use chrono::DateTime;
|
||||||
|
|
||||||
|
use crate::repo;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Error {
|
||||||
|
/// couldn't fetch json with reqwest
|
||||||
|
Fetching(String),
|
||||||
|
/// a serde error
|
||||||
|
Converting(String),
|
||||||
|
/// invalid repos show a valid json with 0 tags
|
||||||
|
NoTagsFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::Fetching(s) => write!(f, "Fetching error: {}", s),
|
||||||
|
Error::Converting(s) => write!(f, "Converting error: {}", s),
|
||||||
|
Error::NoTagsFound => write!(f, "Given Repo has 0 tags. Is it valid?"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TagDetails {
|
||||||
|
arch: Option<String>,
|
||||||
|
size: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TagDetails {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let size = match self.size {
|
||||||
|
None => "".to_string(),
|
||||||
|
Some(s) => (s / 1024 / 1024).to_string(),
|
||||||
|
};
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}|{}MB",
|
||||||
|
self.arch.as_ref().unwrap_or(&"".to_string()),
|
||||||
|
size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Tag {
|
||||||
|
name: String,
|
||||||
|
details: Vec<TagDetails>,
|
||||||
|
last_updated: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tag {
|
||||||
|
pub fn get_name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_name_with_details(&self) -> String {
|
||||||
|
//architecture infos
|
||||||
|
let mut arch = String::new();
|
||||||
|
for image in self.details.iter().take(1) {
|
||||||
|
arch.push_str(&format!("{}", image));
|
||||||
|
}
|
||||||
|
for image in self.details.iter().skip(1) {
|
||||||
|
arch.push_str(&format!(", {}", image));
|
||||||
|
}
|
||||||
|
let arch = if !arch.is_empty() {
|
||||||
|
format!(" [{}]", arch)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let dif = match &self.last_updated {
|
||||||
|
None => "".to_string(),
|
||||||
|
Some(last_updated) => {
|
||||||
|
let now = chrono::Utc::now();
|
||||||
|
let rfc3339 = DateTime::parse_from_rfc3339(last_updated).unwrap();
|
||||||
|
let dif = now - rfc3339.with_timezone(&chrono::Utc);
|
||||||
|
format!(" vor {}", format_time_nice(dif))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if dif.is_empty() {}
|
||||||
|
format!("{}{}{}", self.name, dif, arch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Repo {
|
||||||
|
tags: Vec<Tag>,
|
||||||
|
next_page: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Repo {
|
||||||
|
pub fn new(repo: &str) -> Result<Self, Error> {
|
||||||
|
use crate::repo::Repo;
|
||||||
|
let (registry, repo) = match crate::repo::split_repo_without_tag(repo) {
|
||||||
|
Ok(Repo::WithServer(reg, org, pro)) => (Some(reg), format!("{}/{}", org, pro)),
|
||||||
|
Ok(Repo::WithOrga(org, pro)) => (None, format!("{}/{}", org, pro)),
|
||||||
|
Ok(Repo::Project(pro)) => (None, format!("library/{}", pro)),
|
||||||
|
Err(e) => return Err(Error::Converting(format!("{}", e))),
|
||||||
|
};
|
||||||
|
|
||||||
|
if registry.unwrap_or_default() == "ghcr.io" {
|
||||||
|
ghcr::Ghcr::create_repo(&repo)
|
||||||
|
} else {
|
||||||
|
dockerhub::DockerHub::create_repo(&repo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_url(url: &str) -> Result<Self, Error> {
|
||||||
|
//TODO fix for other registries
|
||||||
|
dockerhub::DockerHub::with_url(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tags(&self) -> &Vec<Tag> {
|
||||||
|
&self.tags
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_page(&self) -> Option<Self> {
|
||||||
|
match &self.next_page {
|
||||||
|
Some(url) => match Self::with_url(url) {
|
||||||
|
Ok(tags) => Some(tags),
|
||||||
|
Err(_) => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// converts a given duration to a readable string
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// checks the repo name and may add a prefix for official images
|
||||||
|
pub fn check_repo(name: &str) -> Result<String, Error> {
|
||||||
|
let repo = match repo::split_tag_from_repo(name) {
|
||||||
|
Err(e) => return Err(Error::Converting(format!("{}", e))),
|
||||||
|
Ok((name, _)) => name,
|
||||||
|
};
|
||||||
|
|
||||||
|
match repo::split_repo_without_tag(name) {
|
||||||
|
Ok(repo::Repo::Project(s)) => Ok(format!("library/{}", s)),
|
||||||
|
Ok(_) => Ok(repo.to_string()),
|
||||||
|
Err(e) => Err(Error::Converting(format!("{}", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn test_check_repo() {
|
||||||
|
assert_eq!(super::check_repo("nginx").unwrap(), "library/nginx");
|
||||||
|
assert_eq!(super::check_repo("library/nginx").unwrap(), "library/nginx");
|
||||||
|
assert_eq!(
|
||||||
|
super::check_repo("rocketchat/rocket.chat").unwrap(),
|
||||||
|
"rocketchat/rocket.chat"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
168
src/tags.rs
168
src/tags.rs
@ -1,168 +0,0 @@
|
|||||||
use std::fmt;
|
|
||||||
|
|
||||||
use crate::repo;
|
|
||||||
use chrono::DateTime;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
|
||||||
struct ImageDetails {
|
|
||||||
architecture: String,
|
|
||||||
size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ImageDetails {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}|{}MB", self.architecture, self.size / 1024 / 1024)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
|
||||||
pub struct Images {
|
|
||||||
images: Vec<ImageDetails>,
|
|
||||||
#[serde(rename(deserialize = "name"))]
|
|
||||||
pub tag_name: String,
|
|
||||||
last_updated: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Images {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
//architecture infos
|
|
||||||
let mut arch = String::new();
|
|
||||||
for image in self.images.iter().take(1) {
|
|
||||||
arch.push_str(&format!("{}", image));
|
|
||||||
}
|
|
||||||
for image in self.images.iter().skip(1) {
|
|
||||||
arch.push_str(&format!(", {}", image));
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
arch
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct Tags {
|
|
||||||
count: usize,
|
|
||||||
#[serde(rename(deserialize = "next"))]
|
|
||||||
pub next_page: Option<String>,
|
|
||||||
pub results: Vec<Images>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Error {
|
|
||||||
/// couldn't fetch json with reqwest
|
|
||||||
Fetching(String),
|
|
||||||
/// a serde error
|
|
||||||
Converting(String),
|
|
||||||
/// invalid repos show a valid json with 0 tags
|
|
||||||
NoTagsFound,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::Fetching(s) => write!(f, "Fetching error: {}", s),
|
|
||||||
Error::Converting(s) => write!(f, "Converting error: {}", s),
|
|
||||||
Error::NoTagsFound => write!(f, "Given Repo has 0 tags. Is it valid?"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tags {
|
|
||||||
/// fetches tag information with a repository name in the form of organization/repository or library/repository in the case of official images from docker
|
|
||||||
pub fn new(repo: String) -> Result<Self, Error> {
|
|
||||||
let request = format!("https://hub.docker.com/v2/repositories/{}/tags", repo);
|
|
||||||
Self::with_url(&request)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// fetches tag information from a url
|
|
||||||
fn with_url(url: &str) -> Result<Self, Error> {
|
|
||||||
let res = match reqwest::blocking::get(url) {
|
|
||||||
Ok(result) => result,
|
|
||||||
Err(e) => return Err(Error::Fetching(format!("reqwest error: {}", e))),
|
|
||||||
};
|
|
||||||
|
|
||||||
//convert it to json
|
|
||||||
let raw = res.text().unwrap();
|
|
||||||
let tags: Self = match serde_json::from_str(&raw) {
|
|
||||||
Ok(result) => result,
|
|
||||||
Err(e) => return Err(Error::Converting(format!("invalid json: {}", e))),
|
|
||||||
};
|
|
||||||
|
|
||||||
if tags.count == 0 {
|
|
||||||
return Err(Error::NoTagsFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// checks the repo name and may add a prefix for official images
|
|
||||||
pub fn check_repo(name: &str) -> Result<String, Error> {
|
|
||||||
let repo = match repo::split_tag_from_repo(name) {
|
|
||||||
Err(e) => return Err(Error::Converting(format!("{}", e))),
|
|
||||||
Ok((name, _)) => name,
|
|
||||||
};
|
|
||||||
|
|
||||||
match repo::split_repo_without_tag(name) {
|
|
||||||
Ok(repo::Repo::Project(s)) => Ok(format!("library/{}", s)),
|
|
||||||
Ok(_) => Ok(repo.to_string()),
|
|
||||||
Err(e) => Err(Error::Converting(format!("{}", e))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns tags of next page
|
|
||||||
pub fn next_page(&self) -> Option<Self> {
|
|
||||||
match &self.next_page {
|
|
||||||
Some(url) => match Self::with_url(url) {
|
|
||||||
Ok(tags) => Some(tags),
|
|
||||||
Err(_) => None,
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// converts a given duration to a readable string
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::tags::Tags;
|
|
||||||
#[test]
|
|
||||||
fn test_check_repo() {
|
|
||||||
assert_eq!(Tags::check_repo("nginx").unwrap(), "library/nginx");
|
|
||||||
assert_eq!(Tags::check_repo("library/nginx").unwrap(), "library/nginx");
|
|
||||||
assert_eq!(
|
|
||||||
Tags::check_repo("rocketchat/rocket.chat").unwrap(),
|
|
||||||
"rocketchat/rocket.chat"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,6 +7,7 @@ use tui::backend::TermionBackend;
|
|||||||
use tui::layout::{Constraint, Direction, Layout};
|
use tui::layout::{Constraint, Direction, Layout};
|
||||||
use tui::Terminal;
|
use tui::Terminal;
|
||||||
|
|
||||||
|
use crate::repository;
|
||||||
use crate::widget::info;
|
use crate::widget::info;
|
||||||
use crate::widget::repo_entry;
|
use crate::widget::repo_entry;
|
||||||
use crate::widget::service_switcher;
|
use crate::widget::service_switcher;
|
||||||
@ -156,7 +157,7 @@ impl Ui {
|
|||||||
match ui.services.extract_repo() {
|
match ui.services.extract_repo() {
|
||||||
Err(e) => ui.info.set_info(&format!("{}", e)),
|
Err(e) => ui.info.set_info(&format!("{}", e)),
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
let repo = match crate::tags::Tags::check_repo(&s) {
|
let repo = match repository::check_repo(&s) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ui.info.set_info(&format!("{}", e));
|
ui.info.set_info(&format!("{}", e));
|
||||||
continue;
|
continue;
|
||||||
@ -177,7 +178,7 @@ impl Ui {
|
|||||||
match ui.services.extract_repo() {
|
match ui.services.extract_repo() {
|
||||||
Err(e) => ui.info.set_info(&format!("{}", e)),
|
Err(e) => ui.info.set_info(&format!("{}", e)),
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
let repo = match crate::tags::Tags::check_repo(&s) {
|
let repo = match repository::check_repo(&s) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ui.info.set_info(&format!("{}", e));
|
ui.info.set_info(&format!("{}", e));
|
||||||
continue;
|
continue;
|
||||||
|
@ -4,7 +4,7 @@ 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;
|
use crate::repository;
|
||||||
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
NoneSelected,
|
NoneSelected,
|
||||||
@ -24,7 +24,7 @@ impl fmt::Display for Error {
|
|||||||
|
|
||||||
enum Line {
|
enum Line {
|
||||||
Status(String),
|
Status(String),
|
||||||
Image(tags::Images),
|
Image(repository::Tag),
|
||||||
NextPage(String),
|
NextPage(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ impl fmt::Display for Line {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Line::Status(s) => write!(f, "{}", s),
|
Line::Status(s) => write!(f, "{}", s),
|
||||||
Line::Image(i) => write!(f, "{}", i),
|
Line::Image(i) => write!(f, "{}", i.get_name_with_details()),
|
||||||
Line::NextPage(s) => write!(f, "{}", s),
|
Line::NextPage(s) => write!(f, "{}", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ impl fmt::Display for Line {
|
|||||||
pub struct TagList {
|
pub struct TagList {
|
||||||
lines: Vec<Line>,
|
lines: Vec<Line>,
|
||||||
state: ListState,
|
state: ListState,
|
||||||
tags: Option<tags::Tags>,
|
tags: Option<repository::Repo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TagList {
|
impl TagList {
|
||||||
@ -54,15 +54,15 @@ impl TagList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_repo_name(repo: String) -> Self {
|
pub fn with_repo_name(repo: String) -> Self {
|
||||||
match tags::Tags::new(repo) {
|
match repository::Repo::new(&repo) {
|
||||||
Ok(tags) => Self::with_tags(tags),
|
Ok(tags) => Self::with_tags(tags),
|
||||||
Err(_) => Self::with_status("input repo was not found"),
|
Err(_) => Self::with_status("input repo was not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_tags(mut tags: tags::Tags) -> Self {
|
pub fn with_tags(mut tags: repository::Repo) -> Self {
|
||||||
let mut lines: Vec<Line> = tags
|
let mut lines: Vec<Line> = tags
|
||||||
.results
|
.get_tags()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|r| Line::Image(r.clone()))
|
.map(|r| Line::Image(r.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
@ -136,7 +136,7 @@ impl TagList {
|
|||||||
None => Err(Error::NoneSelected),
|
None => Err(Error::NoneSelected),
|
||||||
Some(i) => match &self.lines[i] {
|
Some(i) => match &self.lines[i] {
|
||||||
Line::Status(_) => Err(Error::SelectedStatus),
|
Line::Status(_) => Err(Error::SelectedStatus),
|
||||||
Line::Image(i) => Ok(i.tag_name.clone()),
|
Line::Image(i) => Ok(i.get_name().to_string()),
|
||||||
Line::NextPage(_) => {
|
Line::NextPage(_) => {
|
||||||
self.load_next_page();
|
self.load_next_page();
|
||||||
Err(Error::NextPageSelected)
|
Err(Error::NextPageSelected)
|
||||||
@ -158,12 +158,17 @@ impl TagList {
|
|||||||
let next_page = self.lines.pop();
|
let next_page = self.lines.pop();
|
||||||
|
|
||||||
//add tags
|
//add tags
|
||||||
for image in &self.tags.as_ref().unwrap().results {
|
match &self.tags {
|
||||||
self.lines.push(Line::Image(image.clone()));
|
None => (),
|
||||||
|
Some(tags) => {
|
||||||
|
for image in tags.get_tags().iter() {
|
||||||
|
self.lines.push(Line::Image(image.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//readd next page
|
//readd next page
|
||||||
match self.tags.as_ref().unwrap().next_page {
|
match self.tags.as_ref().unwrap().next_page() {
|
||||||
None => (),
|
None => (),
|
||||||
Some(_) => self.lines.push(next_page.unwrap()),
|
Some(_) => self.lines.push(next_page.unwrap()),
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user