diff --git a/src/config/index/cli.rs b/src/config/index/cli.rs deleted file mode 100644 index 5c5d3b06..00000000 --- a/src/config/index/cli.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; - -use crate::index::cli::CommandLineIndex; - -/// The configuration struct for the 'command-line' index management strategy. -/// -/// ```toml -/// [index] -/// type = "command-line" # required -/// path = "crate-index" # required -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct CommandLineIndexConfig { - /// The path to the local index repository. - pub path: PathBuf, -} - -impl From for CommandLineIndex { - fn from(config: CommandLineIndexConfig) -> CommandLineIndex { - CommandLineIndex::new(config.path) - } -} diff --git a/src/config/index/git2.rs b/src/config/index/git2.rs deleted file mode 100644 index ae2c2449..00000000 --- a/src/config/index/git2.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; - -use crate::index::git2::Git2Index; - -/// The configuration struct for the 'git2' index management strategy. -/// -/// ```toml -/// [index] -/// type = "git2" # required -/// path = "crate-index" # required -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Git2IndexConfig { - /// The path to the local index repository. - pub path: PathBuf, -} - -impl From for Git2Index { - fn from(config: Git2IndexConfig) -> Git2Index { - Git2Index::new(config.path).expect("could not initialize the 'git2' index") - } -} diff --git a/src/config/index/mod.rs b/src/config/index/mod.rs deleted file mode 100644 index b864dfc6..00000000 --- a/src/config/index/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// The 'command-line' configuration. -pub mod cli; - -/// The 'git2' configuration. -#[cfg(feature = "git2")] -pub mod git2; - -use crate::config::index::cli::CommandLineIndexConfig; -use crate::index::Index; - -#[cfg(feature = "git2")] -use crate::config::index::git2::Git2IndexConfig; - -/// The configuration enum for index management strategies. -/// -/// ```toml -/// [index] -/// type = "<...>" # required, replace "<...>" by the selected strategy. -/// ``` -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "kebab-case")] -pub enum IndexConfig { - /// The 'command-line' index management strategy (uses "git" shell command). - CommandLine(CommandLineIndexConfig), - /// The 'git2' index management strategy (uses [**`libgit2`**][libgit2]). - /// [libgit2]: https://libgit2.org - #[cfg(feature = "git2")] - Git2(Git2IndexConfig), -} - -impl From for Index { - fn from(config: IndexConfig) -> Index { - match config { - IndexConfig::CommandLine(config) => Index::CommandLine(config.into()), - #[cfg(feature = "git2")] - IndexConfig::Git2(config) => Index::Git2(config.into()), - } - } -} diff --git a/src/config/mod.rs b/src/config/mod.rs index 251f1fcb..89ddb350 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,11 +1,10 @@ +use std::convert::TryInto; use std::net; use serde::{Deserialize, Serialize}; /// Database configuration (`[database]` section). pub mod database; -/// Index management strategy configuration (`[index]` section). -pub mod index; /// Crate storage configuration (`[storage]` section). pub mod storage; /// Syntax-highlighting configurations (`[syntect.syntaxes]` and `[syntect.themes]` sections). @@ -20,9 +19,9 @@ use crate::storage::Storage; use crate::Repo; use crate::config::database::DatabaseConfig; -use crate::config::index::IndexConfig; use crate::config::storage::StorageConfig; use crate::config::syntect::{SyntectConfig, SyntectState}; +use crate::index::Config as IndexConfig; #[cfg(feature = "frontend")] pub use crate::config::frontend::*; @@ -72,7 +71,7 @@ pub struct State { impl From for State { fn from(config: Config) -> State { State { - index: config.index.into(), + index: config.index.try_into().unwrap(), storage: config.storage.into(), repo: Repo::new(&config.database), syntect: config.syntect.into(), diff --git a/src/index/cli.rs b/src/index/cli.rs deleted file mode 100644 index f8149d0c..00000000 --- a/src/index/cli.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::path::PathBuf; -use std::process::{Command, Stdio}; - -use semver::{Version, VersionReq}; - -use crate::error::Error; -use crate::index::tree::Tree; -use crate::index::{CrateVersion, Indexer}; - -/// The 'command-line' crate index management strategy type. -/// -/// It manages the crate index through the invocation of "git" shell commands. -#[derive(Debug, Clone, PartialEq)] -pub struct CommandLineIndex { - repo: Repository, - tree: Tree, -} - -impl CommandLineIndex { - /// Create a CommandLineIndex instance with the given path. - pub fn new>(path: P) -> CommandLineIndex { - let path = path.into(); - let repo = Repository { path: path.clone() }; - let tree = Tree::new(path); - CommandLineIndex { repo, tree } - } -} - -impl Indexer for CommandLineIndex { - fn url(&self) -> Result { - self.repo.url() - } - - fn refresh(&self) -> Result<(), Error> { - self.repo.refresh() - } - - fn commit_and_push(&self, msg: &str) -> Result<(), Error> { - self.repo.commit_and_push(msg) - } - - fn match_record(&self, name: &str, req: VersionReq) -> Result { - self.tree.match_record(name, req) - } - - fn all_records(&self, name: &str) -> Result, Error> { - self.tree.all_records(name) - } - - fn latest_record(&self, name: &str) -> Result { - self.tree.latest_record(name) - } - - fn add_record(&self, record: CrateVersion) -> Result<(), Error> { - self.tree.add_record(record) - } - - fn alter_record(&self, name: &str, version: Version, func: F) -> Result<(), Error> - where - F: FnOnce(&mut CrateVersion), - { - self.tree.alter_record(name, version, func) - } -} - -#[derive(Debug, Clone, PartialEq)] -struct Repository { - path: PathBuf, -} - -impl Repository { - fn url(&self) -> Result { - let output = Command::new("git") - .arg("remote") - .arg("get-url") - .arg("origin") - .stdout(Stdio::piped()) - .current_dir(self.path.canonicalize()?) - .output()?; - - Ok(String::from_utf8_lossy(output.stdout.as_slice()).into()) - } - - fn refresh(&self) -> Result<(), Error> { - Command::new("git") - .arg("pull") - .arg("--ff-only") - .current_dir(self.path.canonicalize()?) - .spawn()? - .wait()?; - - Ok(()) - } - - fn commit_and_push(&self, msg: &str) -> Result<(), Error> { - Command::new("git") - .arg("add") - .arg("--all") - .current_dir(&self.path) - .spawn()? - .wait()?; - Command::new("git") - .arg("commit") - .arg("-m") - .arg(msg) - .current_dir(&self.path) - .spawn()? - .wait()?; - Command::new("git") - .arg("push") - .arg("origin") - .arg("master") - .current_dir(&self.path) - .spawn()? - .wait()?; - - Ok(()) - } -} diff --git a/src/index/mod.rs b/src/index/mod.rs index 82c27f6f..72c57cd3 100644 --- a/src/index/mod.rs +++ b/src/index/mod.rs @@ -1,33 +1,62 @@ use semver::{Version, VersionReq}; +use std::convert::TryFrom; +use std::path::PathBuf; -/// Index management through `git` shell command invocations. -pub mod cli; mod models; mod tree; - -/// Index management using [**`libgit2`**][libgit2]. -/// [libgit2]: https://libgit2.org -#[cfg(feature = "git2")] -pub mod git2; +use tree::Tree; +mod repository; +use repository::Repository; +use serde::{Deserialize, Serialize}; pub use models::{CrateDependency, CrateDependencyKind, CrateVersion}; use crate::error::Error; -use crate::index::cli::CommandLineIndex; -#[cfg(feature = "git2")] -use crate::index::git2::Git2Index; +pub struct Index { + repo: Repository, + tree: Tree, +} -/// The crate indexing management strategy type. +/// The configuration struct for the 'git2' index management strategy. /// -/// It represents which index management strategy is currently used. -pub enum Index { - /// Manages the crate index through the invocation of the "git" shell command. - CommandLine(CommandLineIndex), - /// Manages the crate index using [**`libgit2`**]. - /// [libgit2]: https://libgit2.org +/// ```toml +/// [index] +/// type = "git2" | "command-line" # required +/// path = "crate-index" # required +/// ``` +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type")] +#[serde(rename_all = "kebab-case")] +pub enum Config { #[cfg(feature = "git2")] - Git2(Git2Index), + Git2 { + path: PathBuf, + }, + CommandLine { + path: PathBuf, + }, +} + +impl TryFrom for Index { + type Error = Error; + fn try_from(config: Config) -> Result { + let (path, repo) = match config { + Config::CommandLine { path } => { + let repo = Repository::new_cli(path.clone()); + (path, repo) + } + #[cfg(feature = "git2")] + Config::Git2 { path } => { + let repo = Repository::new_git2(&path)?; + (path, repo) + } + }; + + let tree = Tree::new(path); + + Ok(Self { repo, tree }) + } } /// The required trait that any crate index management type must implement. @@ -62,85 +91,59 @@ pub trait Indexer { impl Indexer for Index { fn url(&self) -> Result { - match self { - Index::CommandLine(idx) => idx.url(), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.url(), - } + self.repo.url() } - fn refresh(&self) -> Result<(), Error> { - match self { - Index::CommandLine(idx) => idx.refresh(), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.refresh(), - } - } - - fn commit_and_push(&self, msg: &str) -> Result<(), Error> { - match self { - Index::CommandLine(idx) => idx.commit_and_push(msg), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.commit_and_push(msg), - } + self.repo.refresh() } - fn all_records(&self, name: &str) -> Result, Error> { - match self { - Index::CommandLine(idx) => idx.all_records(name), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.all_records(name), - } + self.tree.all_records(name) } - fn latest_record(&self, name: &str) -> Result { - match self { - Index::CommandLine(idx) => idx.latest_record(name), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.latest_record(name), - } + self.tree.latest_record(name) } - fn match_record(&self, name: &str, req: VersionReq) -> Result { - match self { - Index::CommandLine(idx) => idx.match_record(name, req), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.match_record(name, req), - } + self.tree.match_record(name, req) + } + fn commit_and_push(&self, msg: &str) -> Result<(), Error> { + self.repo.commit_and_push(msg) } - fn add_record(&self, record: CrateVersion) -> Result<(), Error> { - match self { - Index::CommandLine(idx) => idx.add_record(record), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.add_record(record), - } + self.tree.add_record(record) } - fn alter_record(&self, name: &str, version: Version, func: F) -> Result<(), Error> where F: FnOnce(&mut CrateVersion), { - match self { - Index::CommandLine(idx) => idx.alter_record(name, version, func), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.alter_record(name, version, func), - } + self.tree.alter_record(name, version, func) } +} - fn yank_record(&self, name: &str, version: Version) -> Result<(), Error> { - match self { - Index::CommandLine(idx) => idx.yank_record(name, version), - #[cfg(feature = "git2")] - Index::Git2(idx) => idx.yank_record(name, version), +#[cfg(test)] +mod tests { + use super::Config; + #[test] + fn from_config() { + #[cfg(feature = "git2")] + { + let config = r#" + type = "git2" + path = "crate-index" + "#; + match toml::from_str(config).unwrap() { + Config::Git2 { .. } => (), + Config::CommandLine { .. } => panic!("deserialization failed!"), + } } - } - fn unyank_record(&self, name: &str, version: Version) -> Result<(), Error> { - match self { - Index::CommandLine(idx) => idx.unyank_record(name, version), + let config = r#" + type = "command-line" + path = "crate-index" + "#; + match toml::from_str(config).unwrap() { #[cfg(feature = "git2")] - Index::Git2(idx) => idx.unyank_record(name, version), + Config::Git2 { .. } => panic!("deserialization failed!"), + Config::CommandLine { .. } => (), } } } diff --git a/src/index/repository/cli.rs b/src/index/repository/cli.rs new file mode 100644 index 00000000..2dcc22f4 --- /dev/null +++ b/src/index/repository/cli.rs @@ -0,0 +1,69 @@ +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +use super::r#trait::Repository as RepositoryTrait; +use crate::error::Error; + +/// The 'command-line' crate index management strategy type. +/// +/// It manages the crate index through the invocation of "git" shell commands. +#[derive(Debug, Clone, PartialEq)] +pub struct Repository { + path: PathBuf, +} + +impl Repository { + pub fn new>(path: P) -> Self { + Self { path: path.into() } + } +} + +impl RepositoryTrait for Repository { + fn url(&self) -> Result { + let output = Command::new("git") + .arg("remote") + .arg("get-url") + .arg("origin") + .stdout(Stdio::piped()) + .current_dir(self.path.canonicalize()?) + .output()?; + + Ok(String::from_utf8_lossy(output.stdout.as_slice()).into()) + } + + fn refresh(&self) -> Result<(), Error> { + Command::new("git") + .arg("pull") + .arg("--ff-only") + .current_dir(self.path.canonicalize()?) + .spawn()? + .wait()?; + + Ok(()) + } + + fn commit_and_push(&self, msg: &str) -> Result<(), Error> { + Command::new("git") + .arg("add") + .arg("--all") + .current_dir(&self.path) + .spawn()? + .wait()?; + Command::new("git") + .arg("commit") + .arg("-m") + .arg(msg) + .current_dir(&self.path) + .spawn()? + .wait()?; + Command::new("git") + .arg("push") + .arg("origin") + .arg("master") + .current_dir(&self.path) + .spawn()? + .wait()?; + + Ok(()) + } +} diff --git a/src/index/git2.rs b/src/index/repository/git2.rs similarity index 76% rename from src/index/git2.rs rename to src/index/repository/git2.rs index 918cfb2d..1363867d 100644 --- a/src/index/git2.rs +++ b/src/index/repository/git2.rs @@ -1,31 +1,24 @@ -use std::path::PathBuf; -use std::sync::Mutex; - -use semver::{Version, VersionReq}; - +use super::Repo; use crate::error::Error; -use crate::index::tree::Tree; -use crate::index::{CrateVersion, Indexer}; +use git2; +use std::path::Path; +use std::sync::Mutex; /// The 'git2' crate index management strategy type. /// /// It manages the crate index using the [**`libgit2`**][libgit2] library. /// /// [libgit2]: https://libgit2.org -pub struct Git2Index { +pub struct Repository { /// The path of the crate index. - pub(crate) repo: Mutex, - tree: Tree, + repo: Mutex, } -impl Git2Index { - /// Create a Git2Index instance with the given path. - pub fn new>(path: P) -> Result { - let path = path.into(); - let repo = git2::Repository::open(&path)?; - let repo = Mutex::new(repo); - let tree = Tree::new(path); - Ok(Git2Index { repo, tree }) +impl Repository { + /// Create a Repository instance with the given path. + pub fn new>(path: P) -> Result { + let repo = Mutex::new(git2::Repository::open(path)?); + Ok(Repository { repo }) } } @@ -69,7 +62,7 @@ where }) } -impl Indexer for Git2Index { +impl Repo for Repository { fn url(&self) -> Result { let repo = self.repo.lock().unwrap(); let remote = repo.find_remote("origin")?; @@ -134,27 +127,4 @@ impl Indexer for Git2Index { Ok(()) } - - fn match_record(&self, name: &str, req: VersionReq) -> Result { - self.tree.match_record(name, req) - } - - fn all_records(&self, name: &str) -> Result, Error> { - self.tree.all_records(name) - } - - fn latest_record(&self, name: &str) -> Result { - self.tree.latest_record(name) - } - - fn add_record(&self, record: CrateVersion) -> Result<(), Error> { - self.tree.add_record(record) - } - - fn alter_record(&self, name: &str, version: Version, func: F) -> Result<(), Error> - where - F: FnOnce(&mut CrateVersion), - { - self.tree.alter_record(name, version, func) - } } diff --git a/src/index/repository/mod.rs b/src/index/repository/mod.rs new file mode 100644 index 00000000..9a78588a --- /dev/null +++ b/src/index/repository/mod.rs @@ -0,0 +1,54 @@ +mod cli; +#[cfg(feature = "git2")] +mod git2; +use crate::error::Error; +#[cfg(feature = "git2")] +use std::path::Path; +use std::path::PathBuf; + +mod r#trait; +use r#trait::Repository as RepositoryTrait; + +/// [`LocalRepository`] implements the [`Repository`] trait. +/// +/// This git strategy manages a repository on the local machine. It can use *either* +/// a [git-rs](https://docs.rs/git2/0.13.5/git2/) backend, or shell out to +/// the system git CLI (depending on how it is configured). +pub enum Repository { + #[cfg(feature = "git2")] + Git2(git2::Repository), + Cli(cli::Repository), +} + +impl Repository { + #[cfg(feature = "git2")] + pub fn new_git2>(path: P) -> Result { + Ok(Self::Git2(git2::Repository::new(path)?)) + } + + pub fn new_cli>(path: P) -> Self { + Self::Cli(cli::Repository::new(path)) + } + + fn inner(&self) -> &dyn RepositoryTrait { + match self { + #[cfg(feature = "git2")] + Self::Git2(repo) => repo, + Self::Cli(repo) => repo, + } + } +} + +// This is an opaque implementation of `trait::Repository`, since +// that trait is private. +impl Repository { + pub fn url(&self) -> Result { + self.inner().url() + } + pub fn refresh(&self) -> Result<(), Error> { + self.inner().refresh() + } + pub fn commit_and_push(&self, msg: &str) -> Result<(), Error> { + self.inner().commit_and_push(msg) + } +} diff --git a/src/index/repository/trait.rs b/src/index/repository/trait.rs new file mode 100644 index 00000000..ab3a940c --- /dev/null +++ b/src/index/repository/trait.rs @@ -0,0 +1,10 @@ +use crate::Error; + +/// the [`Repository`] trait is implemented by types which can manage a git repository. +pub trait Repository { + fn url(&self) -> Result; + + fn refresh(&self) -> Result<(), Error>; + + fn commit_and_push(&self, msg: &str) -> Result<(), Error>; +} diff --git a/src/index/tree.rs b/src/index/tree.rs index 54d281b7..e8c4225a 100644 --- a/src/index/tree.rs +++ b/src/index/tree.rs @@ -1,12 +1,12 @@ -use super::models::CrateVersion; use crate::error::AlexError; +use crate::index::models::CrateVersion; use crate::Error; use semver::{Version, VersionReq}; -use std::fs; -use std::io; -use std::io::BufRead; -use std::io::Write; -use std::path::PathBuf; +use std::{ + fs, io, + io::{BufRead, Write}, + path::PathBuf, +}; #[derive(Debug, Clone, PartialEq)] pub struct Tree {