From 9b9833e692540da6fa0742302c8c7d9994463863 Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Fri, 25 Oct 2024 00:30:07 +0200 Subject: [PATCH] improv: cli separation from shared logic --- src/cli/src/cli.rs | 289 +++++++++++++++----------------------- src/cli/src/input.rs | 219 +++++++++++++---------------- src/shared/application.rs | 4 +- src/shared/error.rs | 10 ++ src/shared/git_pull.rs | 12 +- src/shared/host.rs | 4 +- src/shared/project.rs | 37 +++-- src/shared/workspace.rs | 84 ++++++++++- src/ui/src/app.rs | 2 +- 9 files changed, 330 insertions(+), 331 deletions(-) diff --git a/src/cli/src/cli.rs b/src/cli/src/cli.rs index ea4bd8a..dda976f 100644 --- a/src/cli/src/cli.rs +++ b/src/cli/src/cli.rs @@ -1,23 +1,22 @@ use clap::{Parser, Subcommand}; use devmode::action::Action; use devmode::config::Config; -use devmode::workspace::Workspace; +use devmode::workspace::{Workspace, WorkspaceOptions}; use devmode::{DevmodeError, Error}; +use fs_extra::dir::CopyOptions; use fs_extra::{dir, move_items}; -use libset::routes::home; use regex::bytes::Regex; -use requestty::Answer; -use std::fs; +use std::fs::remove_dir_all; use std::path::PathBuf; use url_builder::URLBuilder; use crate::input::{ - clone_setup, config_all, config_editor, config_host, config_owner, fork_setup, overwrite, - select_repo, + clone_setup, config_all, config_editor, config_host, config_owner, create_workspace, + fork_setup, overwrite, select_repo, }; use devmode::fork::ForkAction; use devmode::host::Host; -use devmode::project::{create_paths_reader, find_paths, OpenAction}; +use devmode::project::{find_paths, OpenAction}; use devmode::settings::Settings; use devmode::{clone::CloneAction, constants::patterns::GIT_URL}; @@ -177,7 +176,7 @@ impl Cli { include, remove, list, - } => Cli::workspace(Workspace { + } => Cli::workspace(WorkspaceOptions { name: name.to_owned(), add: *add, delete: *delete, @@ -194,7 +193,7 @@ impl Cli { url.set_protocol("https"); let mut clone = if args.is_empty() { clone_setup()? - } else if Settings::current().is_some() && args.len() == 1 { + } else if Settings::current().is_some() && args.len().eq(&1) { let Some(options) = Settings::current() else { return Err(Error::Devmode(DevmodeError::AppSettingsNotFound)); }; @@ -206,13 +205,13 @@ impl Cli { } CloneAction::new(&url.build()) - } else if args.len() == 1 { + } else if args.len().eq(&1) { if let Some(url) = args.first() { CloneAction::new(url) } else { return Err(Error::Devmode(DevmodeError::NoUrlProvided)); } - } else if args.len() == 3 { + } else if args.len().eq(&3) { if let Some(host) = args.first() { url.set_host(Host::from(host).url()); } @@ -234,7 +233,13 @@ impl Cli { match error { Error::Git(error) => match error.code() { git2::ErrorCode::Exists => { - if overwrite(clone.get_local_path()?)? { + let path = clone.get_local_path()?; + println!( + "Error: {} exists and is not an empty directory", + path.display() + ); + if overwrite()? { + remove_dir_all(&path)?; clone.run()?; } } @@ -248,29 +253,36 @@ impl Cli { } fn open(project: &str) -> Result<(), Error> { - let reader = create_paths_reader()?; - let paths = find_paths(reader, project)?; + let paths = find_paths(project)?; if paths.is_empty() { Err(Error::Devmode(DevmodeError::NoProjectFound)) } else if paths.len() > 1 { - let paths: Vec<&str> = paths.iter().map(|s| s as &str).collect(); - let path = select_repo(paths)?.to_string(); - OpenAction::new(project).open(vec![path]) + let path = select_repo(project, None)?; + OpenAction::new(project).open(path) } else { - OpenAction::new(project).open(paths) + OpenAction::new(project).open( + paths + .get(0) + .ok_or(Error::Devmode(DevmodeError::PathNotFound))? + .clone(), + ) } } + fn update(project: &str) -> Result<(), Error> { - let reader = create_paths_reader()?; - let paths = find_paths(reader, project)?; + let paths = find_paths(project)?; if paths.is_empty() { Err(Error::Devmode(DevmodeError::NoProjectFound)) } else if paths.len() > 1 { - let paths: Vec<&str> = paths.iter().map(|s| s as &str).collect(); - let path = select_repo(paths)?; - OpenAction::new(project).update(vec![path]) + let path = select_repo(project, None)?; + OpenAction::new(project).update(path) } else { - OpenAction::new(project).update(paths) + OpenAction::new(project).update( + paths + .get(0) + .ok_or(Error::Devmode(DevmodeError::PathNotFound))? + .clone(), + ) } } @@ -279,7 +291,7 @@ impl Cli { fork_setup()? } else if rx.is_match(args.first().unwrap().as_bytes()) { ForkAction::parse_url(args.first().unwrap(), rx, upstream.to_string())? - } else if args.len() == 1 { + } else if args.len().eq(&1) { let options = Settings::current().ok_or(Error::Devmode(DevmodeError::AppSettingsNotFound))?; let host = Host::from(&options.host); @@ -333,164 +345,89 @@ impl Cli { Ok(()) } - fn workspace(workspace: Workspace) -> Result<(), Error> { + fn workspace(arguments: WorkspaceOptions) -> Result<(), Error> { let mut settings = Settings::current().ok_or(Error::Devmode(DevmodeError::AppSettingsNotFound))?; - if let Some(name) = workspace.name { - if settings.workspaces.names.contains(&name) { - let index = settings - .workspaces - .names + let Some(ref workspace_name) = arguments.name else { + let workspaces = settings.workspaces.names; + println!("Currently available workspaces: {workspaces:?}"); + return Ok(()); + }; + let mut workspace = Workspace::new(&workspace_name); + if settings.workspaces.names.contains(workspace_name) { + if arguments.delete { + workspace.delete()?; + println!("Workspace {workspace_name} was successfully deleted."); + } else if let Some(ref to) = arguments.rename { + workspace.rename(to)?; + println!("Workspace renamed from {workspace_name} to {to}."); + } else if let Some(ref project) = arguments.include { + let paths: Vec = find_paths(project)? .iter() - .position(|ws| *ws == name) - .unwrap(); - if workspace.delete { - let dev = home().join("Developer"); - for provider in fs::read_dir(dev)? { - for user in fs::read_dir(provider?.path())? { - let user = user?; - for repo_or_workspace in fs::read_dir(user.path())? { - let repo_or_workspace = repo_or_workspace?; - let repo_name = - repo_or_workspace.file_name().to_str().unwrap().to_string(); - if settings.workspaces.names.contains(&repo_name) - && repo_name.eq(&name) - { - for repo in fs::read_dir(repo_or_workspace.path())? { - let repo = repo?; - fs_extra::dir::move_dir( - repo.path(), - user.path(), - &Default::default(), - )?; - } - fs::remove_dir_all(repo_or_workspace.path())?; - } - } - } - } - settings.workspaces.names.remove(index); - settings.write(true)?; - println!("Workspace {name} was successfully deleted.") - } else if workspace.rename.is_some() { - let dev = home().join("Developer"); - for provider in fs::read_dir(dev)? { - for user in fs::read_dir(provider?.path())? { - let user = user?; - for repo_or_workspace in fs::read_dir(user.path())? { - let repo_or_workspace = repo_or_workspace?; - let name = - repo_or_workspace.file_name().to_str().unwrap().to_string(); - if settings.workspaces.names.contains(&name) { - fs::rename( - repo_or_workspace.path(), - repo_or_workspace - .path() - .parent() - .unwrap() - .join(workspace.rename.clone().unwrap()), - )?; - } - } - } - } - *settings.workspaces.names.get_mut(index).unwrap() = - workspace.rename.clone().unwrap(); - settings.write(true)?; - println!( - "Workspace renamed from {name} to {}.", - workspace.rename.unwrap() - ); - } else if let Some(include) = workspace.include { - let reader = create_paths_reader()?; - let paths: Vec = find_paths(reader, &include)? - .iter() - .map(|path| path.to_owned()) - .filter(|path| !path.contains(name.as_str())) - .collect(); - let path = if paths.is_empty() { - return devmode::error("Could not locate the {add} repository."); - } else if paths.len() > 1 { - let paths: Vec<&str> = paths.iter().map(|s| s as &str).collect(); - select_repo(paths)? - } else { - paths[0].clone() - }; - let mut options = dir::CopyOptions::new(); - let to = PathBuf::from(&path).parent().unwrap().join(&name); - if to.join(include.as_str()).exists() { - let question = requestty::Question::confirm("overwrite") - .message("We found an existing repository with the same name, do you want to overwrite the existing repository?") - .build(); - let answer = requestty::prompt_one(question)?; - if let Answer::Bool(overwrite) = answer { - if overwrite { - options.overwrite = true; - move_items(&[path], to, &options)?; - } - } - } else { - move_items(&[path], to, &options)?; - } - } else if let Some(remove) = workspace.remove { - let reader = create_paths_reader()?; - let paths: Vec = find_paths(reader, &remove)? - .iter() - .map(|path| path.to_owned()) - .filter(|path| path.contains(name.as_str())) - .collect(); - let path = if paths.is_empty() { - return devmode::error( - "Could not locate the {remove} repository inside {name}", - ); - } else if paths.len() > 1 { - let paths: Vec<&str> = paths.iter().map(|s| s as &str).collect(); - select_repo(paths)? - } else { - paths[0].clone() - }; - let mut options = dir::CopyOptions::new(); - let path = PathBuf::from(&path); - let to = path.parent().unwrap().parent().unwrap(); - if to.join(remove.as_str()).exists() { - let question = requestty::Question::confirm("overwrite") - .message("We found an existing repository with the same name, do you want to overwrite the existing repository?") - .build(); - let answer = requestty::prompt_one(question)?; - if let Answer::Bool(overwrite) = answer { - if overwrite { - options.overwrite = true; - move_items(&[path.clone()], to, &options)?; - } - } - } else { - move_items(&[path.clone()], to, &options)?; - } + .filter(|path| !path.display().to_string().contains(workspace_name)) + .map(PathBuf::from) + .collect(); + let project = if paths.len() > 0 { + select_repo(project, Some(workspace_name))? } else { - println!("Workspace `{name}` found."); + paths + .get(0) + .ok_or(Error::Devmode(DevmodeError::ProjectNotFound))? + .clone() + }; + let mut options = CopyOptions::new(); + let destination = project + .parent() + .ok_or(Error::Devmode(DevmodeError::PathNotFound))? + .join(&workspace_name); + + if destination.exists() { + options.overwrite = overwrite()?; } - } else if workspace.delete || workspace.rename.is_some() { - return devmode::error("Couldn't find a workspace that matches {name}."); - } else if workspace.add { - settings.workspaces.names.push(name.clone()); - settings.write(true)?; - println!("Workspace {name} was added.") - } else { - let question = requestty::Question::confirm("workspace") - .message("Would you like to create this workspace?") - .build(); - let answer = requestty::prompt_one(question)?; - if let Answer::Bool(create) = answer { - if create { - settings.workspaces.names.push(name.clone()); - settings.write(true)?; - println!("Workspace {name} was added.") - } + + move_items(&[project], destination, &options)?; + } else if let Some(ref project_name) = arguments.remove { + let paths: Vec = find_paths(project_name)? + .iter() + .filter(|path| !path.display().to_string().contains(workspace_name)) + .map(PathBuf::from) + .collect(); + let project = if paths.len() > 0 { + select_repo(project_name, Some(workspace_name))? + } else { + paths + .get(0) + .ok_or(Error::Devmode(DevmodeError::ProjectNotFound))? + .clone() + }; + let mut options = dir::CopyOptions::new(); + let to = project + .parent() + .ok_or(Error::Devmode(DevmodeError::PathNotFound))? + .parent() + .ok_or(Error::Devmode(DevmodeError::PathNotFound))?; + + if to.join(&project).exists() { + options.overwrite = overwrite()?; } + + move_items(&[project.clone()], to, &options)?; + } else { + println!("Workspace `{workspace_name}` found."); + } + } else if arguments.delete || arguments.rename.is_some() { + return devmode::error("Couldn't find a workspace that matches {name}."); + } else if arguments.add { + settings.workspaces.names.push(workspace_name.clone()); + settings.write(true)?; + println!("Workspace {workspace_name} was added.") + } else { + let create = create_workspace()?; + if create { + settings.workspaces.names.push(workspace_name.clone()); + settings.write(true)?; + println!("Workspace {workspace_name} was added.") } - } else if workspace.list { - let workspaces = settings.workspaces.names; - println!("Currently available workspaces: {workspaces:?}",); } Ok(()) } diff --git a/src/cli/src/input.rs b/src/cli/src/input.rs index 639fe43..28e8fc8 100644 --- a/src/cli/src/input.rs +++ b/src/cli/src/input.rs @@ -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 { - 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 { + 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 { + 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>) -> Result { + 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 { 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()); @@ -51,32 +73,24 @@ pub fn clone_setup() -> Result { .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 { 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) } @@ -91,69 +105,34 @@ pub fn config_all() -> Result { } pub fn config_owner() -> Result { - 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 { - 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 { - 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 { @@ -169,37 +148,29 @@ pub fn config_editor() -> Result { Ok(settings) } -pub fn select_repo(paths: Vec<&str>) -> Result { - 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 { + 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::>() + } else { + find_paths(project)? + .iter() + .map(|path| path.display().to_string().to_owned()) + .collect::>() }; - 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 { - 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 { + let create = confirm("workspace", "Would you like to create this workspace?")?; + Ok(create) } -pub fn pick(key: &str, message: &str, options: Vec<&str>) -> Result { - requestty::prompt_one( - Question::select(key) - .message(message) - .choices(options) - .build(), - ) - .map_err(|e| Error::String(e.to_string())) +pub fn overwrite() -> Result { + let overwrite = confirm("overwrite", "Found existing repository, overwrite it?")?; + Ok(overwrite) } diff --git a/src/shared/application.rs b/src/shared/application.rs index bc691d6..16fdc10 100644 --- a/src/shared/application.rs +++ b/src/shared/application.rs @@ -1,4 +1,5 @@ use std::fmt::{Display, Formatter}; +use std::path::PathBuf; use std::process::Command; use crate::{DevmodeStatus, Error}; @@ -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") { diff --git a/src/shared/error.rs b/src/shared/error.rs index a8b8348..09747c5 100644 --- a/src/shared/error.rs +++ b/src/shared/error.rs @@ -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(msg: &'static str) -> Result { @@ -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, } diff --git a/src/shared/git_pull.rs b/src/shared/git_pull.rs index f125aa9..c37a269 100644 --- a/src/shared/git_pull.rs +++ b/src/shared/git_pull.rs @@ -115,7 +115,9 @@ fn fast_forward( fn get_branch(repo: &Repository) -> Result { 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)), @@ -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(), @@ -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 = '?'; } '?' @@ -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 = ""; @@ -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!( "?? {}", diff --git a/src/shared/host.rs b/src/shared/host.rs index 3e4ba9d..4aca8ce 100644 --- a/src/shared/host.rs +++ b/src/shared/host.rs @@ -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 diff --git a/src/shared/project.rs b/src/shared/project.rs index 0140a65..4bbef67 100644 --- a/src/shared/project.rs +++ b/src/shared/project.rs @@ -1,7 +1,7 @@ use std::fs::{create_dir_all, File, OpenOptions}; use std::io::Write; use std::io::{BufRead, BufReader}; -use std::path::Path; +use std::path::PathBuf; use std::process::Command; use cmd_lib::*; @@ -24,12 +24,12 @@ impl OpenAction { } } - pub fn open(&self, paths: Vec) -> Result<(), Error> { + pub fn open(&self, paths: PathBuf) -> Result<(), Error> { open_project(paths) } - pub fn update(&self, paths: Vec) -> Result<(), Error> { - update_project(&self.name, paths) + pub fn update(&self, path: PathBuf) -> Result<(), Error> { + update_project(&self.name, path) } pub fn make_dev_paths() -> Result<(), Error> { @@ -70,8 +70,8 @@ impl OpenAction { }) .last() .unwrap(); - if (entry.depth() == 3 && !settings.workspaces.names.contains(&repo.to_string())) - || (entry.depth() == 4 + if (entry.depth().eq(&3) && !settings.workspaces.names.contains(&repo.to_string())) + || (entry.depth().eq(&4) && settings.workspaces.names.contains(&workspace.to_string())) && entry.path().is_dir() { @@ -85,13 +85,12 @@ impl OpenAction { } } -pub fn open_project(paths: Vec) -> Result<(), Error> { - let path = &paths[0]; +pub fn open_project(path: PathBuf) -> Result<(), Error> { let options = Settings::current().ok_or(Error::Devmode(DevmodeError::AppSettingsNotFound))?; - println!("Opening {} in {}...", path, options.editor.app.to_string(),); + let route = path.display().to_string(); + println!("Opening {} in {}...", route, options.editor.app.to_string(),); if let Application::Custom = options.editor.app { let command_editor = options.editor.command; - let route = path.replace('\\', "/"); if cfg!(target_os = "windows") { Command::new("cmd") .args(["/C", format!("{command_editor} {route}").as_str()]) @@ -105,13 +104,13 @@ pub fn open_project(paths: Vec) -> Result<(), Error> { Ok(()) } -pub fn update_project(name: &str, paths: Vec) -> Result<(), Error> { +pub fn update_project(name: &str, path: PathBuf) -> Result<(), Error> { crate::report(DevmodeStatus::RepositoryUpdated(name.to_string())); - let path = &paths[0]; - git_pull::pull(Path::new(path)) + git_pull::pull(path.as_path()) } -pub fn find_paths(reader: BufReader, path: &str) -> Result, Error> { +pub fn find_paths(path: &str) -> Result, Error> { + let reader = BufReader::new(File::open(data().join("devmode/devpaths"))?); let paths = reader .lines() .filter_map(|e| { @@ -123,16 +122,12 @@ pub fn find_paths(reader: BufReader, path: &str) -> Result, Er "/" }) .collect(); - if split.last().unwrap() == &path { - return Some(line); + if split.last().unwrap().eq(&path) { + return Some(PathBuf::from(line)); } } None }) - .collect::>(); + .collect::>(); Ok(paths) } - -pub fn create_paths_reader() -> Result, Error> { - Ok(BufReader::new(File::open(data().join("devmode/devpaths"))?)) -} diff --git a/src/shared/workspace.rs b/src/shared/workspace.rs index ee3aa1b..c96af1f 100644 --- a/src/shared/workspace.rs +++ b/src/shared/workspace.rs @@ -1,5 +1,9 @@ +use libset::routes::home; + +use super::{settings::Settings, DevmodeError, Error}; + #[derive(Debug)] -pub struct Workspace { +pub struct WorkspaceOptions { pub name: Option, pub add: bool, pub delete: bool, @@ -8,3 +12,81 @@ pub struct Workspace { pub remove: Option, pub list: bool, } + +pub struct Workspace { + name: String, + index: usize, + settings: Settings, +} + +impl Workspace { + pub fn new(name: &str) -> Self { + let settings = Settings::current().unwrap_or_default(); + let index = settings + .workspaces + .names + .iter() + .position(|ws| ws.eq(name)) + .unwrap(); + Self { + name: name.to_string(), + index, + settings, + } + } + + pub fn delete(&mut self) -> Result<(), Error> { + let dev = home().join("Developer"); + for provider in std::fs::read_dir(dev)? { + for user in std::fs::read_dir(provider?.path())? { + let user = user?; + for repo_or_workspace in std::fs::read_dir(user.path())? { + let repo_or_workspace = repo_or_workspace?; + let repo_name = repo_or_workspace.file_name().to_str().unwrap().to_string(); + if self.settings.workspaces.names.contains(&repo_name) + && repo_name.eq(&self.name) + { + for repo in std::fs::read_dir(repo_or_workspace.path())? { + let repo = repo?; + fs_extra::dir::move_dir(repo.path(), user.path(), &Default::default())?; + } + std::fs::remove_dir_all(repo_or_workspace.path())?; + } + } + } + } + self.settings.workspaces.names.remove(self.index); + self.settings.write(true)?; + Ok(()) + } + + pub fn rename(&mut self, rename: &str) -> Result<(), Error> { + let dev = home().join("Developer"); + for provider in std::fs::read_dir(dev)? { + for user in std::fs::read_dir(provider?.path())? { + let user = user?; + for repo_or_workspace in std::fs::read_dir(user.path())? { + let repo_or_workspace = repo_or_workspace?; + let name = repo_or_workspace.file_name().to_str().unwrap().to_string(); + if self.settings.workspaces.names.contains(&name) { + std::fs::rename( + repo_or_workspace.path(), + repo_or_workspace.path().parent().unwrap().join(rename), + )?; + } + } + } + } + + let name = self + .settings + .workspaces + .names + .get_mut(self.index) + .ok_or(Error::Devmode(DevmodeError::WorkspaceMissing))?; + *name = rename.to_string(); + self.settings.write(true)?; + + Ok(()) + } +} diff --git a/src/ui/src/app.rs b/src/ui/src/app.rs index ec74869..cc64e91 100644 --- a/src/ui/src/app.rs +++ b/src/ui/src/app.rs @@ -207,7 +207,7 @@ impl Application for Devmode { let _result = open::that_detached(url); } Message::ToggleContextPage(context_page) => { - if self.context_page == context_page { + if self.context_page.eq(&context_page) { // Close the context drawer if the toggled context page is the same. self.core.window.show_context = !self.core.window.show_context; } else {