pub mod api;
pub mod cli;
pub mod config;
pub mod db;
pub mod errors;
pub mod git;
pub mod utils;
use futures::stream::{self, StreamExt};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::{fs, process::exit, time::Duration};
use tokio::task;
use bytes::Bytes;
use clap::Parser;
use cli::{Cli, Commands, RepoCommands};
use crate::{db::Repo, errors::TeacupError};
const API_URL: &str = "https://teapot-production.up.railway.app";
pub async fn run_cli() -> Result<(), TeacupError> {
let config = config::get_config()?;
db::setup(&config)?;
let cli = Cli::parse();
match cli.command {
Commands::Repo { command } => match command {
RepoCommands::Add { location } => {
let fullpath = utils::to_absolute_path(&location)?;
println!("Repo Add: {}", &fullpath);
if git::is_git_repo(&fullpath) {
db::add(&config, &Repo { location: fullpath })?;
} else {
eprintln!("Not a valid local git repository: {}", fullpath);
}
}
RepoCommands::Rm { location } => {
let fullpath = utils::to_absolute_path(&location)?;
println!("Repo Rm: {}", &fullpath);
db::rm(&config, &Repo { location: fullpath })?;
}
RepoCommands::List => {
println!("Repo List");
let repos = db::list(&config)?;
println!("{:?}", repos);
}
RepoCommands::Search { base_path, add } => {
println!("Repo Search: {}", base_path);
let candidates = utils::find_git_repos(&base_path);
for candidate in candidates {
println!("{}", candidate);
if add {
db::add(
&config,
&Repo {
location: candidate,
},
)?;
}
}
}
},
Commands::Sync { author, alias } => {
println!("Sync'ing to {}", API_URL);
let multiprogress = MultiProgress::new();
let repos = db::list(&config)?;
let repos_with_progress = repos.into_iter().map(move |repo| {
let pb = multiprogress.add(ProgressBar::new_spinner());
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} [{elapsed_precise}] {msg}")
.unwrap(),
);
pb.enable_steady_tick(Duration::from_millis(400));
pb.set_message(format!("Waiting: {}", repo.location));
(repo, pb)
});
let commit_stream = stream::iter(repos_with_progress)
.map(|(repo, pb)| {
task::spawn_blocking(move || {
pb.set_message(format!("Extracting: {}", repo.location));
match git::extract_logs(repo.location.clone()) {
Err(e) => {
pb.finish_and_clear();
vec![]
}
Ok(logs) => {
pb.finish_and_clear();
logs
}
}
})
})
.buffer_unordered(num_cpus::get())
.map(|result| result.unwrap())
.flat_map(|commits| stream::iter(commits))
.map(|commit| {
let mut json = serde_json::to_vec(&commit)?;
json.push(b'\n');
Ok::<_, serde_json::Error>(Bytes::from(json))
});
let aliases = alias
.iter()
.map(|email| format!("alias={}", email))
.collect::<Vec<String>>()
.join("&");
let url = format!("{}/upload?author={}&{}", API_URL, author, aliases);
println!("Uploading to {}", url);
let client = reqwest::Client::new();
let response = client
.post(url)
.body(reqwest::Body::wrap_stream(commit_stream))
.send()
.await
.map_err(|e| {
dbg!(&e);
TeacupError::ApiError(e.to_string())
})?;
let status = response.status();
let body = response
.text()
.await
.unwrap_or_else(|_| "Could not read body".to_string());
eprintln!("Upload complete - Status: {}, Body: {}", status, body);
}
}
Ok(())
}