Skip to content

Commit 9b9833e

Browse files
committed
improv: cli separation from shared logic
1 parent d1cde96 commit 9b9833e

9 files changed

Lines changed: 330 additions & 331 deletions

File tree

src/cli/src/cli.rs

Lines changed: 113 additions & 176 deletions
Large diffs are not rendered by default.

src/cli/src/input.rs

Lines changed: 95 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,68 @@
1-
use std::{fs::remove_dir_all, path::PathBuf};
1+
use std::path::PathBuf;
22

33
use devmode::clone::CloneAction;
4+
use devmode::constants::names::{CUSTOM_NAME, NONE, VIM_NAME, VSCODE_NAME};
45
use devmode::editor::Editor;
56
use devmode::fork::ForkAction;
67
use devmode::host::Host;
8+
use devmode::project::find_paths;
79
use devmode::settings::Settings;
810
use devmode::DevmodeError;
911
use devmode::{application::Application, Error};
1012
use requestty::{Answer, Question};
1113
use url_builder::URLBuilder;
1214

13-
pub fn overwrite(path: PathBuf) -> Result<bool, Error> {
14-
println!(
15-
"Error: {} exists and is not an empty directory",
16-
path.display()
17-
);
18-
let question = requestty::Question::confirm("overwrite")
19-
.message("Do you want to overwrite the existing repository?")
15+
pub fn confirm(message: &str, id: &str) -> Result<bool, Error> {
16+
let question = requestty::Question::confirm(id).message(message).build();
17+
let answer = requestty::prompt_one(question)?;
18+
if let Answer::Bool(confirm) = answer {
19+
Ok(confirm)
20+
} else {
21+
Err(Error::Unknown)
22+
}
23+
}
24+
25+
pub fn input(key: &str, message: &str, err: &str) -> Result<String, Error> {
26+
let question = Question::input(key)
27+
.message(message)
28+
.validate(|answer, _| {
29+
if answer.is_empty() {
30+
Err(err.into())
31+
} else {
32+
Ok(())
33+
}
34+
})
2035
.build();
2136
let answer = requestty::prompt_one(question)?;
22-
if let requestty::Answer::Bool(overwrite) = answer {
23-
if overwrite {
24-
remove_dir_all(&path)?;
25-
return Ok(overwrite);
26-
}
37+
if let Answer::String(output) = answer {
38+
Ok(output)
39+
} else {
40+
Err(Error::Unknown)
41+
}
42+
}
43+
44+
pub fn select(key: &str, message: &str, options: Vec<impl Into<String>>) -> Result<String, Error> {
45+
let question = Question::select(key)
46+
.message(message)
47+
.choices(options)
48+
.build();
49+
let answer = requestty::prompt_one(question)?;
50+
if let Answer::ListItem(item) = answer {
51+
Ok(item.text)
52+
} else {
53+
Err(Error::Unknown)
2754
}
28-
Ok(false)
2955
}
3056

3157
pub fn clone_setup() -> Result<CloneAction, Error> {
3258
let mut url = URLBuilder::new();
3359
url.set_protocol("https");
34-
if let Answer::ListItem(host) = pick("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?
35-
{
36-
url.set_host(Host::from(&host.text).url());
37-
}
38-
if let Answer::String(owner) = ask("owner", "Git username:", "Please enter a Git username.")? {
39-
url.add_route(&owner);
40-
}
41-
if let Answer::String(repo) = ask("repo", "Git repo name:", "Please enter a Git repo name.")? {
42-
url.add_route(&repo);
43-
}
60+
let host = select("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?;
61+
url.set_host(Host::from(&host).url());
62+
let owner = input("owner", "Git username:", "Please enter a Git username.")?;
63+
url.add_route(&owner);
64+
let repo = input("repo", "Git repo name:", "Please enter a Git repo name.")?;
65+
url.add_route(&repo);
4466

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

@@ -51,32 +73,24 @@ pub fn clone_setup() -> Result<CloneAction, Error> {
5173
.iter()
5274
.map(|s| s.as_str())
5375
.collect();
54-
options.insert(0, "None");
55-
if let Answer::ListItem(workspace) = pick("workspace", "Pick a workspace", options)? {
56-
let workspace = workspace.text.to_lowercase();
57-
if !workspace.eq("none") {
58-
clone.set_workspace(workspace);
59-
}
76+
options.insert(0, NONE);
77+
let workspace = select("workspace", "Pick a workspace", options)?;
78+
if !workspace.eq(NONE) {
79+
clone.set_workspace(workspace);
6080
}
6181
Ok(clone)
6282
}
6383

6484
pub fn fork_setup() -> Result<ForkAction, Error> {
6585
let mut fork = ForkAction::new();
66-
if let Answer::ListItem(host) = pick("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?
67-
{
68-
fork.host = Host::from(&host.text);
69-
}
70-
if let Answer::String(owner) = ask("owner", "Git username:", "Please enter a Git username.")? {
71-
fork.owner = owner;
72-
}
73-
if let Answer::String(repo) = ask("repo", "Git repo name:", "Please enter a Git repo name.")? {
74-
fork.repo = repo;
75-
}
76-
if let Answer::String(repo) = ask("upstream", "Upstream URL:", "Please enter an upstream URL.")?
77-
{
78-
fork.upstream = repo;
79-
}
86+
let host = select("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?;
87+
fork.host = Host::from(&host);
88+
let owner = input("owner", "Git username:", "Please enter a Git username.")?;
89+
fork.owner = owner;
90+
let repo = input("repo", "Git repo name:", "Please enter a Git repo name.")?;
91+
fork.repo = repo;
92+
let repo = input("upstream", "Upstream URL:", "Please enter an upstream URL.")?;
93+
fork.upstream = repo;
8094
Ok(fork)
8195
}
8296

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

93107
pub fn config_owner() -> Result<Settings, Error> {
94-
let answer = ask("owner", "Git username:", "Please enter a Git username.")?;
95-
let owner = match answer {
96-
Answer::String(owner) => owner,
97-
_ => return devmode::error("Owner is required."),
98-
};
99-
let current = Settings::current();
100-
let settings = match current {
101-
None => Settings {
102-
owner,
103-
..Default::default()
104-
},
105-
Some(mut settings) => {
106-
settings.owner = owner;
107-
settings
108-
}
109-
};
108+
let owner = input("owner", "Git username:", "Please enter a Git username.")?;
109+
let mut settings = Settings::current().unwrap_or_default();
110+
settings.owner = owner;
110111
Ok(settings)
111112
}
112113

113114
pub fn config_host() -> Result<Settings, Error> {
114-
let answer = pick("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?;
115-
let host = match answer {
116-
Answer::ListItem(item) => Host::from(&item.text).to_string(),
117-
_ => return devmode::error("Host is required."),
118-
};
119-
let current = Settings::current();
120-
let settings = match current {
121-
None => Settings {
122-
host,
123-
..Default::default()
124-
},
125-
Some(mut settings) => {
126-
settings.host = host;
127-
settings
128-
}
129-
};
115+
let host = select("host", "Choose your Git host:", vec!["GitHub", "GitLab"])?;
116+
let mut settings = Settings::current().unwrap_or_default();
117+
settings.host = host;
130118
Ok(settings)
131119
}
132120

133121
pub fn config_editor() -> Result<Settings, Error> {
134-
let answer = pick(
122+
let editor = select(
135123
"editor",
136124
"Choose your favorite editor:",
137-
vec!["Vim", "VSCode", "Custom"],
125+
vec![VIM_NAME, VSCODE_NAME, CUSTOM_NAME],
138126
)?;
139-
let editor = match answer {
140-
Answer::ListItem(item) => {
141-
if item.text.to_lowercase() == "custom" {
142-
let answer = ask(
143-
"command",
144-
"Editor command:",
145-
"Please enter a editor command.",
146-
)?;
147-
if let Answer::String(name) = answer {
148-
Editor::custom(name)
149-
} else {
150-
return devmode::error("Editor name is required.");
151-
}
152-
} else {
153-
Editor::new(Application::from(&item.text))
154-
}
155-
}
156-
_ => return devmode::error("Editor must be picked."),
127+
let editor = if editor.eq(CUSTOM_NAME) {
128+
let command = input(
129+
"command",
130+
"Editor command:",
131+
"Please enter a editor command.",
132+
)?;
133+
Editor::custom(command)
134+
} else {
135+
Editor::new(Application::from(&editor))
157136
};
158137
let current = Settings::current();
159138
let settings = match current {
@@ -169,37 +148,29 @@ pub fn config_editor() -> Result<Settings, Error> {
169148
Ok(settings)
170149
}
171150

172-
pub fn select_repo(paths: Vec<&str>) -> Result<String, Error> {
173-
let answer = pick("repo", "Select the repository you want to open:", paths)?;
174-
let repo = match answer {
175-
Answer::ListItem(item) => item.text,
176-
_ => return devmode::error("Repository must be picked."),
151+
pub fn select_repo(project: &str, workspace: Option<&str>) -> Result<PathBuf, Error> {
152+
let paths = if let Some(workspace) = workspace {
153+
find_paths(project)?
154+
.iter()
155+
.filter(|path| path.display().to_string().contains(&workspace))
156+
.map(|path| path.display().to_string().to_owned())
157+
.collect::<Vec<String>>()
158+
} else {
159+
find_paths(project)?
160+
.iter()
161+
.map(|path| path.display().to_string().to_owned())
162+
.collect::<Vec<String>>()
177163
};
178-
Ok(repo)
164+
let repo = select("repo", "Select the repository you want to open:", paths)?;
165+
Ok(PathBuf::from(repo))
179166
}
180167

181-
pub fn ask(key: &str, message: &str, err: &str) -> Result<Answer, Error> {
182-
requestty::prompt_one(
183-
Question::input(key)
184-
.message(message)
185-
.validate(|owner, _previous| {
186-
if owner.is_empty() {
187-
Err(err.into())
188-
} else {
189-
Ok(())
190-
}
191-
})
192-
.build(),
193-
)
194-
.map_err(|e| Error::String(e.to_string()))
168+
pub fn create_workspace() -> Result<bool, Error> {
169+
let create = confirm("workspace", "Would you like to create this workspace?")?;
170+
Ok(create)
195171
}
196172

197-
pub fn pick(key: &str, message: &str, options: Vec<&str>) -> Result<Answer, Error> {
198-
requestty::prompt_one(
199-
Question::select(key)
200-
.message(message)
201-
.choices(options)
202-
.build(),
203-
)
204-
.map_err(|e| Error::String(e.to_string()))
173+
pub fn overwrite() -> Result<bool, Error> {
174+
let overwrite = confirm("overwrite", "Found existing repository, overwrite it?")?;
175+
Ok(overwrite)
205176
}

src/shared/application.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::fmt::{Display, Formatter};
2+
use std::path::PathBuf;
23
use std::process::Command;
34

45
use crate::{DevmodeStatus, Error};
@@ -24,7 +25,8 @@ impl Application {
2425
_ => "",
2526
})
2627
}
27-
pub fn run(&self, arg: String) -> Result<(), Error> {
28+
pub fn run(&self, path: PathBuf) -> Result<(), Error> {
29+
let arg = path.display().to_string();
2830
match self {
2931
Application::VSCode => {
3032
if cfg!(target_os = "windows") {

src/shared/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub enum Error {
2626
Generic(&'static str),
2727
#[error("String error: {0}")]
2828
String(String),
29+
#[error("An unknown error ocurred")]
30+
Unknown,
2931
}
3032

3133
pub fn error<T>(msg: &'static str) -> Result<T, Error> {
@@ -52,4 +54,12 @@ pub enum DevmodeError {
5254
FailedToSetRemote,
5355
#[error("Failed to get branch")]
5456
FailedToGetBranch,
57+
#[error("Failed to find workspace")]
58+
WorkspaceMissing,
59+
#[error("Failed to find project")]
60+
ProjectNotFound,
61+
#[error("Multiple projects found. Please specify the project name.")]
62+
MultipleProjectsFound,
63+
#[error("Path not found")]
64+
PathNotFound,
5565
}

src/shared/git_pull.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ fn fast_forward(
115115
fn get_branch(repo: &Repository) -> Result<String, Error> {
116116
let head = match repo.head() {
117117
Ok(head) => Some(head),
118-
Err(ref e) if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound => {
118+
Err(ref e)
119+
if e.code().eq(&ErrorCode::UnbornBranch) || e.code().eq(&ErrorCode::NotFound) =>
120+
{
119121
None
120122
}
121123
Err(e) => return Err(Error::Git(e)),
@@ -135,7 +137,7 @@ fn fetch<'a>(
135137
let mut cb = git2::RemoteCallbacks::new();
136138

137139
cb.transfer_progress(|stats| {
138-
if stats.received_objects() == stats.total_objects() {
140+
if stats.received_objects().eq(&stats.total_objects()) {
139141
print!(
140142
"Resolving deltas {}/{}\r",
141143
stats.indexed_deltas(),
@@ -221,7 +223,7 @@ pub fn status_short(path: String) -> Result<(), GitError> {
221223
};
222224
let mut wstatus = match entry.status() {
223225
s if s.contains(git2::Status::WT_NEW) => {
224-
if istatus == ' ' {
226+
if istatus.eq(&' ') {
225227
istatus = '?';
226228
}
227229
'?'
@@ -237,7 +239,7 @@ pub fn status_short(path: String) -> Result<(), GitError> {
237239
istatus = '!';
238240
wstatus = '!';
239241
}
240-
if istatus == '?' && wstatus == '?' {
242+
if istatus.eq(&'?') && wstatus.eq(&'?') {
241243
continue;
242244
}
243245
let mut extra = "";
@@ -300,7 +302,7 @@ pub fn status_short(path: String) -> Result<(), GitError> {
300302

301303
for entry in statuses
302304
.iter()
303-
.filter(|e| e.status() == git2::Status::WT_NEW)
305+
.filter(|e| e.status().eq(&git2::Status::WT_NEW))
304306
{
305307
println!(
306308
"?? {}",

src/shared/host.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ impl Host {
2020
}
2121
pub fn from(text: &str) -> Self {
2222
let text = text.to_lowercase();
23-
if text.contains("github") || text == "gh" {
23+
if text.contains("github") || text.eq(&"gh") {
2424
Host::GitHub
25-
} else if text.contains("gitlab") || text == "gl" {
25+
} else if text.contains("gitlab") || text.eq(&"gl") {
2626
Host::GitLab
2727
} else {
2828
Host::None

0 commit comments

Comments
 (0)