with fuzzy-clone
This commit is contained in:
parent
66105004e0
commit
27f63cc1f9
@ -6,7 +6,7 @@ impl util::Cmd for Auth {
|
||||
}
|
||||
|
||||
fn exec(_: &clap::ArgMatches) -> eyre::Result<()> {
|
||||
util::shell::run(&["gh", "auth", "login"])?;
|
||||
util::shell::run(&["gh", "auth", "login"], None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
258
crates/github/src/fuzzy_clone.rs
Normal file
258
crates/github/src/fuzzy_clone.rs
Normal file
@ -0,0 +1,258 @@
|
||||
use std::io::Write;
|
||||
|
||||
use eyre::Context;
|
||||
|
||||
pub struct FuzzyClone;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct GitHubEntry {
|
||||
org: String,
|
||||
repo: String,
|
||||
}
|
||||
|
||||
impl GitHubEntry {
|
||||
fn from(raw: String) -> Option<Self> {
|
||||
let (org, repo) = raw.split_once("/")?;
|
||||
|
||||
Some(GitHubEntry {
|
||||
org: org.trim().to_string(),
|
||||
repo: repo.trim().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct Settings {
|
||||
orgs: Vec<String>,
|
||||
git_root: String,
|
||||
cache_dir: String,
|
||||
cache_file_path: String,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
fn new() -> eyre::Result<Self> {
|
||||
let mut cache_dir =
|
||||
dirs::cache_dir().ok_or(eyre::anyhow!("could not find a valid cache dir"))?;
|
||||
cache_dir.push("kah-toolkit/github/fc");
|
||||
|
||||
let mut file_path = std::path::Path::new(&cache_dir).to_path_buf();
|
||||
file_path.push("entries");
|
||||
|
||||
Ok(Self {
|
||||
orgs: std::env::var("GITHUB_FC_ORGS")
|
||||
.context("GITHUB_FC_ORGS is not set")?
|
||||
.split(",")
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
git_root: std::env::var("GITHUB_FC_ROOT").context("GITHUB_FC_ROOT is not set")?,
|
||||
cache_dir: cache_dir.to_string_lossy().to_string(),
|
||||
cache_file_path: file_path.to_string_lossy().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FuzzyClone {
|
||||
fn get_settings() -> eyre::Result<Settings> {
|
||||
Settings::new()
|
||||
}
|
||||
|
||||
fn get_cache() -> eyre::Result<Vec<GitHubEntry>> {
|
||||
let settings = Self::get_settings()?;
|
||||
let entries = std::fs::read_to_string(settings.cache_file_path)?;
|
||||
|
||||
Self::parse_entries(entries)
|
||||
}
|
||||
|
||||
fn parse_entries(raw: String) -> eyre::Result<Vec<GitHubEntry>> {
|
||||
let entries = raw
|
||||
.replace("\r\n", "\n")
|
||||
.split("\n")
|
||||
.map(|s| {
|
||||
s.split_once("/").map(|(org, repo)| {
|
||||
if let Some((repo, _)) = repo.split_once("\t") {
|
||||
GitHubEntry {
|
||||
org: org.to_string(),
|
||||
repo: repo.to_string(),
|
||||
}
|
||||
} else {
|
||||
GitHubEntry {
|
||||
org: org.to_string(),
|
||||
repo: repo.to_string(),
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.filter(|i| i.is_some())
|
||||
.map(|i| i.unwrap())
|
||||
.collect::<Vec<GitHubEntry>>();
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn cache_exists() -> eyre::Result<bool> {
|
||||
let settings = Self::get_settings()?;
|
||||
let cf_path = std::path::Path::new(&settings.cache_file_path);
|
||||
if !cf_path.exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
let metadata_time = cf_path.metadata()?.modified()?;
|
||||
let cur_time = std::time::SystemTime::now() - std::time::Duration::new(60 * 60 * 24, 0);
|
||||
|
||||
println!(
|
||||
"metadata_time: {:?}, cur_time: {:?}",
|
||||
metadata_time, cur_time
|
||||
);
|
||||
|
||||
if metadata_time < cur_time {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn set_cache(input: &Vec<GitHubEntry>) -> eyre::Result<()> {
|
||||
let settings = Self::get_settings()?;
|
||||
let cache_file_path = std::path::Path::new(&settings.cache_file_path);
|
||||
let mut fs = if cache_file_path.exists() {
|
||||
std::fs::File::create(cache_file_path)?
|
||||
} else {
|
||||
let cache_dir_path = std::path::Path::new(&settings.cache_dir);
|
||||
if !cache_dir_path.exists() {
|
||||
std::fs::create_dir_all(cache_dir_path)?;
|
||||
}
|
||||
std::fs::File::create(cache_file_path)?
|
||||
};
|
||||
|
||||
fs.write_all(
|
||||
input
|
||||
.iter()
|
||||
.map(|ge| format!("{}/{}", ge.org, ge.repo))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
.as_bytes(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_entries() -> eyre::Result<Vec<GitHubEntry>> {
|
||||
let mut entries = Vec::new();
|
||||
|
||||
for org in Self::get_settings()?.orgs {
|
||||
let private_entires = util::shell::run_with_input_and_output(
|
||||
&[
|
||||
"gh",
|
||||
"repo",
|
||||
"list",
|
||||
&org,
|
||||
"--visibility",
|
||||
"private",
|
||||
"--limit",
|
||||
"1000",
|
||||
],
|
||||
"".into(),
|
||||
)?;
|
||||
|
||||
let public_entires = util::shell::run_with_input_and_output(
|
||||
&[
|
||||
"gh",
|
||||
"repo",
|
||||
"list",
|
||||
&org,
|
||||
"--visibility",
|
||||
"public",
|
||||
"--limit",
|
||||
"1000",
|
||||
],
|
||||
"".into(),
|
||||
)?;
|
||||
|
||||
let private = std::str::from_utf8(private_entires.stdout.as_slice())?;
|
||||
let public = std::str::from_utf8(public_entires.stdout.as_slice())?;
|
||||
let raw_entries = format!("{private}{public}");
|
||||
|
||||
entries.push(Self::parse_entries(raw_entries)?);
|
||||
}
|
||||
|
||||
Ok(entries.into_iter().flat_map(|s| s).collect())
|
||||
}
|
||||
|
||||
fn clone(chosen: GitHubEntry) -> eyre::Result<std::path::PathBuf> {
|
||||
let mut git_path = std::path::Path::new(&Self::get_settings()?.git_root).to_path_buf();
|
||||
git_path.push(&chosen.org);
|
||||
if !git_path.exists() {
|
||||
std::fs::create_dir_all(&git_path)?;
|
||||
}
|
||||
let mut git_repo_path = git_path.clone();
|
||||
git_repo_path.push(&chosen.repo);
|
||||
if !git_repo_path.exists() {
|
||||
util::shell::run(
|
||||
&[
|
||||
"gh",
|
||||
"repo",
|
||||
"clone",
|
||||
format!("{}/{}", &chosen.org, &chosen.repo).as_str(),
|
||||
git_repo_path
|
||||
.to_str()
|
||||
.ok_or(eyre::anyhow!("could you not transform to path"))?,
|
||||
],
|
||||
Some(util::shell::RunOptions {
|
||||
path: git_path.clone(),
|
||||
}),
|
||||
)?;
|
||||
} else {
|
||||
util::shell::run(&["git", "pull"], None)?;
|
||||
}
|
||||
|
||||
Ok(git_repo_path)
|
||||
}
|
||||
|
||||
fn run() -> eyre::Result<()> {
|
||||
let entries = if !Self::cache_exists()? {
|
||||
let entries = Self::get_entries()?;
|
||||
Self::set_cache(&entries)?;
|
||||
entries
|
||||
} else {
|
||||
Self::get_cache()?
|
||||
};
|
||||
|
||||
let entries_str = entries
|
||||
.iter()
|
||||
.map(|ge| format!("{}/{}", ge.org, ge.repo))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
let chosen = util::shell::run_with_input_and_output(&["fzf"], entries_str)?;
|
||||
let chosen = std::str::from_utf8(&chosen.stdout)?;
|
||||
|
||||
Self::clone(GitHubEntry::from(chosen.to_string()).ok_or(eyre::anyhow!(
|
||||
"could not parse choice as github entry <org>/<repo>"
|
||||
))?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update() -> eyre::Result<()> {
|
||||
let entries = Self::get_entries()?;
|
||||
Self::set_cache(&entries)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl util::Cmd for FuzzyClone {
|
||||
fn cmd() -> eyre::Result<clap::Command> {
|
||||
Ok(clap::Command::new("fuzzy-clone")
|
||||
.alias("fc")
|
||||
.alias("c")
|
||||
.subcommand(clap::Command::new("update")))
|
||||
}
|
||||
|
||||
fn exec(args: &clap::ArgMatches) -> eyre::Result<()> {
|
||||
match args.subcommand() {
|
||||
Some(("update", _)) => Self::update()?,
|
||||
_ => Self::run()?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ impl Gh {
|
||||
let mut cmd_args = vec!["gh", external];
|
||||
cmd_args.append(&mut raw.iter().map(|s| &**s).collect());
|
||||
|
||||
util::shell::run(cmd_args.as_slice())?;
|
||||
util::shell::run(cmd_args.as_slice(), None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -29,7 +29,7 @@ impl util::Cmd for Gh {
|
||||
match args.subcommand() {
|
||||
Some((external, args)) => Self::run(external, args),
|
||||
_ => {
|
||||
util::shell::run(&["gh"])?;
|
||||
util::shell::run(&["gh"], None)?;
|
||||
|
||||
Err(eyre::anyhow!("missing argument"))
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
mod auth;
|
||||
mod fuzzy_clone;
|
||||
mod gh;
|
||||
|
||||
pub struct GitHub;
|
||||
@ -17,7 +18,7 @@ impl GitHub {
|
||||
let mut cmd_args = vec!["gh", external];
|
||||
cmd_args.append(&mut raw.iter().map(|s| &**s).collect());
|
||||
|
||||
util::shell::run(cmd_args.as_slice())?;
|
||||
util::shell::run(cmd_args.as_slice(), None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -26,13 +27,19 @@ impl GitHub {
|
||||
impl util::Cmd for GitHub {
|
||||
fn cmd() -> eyre::Result<clap::Command> {
|
||||
Ok(clap::Command::new("github")
|
||||
.subcommands(&[auth::Auth::cmd()?, gh::Gh::cmd()?])
|
||||
.subcommands(&[
|
||||
auth::Auth::cmd()?,
|
||||
gh::Gh::cmd()?,
|
||||
fuzzy_clone::FuzzyClone::cmd()?,
|
||||
])
|
||||
.allow_external_subcommands(true))
|
||||
}
|
||||
|
||||
fn exec(args: &clap::ArgMatches) -> eyre::Result<()> {
|
||||
match args.subcommand() {
|
||||
Some(("auth", subm)) => auth::Auth::exec(subm),
|
||||
Some(("fuzzy-clone", subm)) => fuzzy_clone::FuzzyClone::exec(subm),
|
||||
Some(("fc", subm)) => fuzzy_clone::FuzzyClone::exec(subm),
|
||||
Some(("gh", subm)) => gh::Gh::exec(subm),
|
||||
Some((external, args)) => Self::run(external, args),
|
||||
_ => Err(eyre::anyhow!("missing argument")),
|
||||
|
@ -6,7 +6,7 @@ impl util::Cmd for Auth {
|
||||
}
|
||||
|
||||
fn exec(_: &clap::ArgMatches) -> eyre::Result<()> {
|
||||
util::shell::run(&["src", "login"])?;
|
||||
util::shell::run(&["src", "login"], None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ pub struct Sourcegraph;
|
||||
|
||||
impl Sourcegraph {
|
||||
fn run() -> eyre::Result<()> {
|
||||
util::shell::run(&["src"])?;
|
||||
util::shell::run(&["src"], None)?;
|
||||
|
||||
Err(eyre::anyhow!("missing argument"))
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ impl util::Cmd for Search {
|
||||
let mut cmd_args = vec!["src", "search", external];
|
||||
cmd_args.append(&mut raw.iter().map(|s| &**s).collect());
|
||||
|
||||
util::shell::run(cmd_args.as_slice())?;
|
||||
util::shell::run(cmd_args.as_slice(), None)?;
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ impl util::Cmd for Update {
|
||||
.split(" ")
|
||||
.collect::<Vec<&str>>()
|
||||
.as_slice(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -1,21 +1,31 @@
|
||||
use std::io::Write;
|
||||
|
||||
pub fn run(args: &[&str]) -> eyre::Result<()> {
|
||||
let output = std::process::Command::new(
|
||||
pub struct RunOptions {
|
||||
pub path: std::path::PathBuf,
|
||||
}
|
||||
|
||||
pub fn run(args: &[&str], opts: Option<RunOptions>) -> eyre::Result<()> {
|
||||
let mut cmd = std::process::Command::new(
|
||||
args.first()
|
||||
.ok_or(eyre::anyhow!("could not find first arg"))?,
|
||||
)
|
||||
.args(
|
||||
args.to_vec()
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.collect::<Vec<&str>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.stdout(std::process::Stdio::inherit())
|
||||
.stderr(std::process::Stdio::inherit())
|
||||
.stdin(std::process::Stdio::inherit())
|
||||
.output();
|
||||
);
|
||||
|
||||
if let Some(opts) = opts {
|
||||
cmd.current_dir(opts.path);
|
||||
}
|
||||
|
||||
let output = cmd
|
||||
.args(
|
||||
args.to_vec()
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.collect::<Vec<&str>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.stdout(std::process::Stdio::inherit())
|
||||
.stderr(std::process::Stdio::inherit())
|
||||
.stdin(std::process::Stdio::inherit())
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(o) => {
|
||||
|
Reference in New Issue
Block a user