Skip to content

Commit

Permalink
improv: cli separation from shared logic
Browse files Browse the repository at this point in the history
  • Loading branch information
edfloreshz committed Oct 24, 2024
1 parent d1cde96 commit 9b9833e
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 331 deletions.
289 changes: 113 additions & 176 deletions src/cli/src/cli.rs

Large diffs are not rendered by default.

219 changes: 95 additions & 124 deletions src/cli/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,68 @@
use std::{fs::remove_dir_all, path::PathBuf};
use std::path::PathBuf;

use devmode::clone::CloneAction;
use devmode::constants::names::{CUSTOM_NAME, NONE, VIM_NAME, VSCODE_NAME};
use devmode::editor::Editor;
use devmode::fork::ForkAction;
use devmode::host::Host;
use devmode::project::find_paths;
use devmode::settings::Settings;
use devmode::DevmodeError;
use devmode::{application::Application, Error};
use requestty::{Answer, Question};
use url_builder::URLBuilder;

pub fn overwrite(path: PathBuf) -> Result<bool, Error> {
println!(
"Error: {} exists and is not an empty directory",
path.display()
);
let question = requestty::Question::confirm("overwrite")
.message("Do you want to overwrite the existing repository?")
pub fn confirm(message: &str, id: &str) -> Result<bool, Error> {
let question = requestty::Question::confirm(id).message(message).build();
let answer = requestty::prompt_one(question)?;
if let Answer::Bool(confirm) = answer {
Ok(confirm)
} else {
Err(Error::Unknown)
}
}

pub fn input(key: &str, message: &str, err: &str) -> Result<String, Error> {
let question = Question::input(key)
.message(message)
.validate(|answer, _| {
if answer.is_empty() {
Err(err.into())
} else {
Ok(())
}
})
.build();
let answer = requestty::prompt_one(question)?;
if let requestty::Answer::Bool(overwrite) = answer {
if overwrite {
remove_dir_all(&path)?;
return Ok(overwrite);
}
if let Answer::String(output) = answer {
Ok(output)
} else {
Err(Error::Unknown)
}
}

pub fn select(key: &str, message: &str, options: Vec<impl Into<String>>) -> Result<String, Error> {
let question = Question::select(key)
.message(message)
.choices(options)
.build();
let answer = requestty::prompt_one(question)?;
if let Answer::ListItem(item) = answer {
Ok(item.text)
} else {
Err(Error::Unknown)
}
Ok(false)
}

pub fn clone_setup() -> Result<CloneAction, Error> {
let mut url = URLBuilder::new();
url.set_protocol("https");
if let Answer::ListItem(host) = pick("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?
{
url.set_host(Host::from(&host.text).url());
}
if let Answer::String(owner) = ask("owner", "Git username:", "Please enter a Git username.")? {
url.add_route(&owner);
}
if let Answer::String(repo) = ask("repo", "Git repo name:", "Please enter a Git repo name.")? {
url.add_route(&repo);
}
let host = select("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?;
url.set_host(Host::from(&host).url());
let owner = input("owner", "Git username:", "Please enter a Git username.")?;
url.add_route(&owner);
let repo = input("repo", "Git repo name:", "Please enter a Git repo name.")?;
url.add_route(&repo);

let mut clone = CloneAction::new(&url.build());

Expand All @@ -51,32 +73,24 @@ pub fn clone_setup() -> Result<CloneAction, Error> {
.iter()
.map(|s| s.as_str())
.collect();
options.insert(0, "None");
if let Answer::ListItem(workspace) = pick("workspace", "Pick a workspace", options)? {
let workspace = workspace.text.to_lowercase();
if !workspace.eq("none") {
clone.set_workspace(workspace);
}
options.insert(0, NONE);
let workspace = select("workspace", "Pick a workspace", options)?;
if !workspace.eq(NONE) {
clone.set_workspace(workspace);
}
Ok(clone)
}

pub fn fork_setup() -> Result<ForkAction, Error> {
let mut fork = ForkAction::new();
if let Answer::ListItem(host) = pick("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?
{
fork.host = Host::from(&host.text);
}
if let Answer::String(owner) = ask("owner", "Git username:", "Please enter a Git username.")? {
fork.owner = owner;
}
if let Answer::String(repo) = ask("repo", "Git repo name:", "Please enter a Git repo name.")? {
fork.repo = repo;
}
if let Answer::String(repo) = ask("upstream", "Upstream URL:", "Please enter an upstream URL.")?
{
fork.upstream = repo;
}
let host = select("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?;
fork.host = Host::from(&host);
let owner = input("owner", "Git username:", "Please enter a Git username.")?;
fork.owner = owner;
let repo = input("repo", "Git repo name:", "Please enter a Git repo name.")?;
fork.repo = repo;
let repo = input("upstream", "Upstream URL:", "Please enter an upstream URL.")?;
fork.upstream = repo;
Ok(fork)
}

Expand All @@ -91,69 +105,34 @@ pub fn config_all() -> Result<Settings, Error> {
}

pub fn config_owner() -> Result<Settings, Error> {
let answer = ask("owner", "Git username:", "Please enter a Git username.")?;
let owner = match answer {
Answer::String(owner) => owner,
_ => return devmode::error("Owner is required."),
};
let current = Settings::current();
let settings = match current {
None => Settings {
owner,
..Default::default()
},
Some(mut settings) => {
settings.owner = owner;
settings
}
};
let owner = input("owner", "Git username:", "Please enter a Git username.")?;
let mut settings = Settings::current().unwrap_or_default();
settings.owner = owner;
Ok(settings)
}

pub fn config_host() -> Result<Settings, Error> {
let answer = pick("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?;
let host = match answer {
Answer::ListItem(item) => Host::from(&item.text).to_string(),
_ => return devmode::error("Host is required."),
};
let current = Settings::current();
let settings = match current {
None => Settings {
host,
..Default::default()
},
Some(mut settings) => {
settings.host = host;
settings
}
};
let host = select("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?;
let mut settings = Settings::current().unwrap_or_default();
settings.host = host;
Ok(settings)
}

pub fn config_editor() -> Result<Settings, Error> {
let answer = pick(
let editor = select(
"editor",
"Choose your favorite editor:",
vec!["Vim", "VSCode", "Custom"],
vec![VIM_NAME, VSCODE_NAME, CUSTOM_NAME],
)?;
let editor = match answer {
Answer::ListItem(item) => {
if item.text.to_lowercase() == "custom" {
let answer = ask(
"command",
"Editor command:",
"Please enter a editor command.",
)?;
if let Answer::String(name) = answer {
Editor::custom(name)
} else {
return devmode::error("Editor name is required.");
}
} else {
Editor::new(Application::from(&item.text))
}
}
_ => return devmode::error("Editor must be picked."),
let editor = if editor.eq(CUSTOM_NAME) {
let command = input(
"command",
"Editor command:",
"Please enter a editor command.",
)?;
Editor::custom(command)
} else {
Editor::new(Application::from(&editor))
};
let current = Settings::current();
let settings = match current {
Expand All @@ -169,37 +148,29 @@ pub fn config_editor() -> Result<Settings, Error> {
Ok(settings)
}

pub fn select_repo(paths: Vec<&str>) -> Result<String, Error> {
let answer = pick("repo", "Select the repository you want to open:", paths)?;
let repo = match answer {
Answer::ListItem(item) => item.text,
_ => return devmode::error("Repository must be picked."),
pub fn select_repo(project: &str, workspace: Option<&str>) -> Result<PathBuf, Error> {
let paths = if let Some(workspace) = workspace {
find_paths(project)?
.iter()
.filter(|path| path.display().to_string().contains(&workspace))
.map(|path| path.display().to_string().to_owned())
.collect::<Vec<String>>()
} else {
find_paths(project)?
.iter()
.map(|path| path.display().to_string().to_owned())
.collect::<Vec<String>>()
};
Ok(repo)
let repo = select("repo", "Select the repository you want to open:", paths)?;
Ok(PathBuf::from(repo))
}

pub fn ask(key: &str, message: &str, err: &str) -> Result<Answer, Error> {
requestty::prompt_one(
Question::input(key)
.message(message)
.validate(|owner, _previous| {
if owner.is_empty() {
Err(err.into())
} else {
Ok(())
}
})
.build(),
)
.map_err(|e| Error::String(e.to_string()))
pub fn create_workspace() -> Result<bool, Error> {
let create = confirm("workspace", "Would you like to create this workspace?")?;
Ok(create)
}

pub fn pick(key: &str, message: &str, options: Vec<&str>) -> Result<Answer, Error> {
requestty::prompt_one(
Question::select(key)
.message(message)
.choices(options)
.build(),
)
.map_err(|e| Error::String(e.to_string()))
pub fn overwrite() -> Result<bool, Error> {
let overwrite = confirm("overwrite", "Found existing repository, overwrite it?")?;
Ok(overwrite)
}
4 changes: 3 additions & 1 deletion src/shared/application.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::fmt::{Display, Formatter};
use std::path::PathBuf;
use std::process::Command;

use crate::{DevmodeStatus, Error};
Expand All @@ -24,7 +25,8 @@ impl Application {
_ => "",
})
}
pub fn run(&self, arg: String) -> Result<(), Error> {
pub fn run(&self, path: PathBuf) -> Result<(), Error> {
let arg = path.display().to_string();
match self {
Application::VSCode => {
if cfg!(target_os = "windows") {
Expand Down
10 changes: 10 additions & 0 deletions src/shared/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub enum Error {
Generic(&'static str),
#[error("String error: {0}")]
String(String),
#[error("An unknown error ocurred")]
Unknown,
}

pub fn error<T>(msg: &'static str) -> Result<T, Error> {
Expand All @@ -52,4 +54,12 @@ pub enum DevmodeError {
FailedToSetRemote,
#[error("Failed to get branch")]
FailedToGetBranch,
#[error("Failed to find workspace")]
WorkspaceMissing,
#[error("Failed to find project")]
ProjectNotFound,
#[error("Multiple projects found. Please specify the project name.")]
MultipleProjectsFound,
#[error("Path not found")]
PathNotFound,
}
12 changes: 7 additions & 5 deletions src/shared/git_pull.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ fn fast_forward(
fn get_branch(repo: &Repository) -> Result<String, Error> {
let head = match repo.head() {
Ok(head) => Some(head),
Err(ref e) if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound => {
Err(ref e)
if e.code().eq(&ErrorCode::UnbornBranch) || e.code().eq(&ErrorCode::NotFound) =>
{
None
}
Err(e) => return Err(Error::Git(e)),
Expand All @@ -135,7 +137,7 @@ fn fetch<'a>(
let mut cb = git2::RemoteCallbacks::new();

cb.transfer_progress(|stats| {
if stats.received_objects() == stats.total_objects() {
if stats.received_objects().eq(&stats.total_objects()) {
print!(
"Resolving deltas {}/{}\r",
stats.indexed_deltas(),
Expand Down Expand Up @@ -221,7 +223,7 @@ pub fn status_short(path: String) -> Result<(), GitError> {
};
let mut wstatus = match entry.status() {
s if s.contains(git2::Status::WT_NEW) => {
if istatus == ' ' {
if istatus.eq(&' ') {
istatus = '?';
}
'?'
Expand All @@ -237,7 +239,7 @@ pub fn status_short(path: String) -> Result<(), GitError> {
istatus = '!';
wstatus = '!';
}
if istatus == '?' && wstatus == '?' {
if istatus.eq(&'?') && wstatus.eq(&'?') {
continue;
}
let mut extra = "";
Expand Down Expand Up @@ -300,7 +302,7 @@ pub fn status_short(path: String) -> Result<(), GitError> {

for entry in statuses
.iter()
.filter(|e| e.status() == git2::Status::WT_NEW)
.filter(|e| e.status().eq(&git2::Status::WT_NEW))
{
println!(
"?? {}",
Expand Down
4 changes: 2 additions & 2 deletions src/shared/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ impl Host {
}
pub fn from(text: &str) -> Self {
let text = text.to_lowercase();
if text.contains("github") || text == "gh" {
if text.contains("github") || text.eq(&"gh") {
Host::GitHub
} else if text.contains("gitlab") || text == "gl" {
} else if text.contains("gitlab") || text.eq(&"gl") {
Host::GitLab
} else {
Host::None
Expand Down
Loading

0 comments on commit 9b9833e

Please sign in to comment.