Compare commits
5 Commits
d7e8b57c67
...
1b15bd678c
Author | SHA1 | Date |
---|---|---|
Thomas Forgione | 1b15bd678c | |
Thomas Forgione | 8cf6a2e556 | |
Thomas Forgione | 8480ae88f0 | |
Thomas Forgione | 949490b350 | |
Thomas Forgione | 56e39a33f9 |
|
@ -11,6 +11,7 @@ name = "gclone"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"colored 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"colored 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
colored = "1.7.0"
|
colored = "1.7.0"
|
||||||
walkdir = "2.2.7"
|
walkdir = "2.2.7"
|
||||||
|
lazy_static = "1.2.0"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "gclone"
|
name = "gclone"
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
//! This module contains the cache manager.
|
||||||
|
|
||||||
|
use std::num::Wrapping;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{stdin, stdout, Write, BufRead, BufReader};
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
use colored::*;
|
||||||
|
use crate::{Error, Result};
|
||||||
|
use crate::git::GCLONE_PATH;
|
||||||
|
|
||||||
|
/// Flushes the stdout.
|
||||||
|
fn flush_stdout() {
|
||||||
|
stdout().flush().ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The cache of gclone.
|
||||||
|
///
|
||||||
|
/// When running the command `cdg`, if the computer just booted, finding all the directories can be
|
||||||
|
/// quite slow. The cache contains the list of entries in the git directory to avoid re-finding
|
||||||
|
/// them every time.
|
||||||
|
pub struct Cache(Vec<String>);
|
||||||
|
|
||||||
|
impl Cache {
|
||||||
|
|
||||||
|
/// Generates the cache by traversing the files in the git directory.
|
||||||
|
pub fn generate() -> Cache {
|
||||||
|
Cache(WalkDir::new(&*GCLONE_PATH)
|
||||||
|
.max_depth(3)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|x| x.ok())
|
||||||
|
.map(|x| x.path().display().to_string())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the cache file.
|
||||||
|
pub fn read() -> Result<Cache> {
|
||||||
|
|
||||||
|
let mut path = PathBuf::from(&*GCLONE_PATH);
|
||||||
|
path.push(".cdgcache");
|
||||||
|
|
||||||
|
let file = File::open(&path)?;
|
||||||
|
let file = BufReader::new(file);
|
||||||
|
|
||||||
|
let mut values = vec![];
|
||||||
|
|
||||||
|
for line in file.lines() {
|
||||||
|
if let Ok(l) = line {
|
||||||
|
values.push(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Cache(values))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the cache file, and if it failed, generate a new cache.
|
||||||
|
pub fn read_or_generate() -> Cache {
|
||||||
|
match Cache::read() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => Cache::generate(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the current content of the cache to the cache file.
|
||||||
|
pub fn write(&self) -> Result<()> {
|
||||||
|
let mut path = PathBuf::from(&*GCLONE_PATH);
|
||||||
|
path.push(".cdgcache");
|
||||||
|
let mut file = File::create(path)?;
|
||||||
|
|
||||||
|
for line in &self.0 {
|
||||||
|
writeln!(file, "{}", line)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search a directory in the cache, and returns all matching directories.
|
||||||
|
pub fn find(&self, dirname: &str) -> Vec<String> {
|
||||||
|
let dirname = &format!("/{}", dirname);
|
||||||
|
let mut matches = vec![];
|
||||||
|
for line in &self.0 {
|
||||||
|
if line.ends_with(dirname) {
|
||||||
|
matches.push(line.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
140
src/gclone.rs
140
src/gclone.rs
|
@ -1,155 +1,49 @@
|
||||||
use std::env;
|
use std::process::exit;
|
||||||
use std::process::{Command, exit};
|
|
||||||
use std::fs::{create_dir_all, remove_dir_all};
|
|
||||||
use std::error::Error;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use colored::*;
|
use colored::*;
|
||||||
|
|
||||||
use gclone::{git_dir, Cache, Result};
|
use gclone::{first_arg, Cache, Result};
|
||||||
|
use gclone::git::{GCLONE_PATH, parse_url, clone};
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn help() {
|
fn help() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main_result() -> Result<()> {
|
||||||
|
|
||||||
let git_dir = git_dir()?;
|
|
||||||
|
|
||||||
// Parse args
|
// Parse args
|
||||||
let url = match env::args().nth(1) {
|
let url = first_arg()?;
|
||||||
Some(arg) => arg,
|
|
||||||
None => {
|
|
||||||
eprintln!("{} {}",
|
|
||||||
"error:".bold().red(),
|
|
||||||
"gclone expects an argument".bold());
|
|
||||||
|
|
||||||
exit(1);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if url == "-h" || url == "--help" {
|
if url == "-h" || url == "--help" {
|
||||||
return Ok(help());
|
return Ok(help());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (server, owner, repo) = match parse_url(&url) {
|
let (server, owner, repo) = parse_url(&url)?;
|
||||||
Some(parsed) => parsed,
|
|
||||||
None => {
|
|
||||||
eprintln!("{} {}",
|
|
||||||
"error:".bold().red(),
|
|
||||||
"couldn't guess server, owner and repo names from url".bold());
|
|
||||||
|
|
||||||
exit(1);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build path
|
// Build path
|
||||||
let mut path = PathBuf::from(git_dir);
|
let mut path = PathBuf::from(&*GCLONE_PATH);
|
||||||
path.push(&server);
|
path.push(&server);
|
||||||
path.push(&owner);
|
path.push(&owner);
|
||||||
path.push(&repo);
|
path.push(&repo);
|
||||||
|
|
||||||
if path.exists() {
|
// Clone repository
|
||||||
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);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
eprintln!("{} {} {}{}", "info:".bold(), "cloning", url, "...");
|
eprintln!("{} {} {}{}", "info:".bold(), "cloning", url, "...");
|
||||||
|
clone(&url, &path.display().to_string())?;
|
||||||
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);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
eprintln!("{} {}", "info:".bold(), "done!");
|
eprintln!("{} {}", "info:".bold(), "done!");
|
||||||
|
|
||||||
// Append to cache
|
// Append to cache
|
||||||
let mut cache = Cache::read()?;
|
let mut cache = Cache::read()?;
|
||||||
cache.append(path.display().to_string());
|
cache.append(path.display().to_string());
|
||||||
|
cache.write()?;
|
||||||
match cache.write() {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{} {} {}",
|
|
||||||
"warning:".bold().yellow(),
|
|
||||||
"couldn't write cache:".bold(),
|
|
||||||
e.description());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
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(()),
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
176
src/lib.rs
176
src/lib.rs
|
@ -1,11 +1,26 @@
|
||||||
use std::{env, fmt, result};
|
//! This crate contains the gclone and pgd commands.
|
||||||
use std::error::Error;
|
//!
|
||||||
use std::path::PathBuf;
|
//! The `gclone` command automatically clones a git repository and puts it in the right place.
|
||||||
use std::fs::File;
|
//! The `pgd` command manages a cache for efficiently being able to print a directory corresponding
|
||||||
use std::io::{self, Write, BufRead, BufReader};
|
//! to a repo or an owner. You can then add the following to automatically `cd` to a directory:
|
||||||
|
//! ``` zsh
|
||||||
|
//! cdg() {
|
||||||
|
//! local newdir
|
||||||
|
//! newdir=$(pgd $1) && cd $newdir
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
use std::{env, fmt, num, error, result};
|
||||||
|
use std::io;
|
||||||
|
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
mod cache;
|
||||||
|
pub use cache::Cache;
|
||||||
|
|
||||||
|
pub mod git;
|
||||||
|
|
||||||
macro_rules! impl_from_error {
|
macro_rules! impl_from_error {
|
||||||
($type: ty, $variant: path, $from: ty) => {
|
($type: ty, $variant: path, $from: ty) => {
|
||||||
|
@ -18,101 +33,98 @@ macro_rules! impl_from_error {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MError {
|
/// The error type of gclone.
|
||||||
|
pub enum Error {
|
||||||
|
/// Couldn't read the GCLONE_PATH env.
|
||||||
NoGclonePath(env::VarError),
|
NoGclonePath(env::VarError),
|
||||||
|
|
||||||
|
/// An error occured while manipulating files.
|
||||||
IoError(io::Error),
|
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!(MError, MError::NoGclonePath, env::VarError);
|
impl_from_error!(Error, Error::NoGclonePath, env::VarError);
|
||||||
impl_from_error!(MError, MError::IoError, io::Error);
|
impl_from_error!(Error, Error::IoError, io::Error);
|
||||||
|
impl_from_error!(Error, Error::ParseIntError, num::ParseIntError);
|
||||||
|
|
||||||
impl fmt::Display for MError {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
MError::NoGclonePath(e) =>
|
Error::NoGclonePath(e) =>
|
||||||
write!(fmt, "{} {} {}",
|
write!(fmt, "{} {} {}",
|
||||||
"error:".bold().red(),
|
"error:".bold().red(),
|
||||||
"couldn't read environment variable GCLONE_PATH".bold(),
|
"couldn't read environment variable GCLONE_PATH".bold(),
|
||||||
e),
|
e),
|
||||||
MError::IoError(e) =>
|
Error::IoError(e) =>
|
||||||
write!(fmt, "{} {} {}",
|
write!(fmt, "{} {} {}",
|
||||||
"error:".bold().red(),
|
"error:".bold().red(),
|
||||||
"couldn't read or write cache:".bold(),
|
"couldn't read or write cache:".bold(),
|
||||||
e),
|
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()),
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for MError {}
|
impl error::Error for Error {}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, MError>;
|
/// The result type of gclone.
|
||||||
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
pub fn git_dir() -> Result<String> {
|
/// Gets the single argument of the program.
|
||||||
Ok(env::var("GCLONE_PATH")?)
|
pub fn first_arg() -> Result<String> {
|
||||||
|
env::args().nth(1).ok_or(Error::WrongArgument)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Cache(Vec<String>);
|
|
||||||
|
|
||||||
impl Cache {
|
|
||||||
|
|
||||||
pub fn new() -> Result<Cache> {
|
|
||||||
Ok(Cache(WalkDir::new(git_dir()?)
|
|
||||||
.max_depth(3)
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|x| x.ok())
|
|
||||||
.map(|x| x.path().display().to_string())
|
|
||||||
.collect()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read() -> Result<Cache> {
|
|
||||||
|
|
||||||
let mut path = PathBuf::from(git_dir()?);
|
|
||||||
path.push(".cdgcache");
|
|
||||||
|
|
||||||
let file = match File::open(&path) {
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(_) => return Ok(Cache(vec![])),
|
|
||||||
};
|
|
||||||
|
|
||||||
let file = BufReader::new(file);
|
|
||||||
|
|
||||||
let mut values = vec![];
|
|
||||||
|
|
||||||
for line in file.lines() {
|
|
||||||
if let Ok(l) = line {
|
|
||||||
values.push(l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Cache(values))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(&self) -> Result<()> {
|
|
||||||
let mut path = PathBuf::from(git_dir()?);
|
|
||||||
path.push(".cdgcache");
|
|
||||||
let mut file = File::create(path)?;
|
|
||||||
|
|
||||||
for line in &self.0 {
|
|
||||||
writeln!(file, "{}", line)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find(&self, dirname: &str) -> Vec<String> {
|
|
||||||
let dirname = &format!("/{}", dirname);
|
|
||||||
let mut matches = vec![];
|
|
||||||
for line in &self.0 {
|
|
||||||
if line.ends_with(dirname) {
|
|
||||||
matches.push(line.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
matches
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn append(&mut self, value: String) {
|
|
||||||
self.0.push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
131
src/pgd.rs
131
src/pgd.rs
|
@ -1,127 +1,44 @@
|
||||||
use std::env;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::io::{stdout, stdin, Write};
|
|
||||||
use std::num::Wrapping;
|
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use gclone::{Result, Cache};
|
use gclone::{first_arg, Cache, Error, Result};
|
||||||
|
|
||||||
fn flush_stdout() {
|
fn main() {
|
||||||
stdout().flush().ok();
|
if let Err(e) = main_result() {
|
||||||
|
eprintln!("{}", e);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main_result() -> Result<()> {
|
||||||
let request = match env::args().nth(1) {
|
let request = first_arg()?;
|
||||||
Some(arg) => arg,
|
|
||||||
None => {
|
|
||||||
eprintln!("{} {}",
|
|
||||||
"error:".bold().red(),
|
|
||||||
"cdg expects an argument".bold());
|
|
||||||
|
|
||||||
exit(1);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
main_with_cache(&request, true)
|
main_with_cache(&request, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_with_cache(request: &str, regen: bool) -> Result<()> {
|
fn main_with_cache(request: &str, regen: bool) -> Result<()> {
|
||||||
|
|
||||||
let cache = Cache::read()?;
|
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() {
|
Err(Error::NoSuchDirectory) if regen && ! just_generated => {
|
||||||
0 => {
|
eprintln!("{} {}",
|
||||||
|
"warning:".bold().yellow(),
|
||||||
if regen {
|
"directory not found, regenerating cache...".bold());
|
||||||
eprintln!("{} {}",
|
let cache = Cache::generate();
|
||||||
"warning:".bold().yellow(),
|
cache.write()?;
|
||||||
"directory not found, regenerating cache...".bold());
|
return main_with_cache(request, false);
|
||||||
|
|
||||||
let cache = Cache::new()?;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
1 => {
|
Err(e) => return Err(e),
|
||||||
println!("{}", matches[0]);
|
Ok(x) => x,
|
||||||
},
|
};
|
||||||
|
|
||||||
len => {
|
println!("{}", only_match);
|
||||||
|
|
||||||
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]);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue