Finished cleaning
This commit is contained in:
parent
8cf6a2e556
commit
1b15bd678c
53
src/cache.rs
53
src/cache.rs
|
@ -1,10 +1,18 @@
|
|||
//! This module contains the cache manager.
|
||||
|
||||
use std::num::Wrapping;
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::{Write, BufRead, BufReader};
|
||||
use std::io::{stdin, stdout, Write, BufRead, BufReader};
|
||||
use walkdir::WalkDir;
|
||||
use crate::{Result, GCLONE_PATH};
|
||||
use colored::*;
|
||||
use crate::{Error, Result};
|
||||
use crate::git::GCLONE_PATH;
|
||||
|
||||
/// Flushes the stdout.
|
||||
fn flush_stdout() {
|
||||
stdout().flush().ok();
|
||||
}
|
||||
|
||||
/// The cache of gclone.
|
||||
///
|
||||
|
@ -79,6 +87,47 @@ impl Cache {
|
|||
matches
|
||||
}
|
||||
|
||||
/// Searches a directory, if multiple where found, prompt user for answer.
|
||||
pub fn find_interactive(&self, dirname: &str) -> Result<String> {
|
||||
|
||||
let matches = self.find(dirname);
|
||||
match matches.len() {
|
||||
|
||||
0 => Err(Error::NoSuchDirectory),
|
||||
1 => Ok(matches[0].clone()),
|
||||
|
||||
len => {
|
||||
|
||||
eprintln!("{}", "info: multiple entries found, please select one".bold());
|
||||
|
||||
for (i, j) in matches.iter().enumerate() {
|
||||
eprintln!("{} {}",
|
||||
&format!("[{}]", i + 1).bold().blue(),
|
||||
&format!("{}", j).yellow());
|
||||
}
|
||||
|
||||
eprint!("{}", "Enter your choice: ".bold());
|
||||
flush_stdout();
|
||||
|
||||
let mut string = String::new();
|
||||
stdin().read_line(&mut string)?;
|
||||
|
||||
// Pop the trailing \n of the string
|
||||
string.pop();
|
||||
|
||||
// Use wrapping to move 0 to std::usize::MAX which is ok.
|
||||
let value = (Wrapping(string.parse::<usize>()?) - Wrapping(1)).0;
|
||||
|
||||
if value >= len {
|
||||
return Err(Error::OutsideRange);
|
||||
}
|
||||
|
||||
Ok(matches[value].clone())
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Appends a value to the cache.
|
||||
pub fn append(&mut self, value: String) {
|
||||
self.0.push(value);
|
||||
|
|
136
src/gclone.rs
136
src/gclone.rs
|
@ -1,88 +1,25 @@
|
|||
use std::env;
|
||||
use std::process::{Command, exit};
|
||||
use std::fs::{create_dir_all, remove_dir_all};
|
||||
use std::error::Error;
|
||||
use std::process::exit;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use colored::*;
|
||||
|
||||
use gclone::{GCLONE_PATH, Cache, Result};
|
||||
|
||||
macro_rules! unwrap {
|
||||
($e: expr) => {
|
||||
match $e {
|
||||
Some(e) => e,
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_http_url(input: &str) -> Option<(String, String, String)> {
|
||||
let split = input.split("/").collect::<Vec<_>>();
|
||||
if split.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let repo = String::from(split[split.len() - 1]);
|
||||
let owner = String::from(split[split.len() - 2]);
|
||||
let server = split[2 .. split.len() - 2].join("/");
|
||||
|
||||
Some((server, owner, repo))
|
||||
}
|
||||
|
||||
fn parse_ssh_url(input: &str) -> Option<(String, String, String)> {
|
||||
let url = unwrap!(input.split("@").nth(1));
|
||||
let mut split = url.split(":");
|
||||
let server = String::from(unwrap!(split.next()));
|
||||
let rest = unwrap!(split.next());
|
||||
|
||||
let mut resplit = rest.split("/");
|
||||
let owner = String::from(unwrap!(resplit.next()));
|
||||
let repo = String::from(unwrap!(resplit.next()));
|
||||
|
||||
Some((server, owner, repo))
|
||||
}
|
||||
|
||||
fn parse_url(input: &str) -> Option<(String, String, String)> {
|
||||
if input.starts_with("http") {
|
||||
parse_http_url(input)
|
||||
} else {
|
||||
parse_ssh_url(input)
|
||||
}
|
||||
}
|
||||
use gclone::{first_arg, Cache, Result};
|
||||
use gclone::git::{GCLONE_PATH, parse_url, clone};
|
||||
|
||||
fn help() {
|
||||
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
fn main_result() -> Result<()> {
|
||||
|
||||
// Parse args
|
||||
let url = match env::args().nth(1) {
|
||||
Some(arg) => arg,
|
||||
None => {
|
||||
eprintln!("{} {}",
|
||||
"error:".bold().red(),
|
||||
"gclone expects an argument".bold());
|
||||
|
||||
exit(1);
|
||||
},
|
||||
};
|
||||
let url = first_arg()?;
|
||||
|
||||
if url == "-h" || url == "--help" {
|
||||
return Ok(help());
|
||||
}
|
||||
|
||||
let (server, owner, repo) = match parse_url(&url) {
|
||||
Some(parsed) => parsed,
|
||||
None => {
|
||||
eprintln!("{} {}",
|
||||
"error:".bold().red(),
|
||||
"couldn't guess server, owner and repo names from url".bold());
|
||||
|
||||
exit(1);
|
||||
},
|
||||
};
|
||||
let (server, owner, repo) = parse_url(&url)?;
|
||||
|
||||
// Build path
|
||||
let mut path = PathBuf::from(&*GCLONE_PATH);
|
||||
|
@ -90,64 +27,23 @@ fn main() -> Result<()> {
|
|||
path.push(&owner);
|
||||
path.push(&repo);
|
||||
|
||||
if path.exists() {
|
||||
eprintln!("{} {}",
|
||||
"error:".red().bold(),
|
||||
"the corresponding directory already exists".bold());
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
match create_dir_all(&path) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
eprintln!("{} {} {}",
|
||||
"error:".red().bold(),
|
||||
"couldn't create the corresponding directory:".bold(),
|
||||
e.description());
|
||||
|
||||
remove_dir_all(&path).ok();
|
||||
|
||||
exit(1);
|
||||
},
|
||||
}
|
||||
|
||||
// Clone repository
|
||||
eprintln!("{} {} {}{}", "info:".bold(), "cloning", url, "...");
|
||||
|
||||
let command = Command::new("git")
|
||||
.args(&["clone", &url, &path.display().to_string()])
|
||||
.output();
|
||||
|
||||
match command {
|
||||
Ok(repo) => repo,
|
||||
Err(e) => {
|
||||
eprintln!("{} {} {}",
|
||||
"error:".bold().red(),
|
||||
"couldn't clone repository:".bold(),
|
||||
e.description());
|
||||
|
||||
remove_dir_all(&path).ok();
|
||||
|
||||
exit(1);
|
||||
},
|
||||
};
|
||||
|
||||
clone(&url, &path.display().to_string())?;
|
||||
eprintln!("{} {}", "info:".bold(), "done!");
|
||||
|
||||
// Append to cache
|
||||
let mut cache = Cache::read()?;
|
||||
cache.append(path.display().to_string());
|
||||
|
||||
match cache.write() {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
eprintln!("{} {} {}",
|
||||
"warning:".bold().yellow(),
|
||||
"couldn't write cache:".bold(),
|
||||
e.description());
|
||||
},
|
||||
}
|
||||
cache.write()?;
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = main_result() {
|
||||
eprintln!("{}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
//! This module contains the utility functions to manage git repository.
|
||||
|
||||
use std::env;
|
||||
use std::fs::{create_dir_all, remove_dir_all};
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio, exit};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
lazy_static! {
|
||||
/// The directory in which the repositories will be cloned and searched.
|
||||
pub static ref GCLONE_PATH: String = {
|
||||
match env::var("GCLONE_PATH") {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
let e: Error = e.into();
|
||||
eprintln!("{}", e);
|
||||
exit(1);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! unwrap {
|
||||
($e: expr) => {
|
||||
match $e {
|
||||
Some(e) => e,
|
||||
None => return Err(Error::GitUrlParseError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms a git url to a (server, owner, repository) tuple.
|
||||
///
|
||||
/// Returns none if it failed.
|
||||
pub fn parse_url(input: &str) -> Result<(String, String, String)> {
|
||||
if input.starts_with("http") {
|
||||
parse_http_url(input)
|
||||
} else {
|
||||
parse_ssh_url(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an HTTP url for a git repository.
|
||||
fn parse_http_url(input: &str) -> Result<(String, String, String)> {
|
||||
let split = input.split("/").collect::<Vec<_>>();
|
||||
if split.len() < 3 {
|
||||
return Err(Error::GitUrlParseError);
|
||||
}
|
||||
|
||||
let repo = String::from(split[split.len() - 1]);
|
||||
let owner = String::from(split[split.len() - 2]);
|
||||
let server = split[2 .. split.len() - 2].join("/");
|
||||
|
||||
Ok((server, owner, repo))
|
||||
}
|
||||
|
||||
/// Parses an SSH url for a git repository.
|
||||
fn parse_ssh_url(input: &str) -> Result<(String, String, String)> {
|
||||
let url = unwrap!(input.split("@").nth(1));
|
||||
let mut split = url.split(":");
|
||||
let server = String::from(unwrap!(split.next()));
|
||||
let rest = unwrap!(split.next());
|
||||
|
||||
let mut resplit = rest.split("/");
|
||||
let owner = String::from(unwrap!(resplit.next()));
|
||||
let repo = String::from(unwrap!(resplit.next()));
|
||||
|
||||
Ok((server, owner, repo))
|
||||
}
|
||||
|
||||
/// Clones a git repository in the right place.
|
||||
///
|
||||
/// If an error happens, it deletes the directory created.
|
||||
pub fn clone<P: AsRef<Path>>(url: &str, place: P) -> Result<()> {
|
||||
|
||||
match clone_dirty(url, &place) {
|
||||
Ok(o) => Ok(o),
|
||||
Err(e) => {
|
||||
remove_dir_all(&place).ok();
|
||||
Err(e)
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Clones a git repository in the right place.
|
||||
fn clone_dirty<P: AsRef<Path>>(url: &str, place: P) -> Result<()> {
|
||||
|
||||
let place = place.as_ref();
|
||||
|
||||
if place.exists() {
|
||||
return Err(Error::PathAlreadyExists);
|
||||
}
|
||||
|
||||
create_dir_all(place)?;
|
||||
|
||||
let command = Command::new("git")
|
||||
.args(&["clone", &url, &place.display().to_string()])
|
||||
.stderr(Stdio::null())
|
||||
.status();
|
||||
|
||||
match command {
|
||||
Err(e) => Err(Error::GitCloneError(Some(e))),
|
||||
Ok(o) if ! o.success() => Err(Error::GitCloneError(None)),
|
||||
Ok(_) => Ok(()),
|
||||
}
|
||||
|
||||
}
|
79
src/lib.rs
79
src/lib.rs
|
@ -12,29 +12,15 @@
|
|||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::{env, fmt, error, result};
|
||||
use std::process::exit;
|
||||
use std::{env, fmt, num, error, result};
|
||||
use std::io;
|
||||
|
||||
use colored::*;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
mod cache;
|
||||
pub use cache::Cache;
|
||||
|
||||
lazy_static! {
|
||||
/// The directory in which the repositories will be cloned and searched.
|
||||
pub static ref GCLONE_PATH: String = {
|
||||
match env::var("GCLONE_PATH") {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
let e: Error = e.into();
|
||||
eprintln!("{}", e);
|
||||
exit(1);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
pub mod git;
|
||||
|
||||
macro_rules! impl_from_error {
|
||||
($type: ty, $variant: path, $from: ty) => {
|
||||
|
@ -54,10 +40,32 @@ pub enum Error {
|
|||
|
||||
/// An error occured while manipulating files.
|
||||
IoError(io::Error),
|
||||
|
||||
/// Couldn't parse git url.
|
||||
GitUrlParseError,
|
||||
|
||||
/// Couldn't clone the respository.
|
||||
GitCloneError(Option<io::Error>),
|
||||
|
||||
/// The specified directory already exists.
|
||||
PathAlreadyExists,
|
||||
|
||||
/// The binaries of this crate require exactly one argument.
|
||||
WrongArgument,
|
||||
|
||||
/// An error while parsing an int.
|
||||
ParseIntError(num::ParseIntError),
|
||||
|
||||
/// A value is outside a range.
|
||||
OutsideRange,
|
||||
|
||||
/// No such directory was found.
|
||||
NoSuchDirectory,
|
||||
}
|
||||
|
||||
impl_from_error!(Error, Error::NoGclonePath, env::VarError);
|
||||
impl_from_error!(Error, Error::IoError, io::Error);
|
||||
impl_from_error!(Error, Error::ParseIntError, num::ParseIntError);
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
|
@ -72,6 +80,41 @@ impl fmt::Display for Error {
|
|||
"error:".bold().red(),
|
||||
"couldn't read or write cache:".bold(),
|
||||
e),
|
||||
Error::GitUrlParseError =>
|
||||
write!(fmt, "{} {}",
|
||||
"error:".bold().red(),
|
||||
"couldn't guess server, owner and repo from url".bold()),
|
||||
Error::GitCloneError(Some(e)) =>
|
||||
write!(fmt, "{} {} {}",
|
||||
"error:".bold().red(),
|
||||
"couldn't clone git repository:".bold(),
|
||||
e),
|
||||
Error::GitCloneError(None) =>
|
||||
write!(fmt, "{} {}",
|
||||
"error:".bold().red(),
|
||||
"couldn't clone git repository".bold()),
|
||||
Error::PathAlreadyExists =>
|
||||
write!(fmt, "{} {}",
|
||||
"error:".bold().red(),
|
||||
"the path corresponding to the repository already exists".bold()),
|
||||
Error::WrongArgument =>
|
||||
write!(fmt, "{} {}",
|
||||
"error:".bold().red(),
|
||||
"this program expects exactly one argument".bold()),
|
||||
Error::ParseIntError(e) =>
|
||||
write!(fmt, "{} {} {}",
|
||||
"error:".bold().red(),
|
||||
"couldn't parse integer:".bold(),
|
||||
e),
|
||||
Error::OutsideRange =>
|
||||
write!(fmt, "{} {}",
|
||||
"error:".bold().red(),
|
||||
"integer is outside the range".bold()),
|
||||
Error::NoSuchDirectory =>
|
||||
write!(fmt, "{} {}",
|
||||
"error:".bold().red(),
|
||||
"no such directory was found".bold()),
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,3 +124,7 @@ impl error::Error for Error {}
|
|||
/// The result type of gclone.
|
||||
pub type Result<T> = result::Result<T, Error>;
|
||||
|
||||
/// Gets the single argument of the program.
|
||||
pub fn first_arg() -> Result<String> {
|
||||
env::args().nth(1).ok_or(Error::WrongArgument)
|
||||
}
|
||||
|
|
132
src/pgd.rs
132
src/pgd.rs
|
@ -1,130 +1,44 @@
|
|||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::process::exit;
|
||||
use std::io::{stdout, stdin, Write};
|
||||
use std::num::Wrapping;
|
||||
use colored::*;
|
||||
use gclone::{Result, Cache};
|
||||
use gclone::{first_arg, Cache, Error, Result};
|
||||
|
||||
fn flush_stdout() {
|
||||
stdout().flush().ok();
|
||||
fn main() {
|
||||
if let Err(e) = main_result() {
|
||||
eprintln!("{}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let request = match env::args().nth(1) {
|
||||
Some(arg) => arg,
|
||||
None => {
|
||||
eprintln!("{} {}",
|
||||
"error:".bold().red(),
|
||||
"cdg expects an argument".bold());
|
||||
|
||||
exit(1);
|
||||
},
|
||||
};
|
||||
|
||||
fn main_result() -> Result<()> {
|
||||
let request = first_arg()?;
|
||||
main_with_cache(&request, true)
|
||||
}
|
||||
|
||||
fn main_with_cache(request: &str, regen: bool) -> Result<()> {
|
||||
|
||||
let (cache, regen) = match Cache::read() {
|
||||
Ok(c) => (c, regen),
|
||||
Err(_) => (Cache::generate(), false),
|
||||
let (cache, just_generated) = match Cache::read() {
|
||||
Ok(c) => (c, false),
|
||||
Err(_) => (Cache::generate(), true),
|
||||
};
|
||||
|
||||
let matches = cache.find(&request);
|
||||
let only_match = match cache.find_interactive(&request) {
|
||||
|
||||
match matches.len() {
|
||||
0 => {
|
||||
|
||||
if regen {
|
||||
eprintln!("{} {}",
|
||||
"warning:".bold().yellow(),
|
||||
"directory not found, regenerating cache...".bold());
|
||||
|
||||
let cache = Cache::generate();
|
||||
|
||||
match cache.write() {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
eprintln!("{} {} {}",
|
||||
"warning:".bold().yellow(),
|
||||
"couldn't write cache:".bold(),
|
||||
e.description());
|
||||
},
|
||||
}
|
||||
|
||||
return main_with_cache(request, false);
|
||||
|
||||
} else {
|
||||
eprintln!("{} {}",
|
||||
"error:".bold().red(),
|
||||
"no such directory".bold());
|
||||
|
||||
exit(1);
|
||||
}
|
||||
Err(Error::NoSuchDirectory) if regen && ! just_generated => {
|
||||
eprintln!("{} {}",
|
||||
"warning:".bold().yellow(),
|
||||
"directory not found, regenerating cache...".bold());
|
||||
let cache = Cache::generate();
|
||||
cache.write()?;
|
||||
return main_with_cache(request, false);
|
||||
},
|
||||
|
||||
1 => {
|
||||
println!("{}", matches[0]);
|
||||
},
|
||||
Err(e) => return Err(e),
|
||||
Ok(x) => x,
|
||||
};
|
||||
|
||||
len => {
|
||||
|
||||
eprintln!("{}", "info: multiple entries found, please select one".bold());
|
||||
|
||||
for (i, j) in matches.iter().enumerate() {
|
||||
eprintln!("{} {}",
|
||||
&format!("[{}]", i + 1).bold().blue(),
|
||||
&format!("{}", j).yellow());
|
||||
}
|
||||
|
||||
eprint!("{}", "Enter your choice: ".bold());
|
||||
flush_stdout();
|
||||
|
||||
let mut string = String::new();
|
||||
match stdin().read_line(&mut string) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
eprintln!("{} {} {}",
|
||||
"error:".bold().red(),
|
||||
"couldn't read line:".bold(),
|
||||
e.description());
|
||||
|
||||
exit(1);
|
||||
},
|
||||
};
|
||||
|
||||
// Pop the trailing \n of the string
|
||||
string.pop();
|
||||
|
||||
// Use wrapping to move 0 to std::usize::MAX which is ok.
|
||||
let value = (Wrapping(match string.parse::<usize>() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("{} {} {}",
|
||||
"error:".bold().red(),
|
||||
"couldn't parse integer:".bold(),
|
||||
e.description());
|
||||
|
||||
exit(1);
|
||||
}
|
||||
}) - Wrapping(1)).0;
|
||||
|
||||
if value >= len {
|
||||
eprintln!("{} {}",
|
||||
"error:".bold().red(),
|
||||
"integer wasn't in the right range".bold());
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
println!("{}", matches[value]);
|
||||
},
|
||||
}
|
||||
println!("{}", only_match);
|
||||
|
||||
Ok(())
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue