Finished cleaning

This commit is contained in:
Thomas Forgione 2019-02-12 14:51:45 +01:00
parent 8cf6a2e556
commit 1b15bd678c
No known key found for this signature in database
GPG Key ID: 203DAEA747F48F41
5 changed files with 264 additions and 247 deletions

View File

@ -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);

View File

@ -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);
}
}

111
src/git.rs Normal file
View File

@ -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(()),
}
}

View File

@ -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)
}

View File

@ -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(())
}