use std::{
collections::BTreeMap, env,
error::Error,
fs,
io::{self, BufRead, Write},
path::PathBuf,
process::{self, Command, Stdio},
};
pub(crate) fn db_path() -> PathBuf {
#[allow(deprecated)]
let home_dir = env::home_dir().expect("home directory should be defined");
home_dir.join(".config").join(env!("CARGO_PKG_NAME"))
}
pub(crate) fn db_file() -> PathBuf {
db_path().join("db")
}
const DB_HEADER: &str = "# GENERATED BY gprofile, EDIT WITH CAUTION!\n\n";
pub(crate) fn init() -> Result<(), Box<dyn Error>> {
let git = Command::new("git")
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.output()
.is_ok();
#[allow(clippy::print_literal)]
if !git {
eprintln!(
"{0}: {1}\n{2}",
env!("CARGO_PKG_NAME"),
"Unable to find `git` in PATH, make sure it's installed.",
"The tool compliments `git` it has no use on it's own."
);
process::exit(1);
}
let db_path = db_path();
if !db_path.is_dir() {
fs::create_dir_all(&db_path)?;
}
let db_file = db_file();
if !db_file.exists() {
let mut db = fs::File::create(db_file)?;
db.write_all(DB_HEADER.as_bytes())?;
db.flush()?;
}
Ok(())
}
pub(crate) fn create() -> Result<(), Box<dyn Error>> {
let profile = prompt("Profile name: ")?;
let mut db = read_from_db()?;
if db.contains_key(&profile) {
loop {
if let Ok(response) = prompt("Profile already exist, overwrite? [y|n] ") {
match response.to_lowercase().trim() {
"yes" | "y" => break,
"no" | "n" => return Ok(()),
_ => {}
}
}
}
}
let name = prompt("Please input your git username: ")?;
let email = prompt("Please input your git email: ")?;
db.insert(
profile,
BTreeMap::from([("email".to_string(), email), ("name".to_string(), name)]),
);
write_to_db(db)?;
Ok(())
}
pub(crate) fn list() -> Result<(), Box<dyn Error>> {
let db = read_from_db()?;
if db.is_empty() {
eprintln!("No user profiles has been saved yet.");
return Ok(());
}
println!("Available profiles: ");
for profile in db.keys() {
println!(" - {}", profile);
}
Ok(())
}
pub(crate) fn delete(profile: String) -> Result<(), Box<dyn Error>> {
let mut db = read_from_db()?;
if db.remove(&profile).is_none() {
eprintln!("Profile '{}' was not found in db.", &profile);
process::exit(1);
}
write_to_db(db)?;
Ok(())
}
pub(crate) fn edit(profile: String) -> Result<(), Box<dyn Error>> {
let mut db = read_from_db()?;
if !db.contains_key(&profile) {
eprintln!("Profile '{}' was not found in db.", &profile);
process::exit(1);
}
let name = prompt("Please input your git username: ")?;
let email = prompt("Please input your git email: ")?;
db.insert(
profile,
BTreeMap::from([("email".to_string(), email), ("name".to_string(), name)]),
);
write_to_db(db)?;
Ok(())
}
pub(crate) fn switch(profile: String) -> Result<(), Box<dyn Error>> {
let db = read_from_db()?;
if let Some(config) = db.get(&profile) {
Command::new("git")
.args(["config", "user.name", config.get("name").unwrap()])
.output()?;
Command::new("git")
.args(["config", "user.email", config.get("email").unwrap()])
.output()?;
} else {
eprintln!("Profile '{}' was not found in db.", &profile);
process::exit(1);
}
Ok(())
}
type Db = BTreeMap<String, BTreeMap<String, String>>;
pub(crate) fn write_to_db(db: Db) -> Result<(), Box<dyn Error>> {
let contents = db
.iter()
.fold(String::from(DB_HEADER), |mut acc, (profile, config)| {
for (key, value) in config.iter() {
acc.push_str(format!("{}.{} = {}\n", profile, key, value).as_str());
}
acc.push('\n');
acc
});
let mut db = fs::File::create(db_file())?;
db.write_all(contents.as_bytes())?;
db.flush()?;
Ok(())
}
pub(crate) fn read_from_db() -> Result<Db, Box<dyn Error>> {
let db = fs::File::open(db_file())?;
let buf = io::BufReader::new(db);
let parsed = buf
.lines()
.filter_map(|line| {
if let Ok(line) = line {
if line.is_empty() || line.starts_with('#') {
None
} else {
Some(line)
}
} else {
None
}
})
.fold(BTreeMap::new(), |mut acc: Db, line| {
let profile_and_key_value = &line.split('=').collect::<Vec<_>>();
if profile_and_key_value.len() != 2 {
eprintln!("Error in line: \n{}", &line);
process::exit(1);
}
let profile_and_key = profile_and_key_value[0].split('.').collect::<Vec<_>>();
if profile_and_key.len() != 2 {
eprintln!("Error in line: \n{:?}", profile_and_key);
process::exit(1);
}
let profile = profile_and_key[0];
let key = profile_and_key[1];
let value = profile_and_key_value[1];
if let Some(config) = acc.get_mut(&profile.to_string()) {
config.insert(key.trim().to_string(), value.trim().to_string());
} else {
let mut config = BTreeMap::new();
config.insert(key.trim().to_string(), value.trim().to_string());
acc.insert(profile.trim().to_string(), config);
}
acc
});
Ok(parsed)
}
pub(crate) fn prompt(msg: &str) -> Result<String, Box<dyn Error>> {
print!("{}", msg);
io::stdout().flush()?;
let mut answer = String::new();
loop {
io::stdin().read_line(&mut answer)?;
if !answer.trim().is_empty() {
break;
}
}
Ok(answer.trim().to_string())
}