Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
515 changes: 0 additions & 515 deletions src/bin/ambit/cmd.rs

This file was deleted.

106 changes: 47 additions & 59 deletions src/bin/ambit/main.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
mod cmd;
mod directories;

use clap::{App, AppSettings, Arg, SubCommand};

use std::process;

use ambit::error::{self, AmbitResult};
use ambit::{
cmd,
directories::AMBIT_PATHS,
error::{self, AmbitResult},
linker::{self, Linker},
};

// Return instance of ambit application
fn get_app() -> App<'static, 'static> {
let force_arg = Arg::with_name("force")
.short("f")
.long("force")
.help("Overwrite currently initialized dotfile repository");
let linker_args = &[
force_arg.clone(),
Arg::with_name("dry-run")
.long("dry-run")
.help("If set, do not actually symlink the files"),
Arg::with_name("quiet")
.long("quiet")
.short("q")
.help("Don't report individual symlinks"),
];

App::new("ambit")
.about("Dotfile manager")
Expand All @@ -38,43 +50,17 @@ fn get_app() -> App<'static, 'static> {
.subcommand(
SubCommand::with_name("sync")
.about("Sync files in dotfile repository to system through symbolic links")
.arg(
Arg::with_name("dry-run")
.long("dry-run")
.help("If set, do not actually symlink the files"),
)
.arg(
Arg::with_name("quiet")
.long("quiet")
.short("q")
.help("Don't report individual symlinks"),
)
.arg(
Arg::with_name("move")
.long("move")
.short("m")
.help("Move host files into dotfile repository if needed")
.long_help("Will automatically move host files into repository if they don't already exist in the repository and then symlink them"),
)
.arg(
Arg::with_name("use-repo-config")
.long("use-repo-config")
.help("Recursively search dotfile repository for configuration file and use it to sync")
)
.arg(
Arg::with_name("use-repo-config-if-required")
.long("use-repo-config-if-required")
.help("Search for configuration file in dotfile repository if configuration in default location does not exist")
)
.arg(
Arg::with_name("use-any-repo-config-found")
.long("use-any-repo-config-found")
.help("Use first repository configuration found after recursive search")
)
.args(linker_args),
)
.subcommand(
SubCommand::with_name("clean")
.about("Remove all symlinks and delete host files")
.about("Remove all symlinks and delete host files")
.args(linker_args),
)
.subcommand(
SubCommand::with_name("move")
.about("Move host files into dotfile repository if needed")
.args(linker_args),
)
.subcommand(SubCommand::with_name("check").about("Check ambit configuration for errors"))
}
Expand All @@ -95,23 +81,26 @@ fn run() -> AmbitResult<()> {
cmd::git(git_arguments)?;
} else if matches.is_present("check") {
cmd::check()?;
} else if let Some(matches) = matches.subcommand_matches("sync") {
let dry_run = matches.is_present("dry-run");
let quiet = matches.is_present("quiet");
let move_files = matches.is_present("move");
let use_repo_config = matches.is_present("use-repo-config");
let use_repo_config_if_required = matches.is_present("use-repo-config-if-required");
let use_any_repo_config = matches.is_present("use-any-repo-config-found");
cmd::sync(
dry_run,
quiet,
move_files,
use_repo_config,
use_repo_config_if_required,
use_any_repo_config,
)?;
} else if matches.is_present("clean") {
cmd::clean()?;
} else {
type LinkerAction = fn(&Linker) -> AmbitResult<()>;
let linker_commands: &[(&str, LinkerAction)] = &[
("sync", Linker::sync_paths),
("move", Linker::move_paths),
("clean", Linker::clean_paths),
];
// Iterate through sync, move, and clean commands and execute corresponding function.
for (subcommand, func) in linker_commands {
if let Some(matches) = matches.subcommand_matches(subcommand) {
let options = linker::Options {
force: matches.is_present("force"),
dry_run: matches.is_present("dry-run"),
quiet: matches.is_present("quiet"),
};
let linker = Linker::new(&AMBIT_PATHS, options)?;
func(&linker)?;
break;
}
}
}
Ok(())
}
Expand Down Expand Up @@ -157,10 +146,9 @@ mod tests {
#[test]
fn git_arguments_with_hyphen() {
let matches = arguments_list!("git", "status", "-v", "--short");
let git_arguments: Option<Vec<_>> = match matches.subcommand_matches("git") {
Some(matches) => Some(matches.values_of("GIT_ARGUMENTS").unwrap().collect()),
None => None,
};
let git_arguments: Option<Vec<_>> = matches
.subcommand_matches("git")
.map(|matches| matches.values_of("GIT_ARGUMENTS").unwrap().collect());
assert_eq!(git_arguments, Some(vec!["status", "-v", "--short"]));
}

Expand Down
103 changes: 103 additions & 0 deletions src/cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Symlink function is dependent on OS
use crate::{
config,
directories::AMBIT_PATHS,
error::{AmbitError, AmbitResult},
};
use std::{
io::{self, Write},
process::Command,
};

// Initialize config and repository directory
fn ensure_no_repo_conflicts(force: bool) -> AmbitResult<()> {
let repo_exists = AMBIT_PATHS.repo.exists();
if repo_exists
// No need to prompt if force is true.
&& !force
// Ask user if they want to overwrite.
&& !prompt_confirm("A repository already exists. Overwrite?")?
{
return Err(AmbitError::Other(
"Dotfile repository already exists.\nUse '-f' flag to overwrite.".to_owned(),
));
} else if repo_exists {
// Remove if either force is enabled or if the user confirmed to overwrite.
AMBIT_PATHS.repo.remove()?;
}
Ok(())
}

// Prompt user for confirmation with message.
pub fn prompt_confirm(message: &str) -> AmbitResult<bool> {
print!("{} [Y/n] ", message);
io::stdout().flush()?;
let mut answer = String::new();
io::stdin().read_line(&mut answer)?;
Ok(answer.trim().to_lowercase() == "y")
}

// Initialize an empty dotfile repository
pub fn init(force: bool) -> AmbitResult<()> {
ensure_no_repo_conflicts(force)?;
AMBIT_PATHS.repo.create()?;
// Initialize an empty git repository
git(vec!["init"])?;
Ok(())
}

// Clone an existing dotfile repository with given origin
pub fn clone(force: bool, clone_arguments: Vec<&str>) -> AmbitResult<()> {
ensure_no_repo_conflicts(force)?;
// Clone will handle creating the repository directory
let repo_path = AMBIT_PATHS.repo.to_str()?;
let status = Command::new("git")
.arg("clone")
.args(clone_arguments)
.args(vec!["--", repo_path])
.status()?;
match status.success() {
true => {
println!("Successfully cloned repository to {}", repo_path);
Ok(())
}
false => Err(AmbitError::Other("Failed to clone repository".to_owned())),
}
}

// Check ambit configuration for errors
pub fn check() -> AmbitResult<()> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would probably be useful if this would check more than just the configuration, such as whether the symlinks specified in the config actually exist.

config::get_entries(&AMBIT_PATHS.config)?;
Ok(())
}

// Run git commands from the dotfile repository
pub fn git(git_arguments: Vec<&str>) -> AmbitResult<()> {
// The path to repository (git-dir) and the working tree (work-tree) is
// passed to ensure that git commands are run from the dotfile repository
let mut command = Command::new("git");
command.args(&[
["--git-dir=", AMBIT_PATHS.git.to_str()?].concat(),
["--work-tree=", AMBIT_PATHS.repo.to_str()?].concat(),
]);
command.args(git_arguments);
// Conditional compilation so that this still compiles on Windows.
#[cfg(unix)]
fn exec_git_command(mut command: Command) -> AmbitResult<()> {
use std::os::unix::process::CommandExt;
// Try to replace this process with the `git` process.
// This is to allow stuff like terminal colors.
// We just want `ambit git` to act like `cd ~/.config/ambit/repo; git`.
// If the `.exec()` method returns, it failed to execute, so it's automatically an error.
Err(AmbitError::Io(command.exec()))
}
#[cfg(not(unix))]
fn exec_git_command(mut command: Command) -> AmbitResult<()> {
// Not easy to do this on other systems, just use defaults
let output = command.output()?;
io::stdout().write_all(&output.stdout)?;
io::stdout().write_all(&output.stderr)?;
Ok(())
}
exec_git_command(command)
}
2 changes: 1 addition & 1 deletion src/config/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl MatchExpr {
}
}

// A comma seperated list of `T`s, with optional trailing comma.
// A comma separated list of `T`s, with optional trailing comma.
// (The delimiters are passed to the parse() function.)
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct CommaList<T: SimpleParse> {
Expand Down
13 changes: 9 additions & 4 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ pub use parser::Parser;

use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::iter::Peekable;

use crate::{
directories::AmbitPath,
error::{AmbitError, AmbitResult},
};

#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum ParseErrorType {
Expand Down Expand Up @@ -42,7 +46,8 @@ impl From<ParseErrorType> for ParseError {

pub type ParseResult<T> = std::result::Result<T, ParseError>;

pub fn get_entries<I: Iterator<Item = char>>(char_iter: Peekable<I>) -> Parser<Lexer<I>> {
let lex = Lexer::new(char_iter);
Parser::new(lex.peekable())
pub fn get_entries(config_path: &AmbitPath) -> AmbitResult<Vec<Entry>> {
Parser::new(Lexer::new(config_path.as_string()?.chars().peekable()).peekable())
.collect::<Result<Vec<_>, _>>()
.map_err(AmbitError::Parse)
}
5 changes: 1 addition & 4 deletions src/config/strgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,10 +267,7 @@ impl MatchExpr {
fn raw_iter(&self) -> MatchIter {
MatchIter {
expr: &self,
spec_iter: match self.resolve() {
Some(spec) => Some(spec.raw_iter()),
None => None,
},
spec_iter: self.resolve().map(|spec| spec.raw_iter()),
}
}
}
Expand Down
35 changes: 28 additions & 7 deletions src/bin/ambit/directories.rs → src/directories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,47 @@ use std::{
env,
fs::{self, File},
io::Read,
ops::Deref,
path::PathBuf,
};

use ambit::error::{AmbitError, AmbitResult};
use crate::error::{AmbitError, AmbitResult};

pub const CONFIG_NAME: &str = "config.ambit";

#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum AmbitPathKind {
File,
Directory,
}

#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct AmbitPath {
pub path: PathBuf,
kind: AmbitPathKind,
}

impl From<&PathBuf> for AmbitPath {
fn from(path: &PathBuf) -> Self {
let kind = if path.is_file() {
AmbitPathKind::File
} else {
AmbitPathKind::Directory
};
Self {
path: path.to_path_buf(),
kind,
}
}
}

impl Deref for AmbitPath {
type Target = PathBuf;
fn deref(&self) -> &Self::Target {
&self.path
}
}

impl AmbitPath {
pub fn new(path: PathBuf, kind: AmbitPathKind) -> Self {
Self { path, kind }
Expand Down Expand Up @@ -78,6 +100,7 @@ impl AmbitPath {
pub fn create(&self) -> AmbitResult<()> {
match self.kind {
AmbitPathKind::File => {
self.ensure_parent_dirs_exist()?;
File::create(&self.path)?;
}
AmbitPathKind::Directory => {
Expand All @@ -96,6 +119,7 @@ impl AmbitPath {
}
}

#[derive(Debug, Clone)]
pub struct AmbitPaths {
pub home: AmbitPath,
pub config: AmbitPath,
Expand Down Expand Up @@ -129,10 +153,7 @@ impl AmbitPaths {

// Attempt to fetch path from env if set
fn get_path_from_env(key: &str) -> Option<PathBuf> {
match env::var_os(key) {
Some(path) => Some(PathBuf::from(path)),
None => None,
}
env::var_os(key).map(PathBuf::from)
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
pub mod cmd;
pub mod config;
pub mod directories;
pub mod error;
pub mod linker;
Loading