chore: refactor into lib
This commit is contained in:
parent
430d988f31
commit
9628bf9b71
4 changed files with 148 additions and 126 deletions
5
src/lib.rs
Normal file
5
src/lib.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
mod root;
|
||||||
|
mod shell;
|
||||||
|
|
||||||
|
pub use root::SrcRoot;
|
||||||
|
pub use shell::setup_shell;
|
||||||
163
src/main.rs
163
src/main.rs
|
|
@ -1,153 +1,64 @@
|
||||||
use std::{
|
use std::{
|
||||||
env::{current_dir, home_dir, var},
|
env::{current_dir, home_dir, var},
|
||||||
ffi::{OsStr, OsString},
|
path::PathBuf,
|
||||||
fs::create_dir_all,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::Command,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::Parser;
|
||||||
use url::Url;
|
use repo_rs::{SrcRoot, setup_shell};
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
const MAX_DEPTH: usize = 5;
|
mod cli {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn setup_shell(code_root: &Path) {
|
use clap::{Parser, Subcommand};
|
||||||
println!(
|
|
||||||
r#"
|
|
||||||
h() {{
|
|
||||||
_h_dir=$(command repo resolve --root "{}" "$@")
|
|
||||||
_h_ret=$?
|
|
||||||
[ "$_h_dir" != "$PWD" ] && pushd "$_h_dir"
|
|
||||||
return $_h_ret
|
|
||||||
}}
|
|
||||||
"#,
|
|
||||||
code_root.display()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SrcRoot<T: AsRef<Path> + AsRef<OsStr>> {
|
#[derive(Parser)]
|
||||||
root_path: T,
|
#[command(version, about, long_about = None)]
|
||||||
}
|
#[command(propagate_version = true)]
|
||||||
|
pub(crate) struct Cli {
|
||||||
impl<T: AsRef<Path> + AsRef<OsStr>> SrcRoot<T> {
|
#[command(subcommand)]
|
||||||
fn clone_repo(url: &Url, destination: &Path) -> Result<()> {
|
pub(crate) command: Commands,
|
||||||
let url = OsString::from(url.as_ref());
|
|
||||||
Command::new("git")
|
|
||||||
.args([
|
|
||||||
OsString::from("clone").as_os_str(),
|
|
||||||
url.as_os_str(),
|
|
||||||
destination.as_os_str(),
|
|
||||||
])
|
|
||||||
.status()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_repo_path(&self, url: &Url) -> PathBuf {
|
#[derive(Subcommand)]
|
||||||
let mut dir = PathBuf::new();
|
pub(crate) enum Commands {
|
||||||
dir.push::<&Path>(self.root_path.as_ref());
|
Resolve {
|
||||||
dir.push(url.host_str().unwrap_or("misc"));
|
#[arg(long)]
|
||||||
url.path_segments()
|
root: Option<PathBuf>,
|
||||||
.into_iter()
|
repo: String,
|
||||||
.flatten()
|
},
|
||||||
.filter(|s| !s.is_empty())
|
Setup {
|
||||||
.for_each(|s| dir.push(s.trim_end_matches(".git")));
|
#[arg(long)]
|
||||||
dir
|
root: Option<PathBuf>,
|
||||||
}
|
},
|
||||||
|
|
||||||
fn ensure_repo_checkout(&self, url: &Url) -> Result<PathBuf> {
|
|
||||||
let repo_path = self.as_repo_path(url);
|
|
||||||
if !repo_path.is_dir() {
|
|
||||||
create_dir_all(&repo_path)?;
|
|
||||||
}
|
|
||||||
Self::clone_repo(url, &repo_path)?;
|
|
||||||
Ok(repo_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_repo<S: AsRef<OsStr>>(&self, slug: S) -> Result<Option<PathBuf>> {
|
|
||||||
let walker = WalkDir::new(&self.root_path)
|
|
||||||
.follow_links(false)
|
|
||||||
.max_depth(MAX_DEPTH)
|
|
||||||
.follow_root_links(false)
|
|
||||||
.max_open(1)
|
|
||||||
.sort_by_file_name()
|
|
||||||
.contents_first(false)
|
|
||||||
.same_file_system(true);
|
|
||||||
for path in walker {
|
|
||||||
let p = path?;
|
|
||||||
let git = {
|
|
||||||
let mut g = p.clone().into_path();
|
|
||||||
g.push(".git");
|
|
||||||
g
|
|
||||||
};
|
|
||||||
if let Some(file_name) = p.path().file_name() {
|
|
||||||
if file_name == slug.as_ref() && git.is_dir() {
|
|
||||||
return Ok(Some(p.into_path()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(path: T) -> Self {
|
|
||||||
Self { root_path: path }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn src_root() -> Result<PathBuf> {
|
fn resolve_root(root: Option<PathBuf>) -> Result<PathBuf> {
|
||||||
if let Ok(src_root) = var("REPO_CODE_ROOT").map(PathBuf::from) {
|
let path_buf = if let Some(root) = root {
|
||||||
Ok(src_root)
|
root
|
||||||
|
} else if let Ok(src_root) = var("REPO_CODE_ROOT").map(PathBuf::from) {
|
||||||
|
src_root
|
||||||
} else {
|
} else {
|
||||||
let mut src_root = home_dir().unwrap_or(current_dir()?);
|
let mut src_root = home_dir().unwrap_or(current_dir()?);
|
||||||
src_root.push("code");
|
src_root.push("code");
|
||||||
Ok(src_root)
|
src_root
|
||||||
}
|
};
|
||||||
}
|
Ok(path_buf)
|
||||||
|
|
||||||
fn resolve_repo(arg: String, src_root: PathBuf) -> Result<(), anyhow::Error> {
|
|
||||||
let src_root = SrcRoot::new(src_root);
|
|
||||||
let path = if let Ok(url) = Url::parse(&arg) {
|
|
||||||
src_root.ensure_repo_checkout(&url)
|
|
||||||
} else {
|
|
||||||
let slug: OsString = arg.into();
|
|
||||||
src_root.search_repo(slug)?.context("Repo not found")
|
|
||||||
}?;
|
|
||||||
println!("{}", path.display());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(version, about, long_about = None)]
|
|
||||||
#[command(propagate_version = true)]
|
|
||||||
struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum Commands {
|
|
||||||
Resolve {
|
|
||||||
#[arg(long)]
|
|
||||||
root: Option<PathBuf>,
|
|
||||||
repo: String,
|
|
||||||
},
|
|
||||||
Setup {
|
|
||||||
#[arg(long)]
|
|
||||||
root: Option<PathBuf>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
use crate::cli::{Cli, Commands};
|
||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Resolve { root, repo } => {
|
Commands::Resolve { root, repo } => {
|
||||||
let root = root.context("").or_else(|_e| src_root())?;
|
let root = SrcRoot::new(resolve_root(root)?);
|
||||||
resolve_repo(repo, root)?;
|
println!("{}", root.resolve_repo(repo)?.as_ref().display());
|
||||||
}
|
}
|
||||||
Commands::Setup { root } => {
|
Commands::Setup { root } => {
|
||||||
let root = root.context("").or_else(|_e| src_root())?;
|
let root = resolve_root(root)?;
|
||||||
setup_shell(&root);
|
setup_shell(&root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
91
src/root.rs
Normal file
91
src/root.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::{
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
fs::create_dir_all,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::Command,
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
const MAX_DEPTH: usize = 5;
|
||||||
|
|
||||||
|
pub struct SrcRoot {
|
||||||
|
root_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SrcRoot {
|
||||||
|
fn clone_repo(url: &Url, destination: &Path) -> Result<()> {
|
||||||
|
let url = OsString::from(url.as_ref());
|
||||||
|
Command::new("git")
|
||||||
|
.args([
|
||||||
|
OsString::from("clone").as_os_str(),
|
||||||
|
url.as_os_str(),
|
||||||
|
destination.as_os_str(),
|
||||||
|
])
|
||||||
|
.status()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_repo_path(&self, url: &Url) -> PathBuf {
|
||||||
|
let mut dir = PathBuf::new();
|
||||||
|
dir.push::<&Path>(self.root_path.as_ref());
|
||||||
|
dir.push(url.host_str().unwrap_or("misc"));
|
||||||
|
url.path_segments()
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.for_each(|s| dir.push(s.trim_end_matches(".git")));
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_repo_checkout(&self, url: &Url) -> Result<PathBuf> {
|
||||||
|
let repo_path = self.as_repo_path(url);
|
||||||
|
if !repo_path.is_dir() {
|
||||||
|
create_dir_all(&repo_path)?;
|
||||||
|
}
|
||||||
|
Self::clone_repo(url, &repo_path)?;
|
||||||
|
Ok(repo_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_repo<S: AsRef<OsStr>>(&self, slug: S) -> Result<Option<PathBuf>> {
|
||||||
|
let walker = WalkDir::new(&self.root_path)
|
||||||
|
.follow_links(false)
|
||||||
|
.max_depth(MAX_DEPTH)
|
||||||
|
.follow_root_links(false)
|
||||||
|
.max_open(1)
|
||||||
|
.sort_by_file_name()
|
||||||
|
.contents_first(false)
|
||||||
|
.same_file_system(true);
|
||||||
|
for path in walker {
|
||||||
|
let p = path?;
|
||||||
|
let git = {
|
||||||
|
let mut g = p.clone().into_path();
|
||||||
|
g.push(".git");
|
||||||
|
g
|
||||||
|
};
|
||||||
|
if let Some(file_name) = p.path().file_name() {
|
||||||
|
if file_name == slug.as_ref() && git.is_dir() {
|
||||||
|
return Ok(Some(p.into_path()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Errors
|
||||||
|
/// File system errors
|
||||||
|
pub fn resolve_repo(&self, arg: String) -> Result<impl AsRef<Path>> {
|
||||||
|
if let Ok(url) = Url::parse(&arg) {
|
||||||
|
self.ensure_repo_checkout(&url)
|
||||||
|
} else {
|
||||||
|
let slug: OsString = arg.into();
|
||||||
|
self.search_repo(slug)?.context("Repo not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(path: PathBuf) -> Self {
|
||||||
|
Self { root_path: path }
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/shell.rs
Normal file
15
src/shell.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub fn setup_shell(code_root: &Path) {
|
||||||
|
println!(
|
||||||
|
r#"
|
||||||
|
h() {{
|
||||||
|
_h_dir=$(command repo resolve --root "{}" "$@")
|
||||||
|
_h_ret=$?
|
||||||
|
[ "$_h_dir" != "$PWD" ] && pushd "$_h_dir"
|
||||||
|
return $_h_ret
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
code_root.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue