diff --git a/README.md b/README.md index 8083196..cdad048 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,21 @@ owner = "acme" # Applies company profiles to all repositories in `acme` org [[rules]] profile.name = "default" ``` + +### Finding path of the repository +```shell +ghr path # Root directory +ghr path # Owner root +ghr path # Repository directory +ghr path --host=github.com # Host root +ghr path --host=github.com # Owner root of the specified host +ghr path --host=github.com # Repository directory of the specified host +``` + +## 🛠 Customising +You can change the root of repositories managed by ghr by setting environment variable `GHR_ROOT` in your shell profile. + +```shell +ghr path # ~/.ghr +GHR_ROOT=/path/to/root ghr path # /path/to/root +``` diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 464a7e2..73b2538 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,4 +1,5 @@ mod clone; +mod path; mod profile; use anyhow::Result; @@ -8,6 +9,8 @@ use clap::{Parser, Subcommand}; pub enum Action { /// Clones a Git repository to local. Clone(clone::Cmd), + /// Prints the path to root, owner, or a repository. + Path(path::Cmd), /// Manages profiles to use in repositories. Profile(profile::Cmd), } @@ -23,6 +26,7 @@ impl Cli { use Action::*; match self.action { Clone(cmd) => cmd.run().await, + Path(cmd) => cmd.run(), Profile(cmd) => cmd.run(), } } diff --git a/src/cmd/path.rs b/src/cmd/path.rs new file mode 100644 index 0000000..f277910 --- /dev/null +++ b/src/cmd/path.rs @@ -0,0 +1,40 @@ +use crate::path::PartialPath; +use crate::root::Root; +use anyhow::{anyhow, Result}; +use clap::Parser; +use std::path::PathBuf; + +#[derive(Debug, Parser)] +pub struct Cmd { + /// Remote host of the repository. + /// Defaults to github.com. + #[clap(long)] + host: Option, + /// Owner name of the repository. + owner: Option, + /// Repository name. + repo: Option, +} + +impl Cmd { + pub fn run(self) -> Result<()> { + let root = Root::find()?; + let path = PartialPath { + root: &root, + host: self.host, + owner: self.owner, + repo: self.repo, + }; + + let path = PathBuf::from(path); + if !path.exists() || !path.is_dir() { + return Err(anyhow!( + "The path does not exist or is not a directory. Did you cloned the repository?" + )); + } + + println!("{}", path.to_string_lossy()); + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index e823598..18de7da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,10 @@ mod root; mod rule; mod url; -use clap::Parser; +use std::io::stderr; use std::process::exit; + +use clap::Parser; use tracing::error; use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::EnvFilter; @@ -25,6 +27,7 @@ async fn main() { .with_default_directive(LevelFilter::INFO.into()) .from_env_lossy(), ) + .with_writer(stderr) .init(); if let Err(e) = Cli::parse().run().await { diff --git a/src/path.rs b/src/path.rs index aa5d6de..0586777 100644 --- a/src/path.rs +++ b/src/path.rs @@ -31,3 +31,37 @@ impl<'a> From> for PathBuf { (&p).into() } } + +pub struct PartialPath<'a> { + pub root: &'a Root, + pub host: Option, + pub owner: Option, + pub repo: Option, +} + +impl<'a> From<&PartialPath<'a>> for PathBuf { + fn from(p: &PartialPath<'a>) -> Self { + let mut path = p.root.path().to_owned(); + + match p.host.as_deref() { + Some(h) => path = path.join(h), + _ => return path, + } + + match p.owner.as_deref() { + Some(o) => path = path.join(o), + _ => return path, + } + + match p.repo.as_deref() { + Some(r) => path.join(r), + _ => path, + } + } +} + +impl<'a> From> for PathBuf { + fn from(p: PartialPath<'a>) -> Self { + (&p).into() + } +} diff --git a/src/root.rs b/src/root.rs index 8d655a6..e9a521a 100644 --- a/src/root.rs +++ b/src/root.rs @@ -1,18 +1,29 @@ +use std::env::var; use std::path::PathBuf; +use std::str::FromStr; use anyhow::{anyhow, Result}; use dirs::home_dir; use tracing::info; +const ENV_VAR_KEY: &str = "GHR_ROOT"; +const DEFAULT_ROOT_NAME: &str = ".ghr"; + pub struct Root { path: PathBuf, } impl Root { pub fn find() -> Result { - let path = home_dir() - .ok_or_else(|| anyhow!("Could not find a home directory"))? - .join(".ghr"); + let path = match var(ENV_VAR_KEY).ok().and_then(|s| match s.is_empty() { + true => None, + _ => Some(s), + }) { + Some(p) => PathBuf::from_str(&p)?.canonicalize()?, + _ => home_dir() + .ok_or_else(|| anyhow!("Could not find a home directory"))? + .join(DEFAULT_ROOT_NAME), + }; info!( "Found a root directory: {}",