From d2d0c95f9f01b10e3e3557b4d1e7ce9e92fd34a3 Mon Sep 17 00:00:00 2001 From: Patrick Haun Date: Fri, 1 Dec 2023 10:02:33 +0100 Subject: [PATCH] use default indent --- .editorconfig | 9 +- rustfmt.toml | 2 +- src/app/mod.rs | 28 +- src/config/metadata_from_repository.rs | 2 +- src/config/mod.rs | 780 ++++++++++++------------- src/config/path.rs | 116 ++-- src/config/project.rs | 62 +- src/config/settings.rs | 94 +-- src/errors/mod.rs | 106 ++-- src/git/mod.rs | 246 ++++---- src/intellij/mod.rs | 86 +-- src/main.rs | 390 ++++++------- src/project/mod.rs | 324 +++++----- src/projectile/mod.rs | 90 +-- src/setup/mod.rs | 396 ++++++------- src/shell/mod.rs | 78 +-- src/spawn/mod.rs | 174 +++--- src/sync/mod.rs | 198 +++---- src/tag/mod.rs | 264 ++++----- src/util/mod.rs | 28 +- src/workon/mod.rs | 74 +-- src/ws/github/mod.rs | 164 +++--- 22 files changed, 1855 insertions(+), 1856 deletions(-) diff --git a/.editorconfig b/.editorconfig index 1f546b6e..83abc93e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,8 +1,7 @@ root = true -[*] -charset = utf-8 -end_of_line = lf -indent_size = 2 +[*.rs] indent_style = space -trim_trailing_whitespace = true +indent_size = 4 +max_line_length = 160 + diff --git a/rustfmt.toml b/rustfmt.toml index 77ba1656..cc6164ae 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ -tab_spaces = 2 +tab_spaces = 4 max_width = 160 use_try_shorthand = true reorder_imports = true diff --git a/src/app/mod.rs b/src/app/mod.rs index e49d015b..9abb74a5 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3,21 +3,21 @@ use clap::{builder::EnumValueParser, crate_version, value_parser, Arg, ArgAction use crate::setup::ProjectState; pub fn app() -> Command { - let arg_with_fzf = Arg::new("with-fzf") - .long("with-fzf") - .short('f') - .num_args(0) - .action(ArgAction::SetTrue) - .help("Integrate with fzf"); - let arg_with_skim = Arg::new("with-skim") - .long("with-skim") - .short('s') - .help("Integrate with skim") - .conflicts_with("with-fzf") - .action(ArgAction::SetTrue) - .num_args(0); + let arg_with_fzf = Arg::new("with-fzf") + .long("with-fzf") + .short('f') + .num_args(0) + .action(ArgAction::SetTrue) + .help("Integrate with fzf"); + let arg_with_skim = Arg::new("with-skim") + .long("with-skim") + .short('s') + .help("Integrate with skim") + .conflicts_with("with-fzf") + .action(ArgAction::SetTrue) + .num_args(0); - Command::new("fw") + Command::new("fw") .version(crate_version!()) .author("Brocode ") .about( diff --git a/src/config/metadata_from_repository.rs b/src/config/metadata_from_repository.rs index 3f53bd8f..43b15e4f 100644 --- a/src/config/metadata_from_repository.rs +++ b/src/config/metadata_from_repository.rs @@ -4,5 +4,5 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct MetadataFromRepository { - pub tags: Option>, + pub tags: Option>, } diff --git a/src/config/mod.rs b/src/config/mod.rs index f16b4334..2308f02f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -19,440 +19,440 @@ use settings::{PersistedSettings, Settings, Tag}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Config { - pub projects: BTreeMap, - pub settings: Settings, + pub projects: BTreeMap, + pub settings: Settings, } pub fn read_config() -> Result { - let paths = fw_path()?; - - let settings_raw = read_to_string(&paths.settings) - .map_err(|e| AppError::RuntimeError(format!("Could not read settings file ({}): {}", paths.settings.to_string_lossy(), e)))?; - - let settings: PersistedSettings = toml::from_str(&settings_raw)?; - - let mut projects: BTreeMap = BTreeMap::new(); - if paths.projects.exists() { - for maybe_project_file in WalkDir::new(&paths.projects).follow_links(true) { - let project_file = maybe_project_file?; - if project_file.metadata()?.is_file() && !project_file.file_name().to_os_string().eq(".DS_Store") { - let raw_project = read_to_string(project_file.path())?; - let mut project: Project = match toml::from_str(&raw_project) { - o @ Ok(_) => o, - e @ Err(_) => { - eprintln!("There is an issue in your config for project {}", project_file.file_name().to_string_lossy()); - e - } - }?; - - project.name = project_file - .file_name() - .to_str() - .map(ToOwned::to_owned) - .ok_or(AppError::InternalError("Failed to get project name"))?; - project.project_config_path = PathBuf::from(project_file.path().parent().ok_or(AppError::InternalError("Expected file to have a parent"))?) - .strip_prefix(paths.projects.as_path()) - .map_err(|e| AppError::RuntimeError(format!("Failed to strip prefix: {}", e)))? - .to_string_lossy() - .to_string(); - if projects.contains_key(&project.name) { - eprintln!( - "Inconsistency found: project {} defined more than once. Will use the project that is found last. Results might be inconsistent.", - project.name - ); + let paths = fw_path()?; + + let settings_raw = read_to_string(&paths.settings) + .map_err(|e| AppError::RuntimeError(format!("Could not read settings file ({}): {}", paths.settings.to_string_lossy(), e)))?; + + let settings: PersistedSettings = toml::from_str(&settings_raw)?; + + let mut projects: BTreeMap = BTreeMap::new(); + if paths.projects.exists() { + for maybe_project_file in WalkDir::new(&paths.projects).follow_links(true) { + let project_file = maybe_project_file?; + if project_file.metadata()?.is_file() && !project_file.file_name().to_os_string().eq(".DS_Store") { + let raw_project = read_to_string(project_file.path())?; + let mut project: Project = match toml::from_str(&raw_project) { + o @ Ok(_) => o, + e @ Err(_) => { + eprintln!("There is an issue in your config for project {}", project_file.file_name().to_string_lossy()); + e + } + }?; + + project.name = project_file + .file_name() + .to_str() + .map(ToOwned::to_owned) + .ok_or(AppError::InternalError("Failed to get project name"))?; + project.project_config_path = PathBuf::from(project_file.path().parent().ok_or(AppError::InternalError("Expected file to have a parent"))?) + .strip_prefix(paths.projects.as_path()) + .map_err(|e| AppError::RuntimeError(format!("Failed to strip prefix: {}", e)))? + .to_string_lossy() + .to_string(); + if projects.contains_key(&project.name) { + eprintln!( + "Inconsistency found: project {} defined more than once. Will use the project that is found last. Results might be inconsistent.", + project.name + ); + } + projects.insert(project.name.clone(), project); + } } - projects.insert(project.name.clone(), project); - } } - } - - let mut tags: BTreeMap = BTreeMap::new(); - if paths.tags.exists() { - for maybe_tag_file in WalkDir::new(&paths.tags).follow_links(true) { - let tag_file = maybe_tag_file?; - - if tag_file.metadata()?.is_file() && !tag_file.file_name().to_os_string().eq(".DS_Store") { - let raw_tag = read_to_string(tag_file.path())?; - let mut tag: Tag = toml::from_str(&raw_tag)?; - let tag_name: String = tag_file - .file_name() - .to_str() - .map(ToOwned::to_owned) - .ok_or(AppError::InternalError("Failed to get tag name"))?; - tag.tag_config_path = PathBuf::from(tag_file.path().parent().ok_or(AppError::InternalError("Expected file to have a parent"))?) - .strip_prefix(paths.tags.as_path()) - .map_err(|e| AppError::RuntimeError(format!("Failed to strip prefix: {}", e)))? - .to_string_lossy() - .to_string(); - if tags.contains_key(&tag_name) { - eprintln!( - "Inconsistency found: tag {} defined more than once. Will use the project that is found last. Results might be inconsistent.", - tag_name - ); + + let mut tags: BTreeMap = BTreeMap::new(); + if paths.tags.exists() { + for maybe_tag_file in WalkDir::new(&paths.tags).follow_links(true) { + let tag_file = maybe_tag_file?; + + if tag_file.metadata()?.is_file() && !tag_file.file_name().to_os_string().eq(".DS_Store") { + let raw_tag = read_to_string(tag_file.path())?; + let mut tag: Tag = toml::from_str(&raw_tag)?; + let tag_name: String = tag_file + .file_name() + .to_str() + .map(ToOwned::to_owned) + .ok_or(AppError::InternalError("Failed to get tag name"))?; + tag.tag_config_path = PathBuf::from(tag_file.path().parent().ok_or(AppError::InternalError("Expected file to have a parent"))?) + .strip_prefix(paths.tags.as_path()) + .map_err(|e| AppError::RuntimeError(format!("Failed to strip prefix: {}", e)))? + .to_string_lossy() + .to_string(); + if tags.contains_key(&tag_name) { + eprintln!( + "Inconsistency found: tag {} defined more than once. Will use the project that is found last. Results might be inconsistent.", + tag_name + ); + } + tags.insert(tag_name, tag); + } } - tags.insert(tag_name, tag); - } } - } - - let default_tags: BTreeSet = tags - .iter() - .filter(|(_, value)| value.default.unwrap_or_default()) - .map(|(key, _)| key.to_string()) - .collect(); - - Ok(Config { - projects, - settings: Settings { - tags: Some(tags), - workspace: settings.workspace, - shell: settings.shell, - default_after_workon: settings.default_after_workon, - default_after_clone: settings.default_after_clone, - default_tags: Some(default_tags), - github_token: settings.github_token, - gitlab: settings.gitlab, - }, - }) + + let default_tags: BTreeSet = tags + .iter() + .filter(|(_, value)| value.default.unwrap_or_default()) + .map(|(key, _)| key.to_string()) + .collect(); + + Ok(Config { + projects, + settings: Settings { + tags: Some(tags), + workspace: settings.workspace, + shell: settings.shell, + default_after_workon: settings.default_after_workon, + default_after_clone: settings.default_after_clone, + default_tags: Some(default_tags), + github_token: settings.github_token, + gitlab: settings.gitlab, + }, + }) } pub fn write_settings(settings: &PersistedSettings) -> Result<(), AppError> { - let paths = fw_path()?; - paths.ensure_base_exists()?; + let paths = fw_path()?; + paths.ensure_base_exists()?; - let mut buffer = File::create(&paths.settings)?; - let serialized = toml::to_string_pretty(settings)?; - write!(buffer, "{}", serialized)?; - write_example(&mut buffer, PersistedSettings::example())?; + let mut buffer = File::create(&paths.settings)?; + let serialized = toml::to_string_pretty(settings)?; + write!(buffer, "{}", serialized)?; + write_example(&mut buffer, PersistedSettings::example())?; - Ok(()) + Ok(()) } pub fn write_tag(tag_name: &str, tag: &Tag) -> Result<(), AppError> { - let paths = fw_path()?; - paths.ensure_base_exists()?; - - let mut tag_path = paths.tags; - tag_path.push(PathBuf::from(&tag.tag_config_path)); - std::fs::create_dir_all(&tag_path) - .map_err(|e| AppError::RuntimeError(format!("Failed to create tag config path '{}'. {}", tag_path.to_string_lossy(), e)))?; - - let mut tag_file_path = tag_path; - tag_file_path.push(tag_name); - - let mut buffer = File::create(&tag_file_path) - .map_err(|e| AppError::RuntimeError(format!("Failed to create project config file '{}'. {}", tag_file_path.to_string_lossy(), e)))?; - let serialized = toml::to_string_pretty(&tag)?; - write!(buffer, "{}", CONF_MODE_HEADER)?; - write!(buffer, "{}", serialized)?; - write_example(&mut buffer, Tag::example())?; - Ok(()) + let paths = fw_path()?; + paths.ensure_base_exists()?; + + let mut tag_path = paths.tags; + tag_path.push(PathBuf::from(&tag.tag_config_path)); + std::fs::create_dir_all(&tag_path) + .map_err(|e| AppError::RuntimeError(format!("Failed to create tag config path '{}'. {}", tag_path.to_string_lossy(), e)))?; + + let mut tag_file_path = tag_path; + tag_file_path.push(tag_name); + + let mut buffer = File::create(&tag_file_path) + .map_err(|e| AppError::RuntimeError(format!("Failed to create project config file '{}'. {}", tag_file_path.to_string_lossy(), e)))?; + let serialized = toml::to_string_pretty(&tag)?; + write!(buffer, "{}", CONF_MODE_HEADER)?; + write!(buffer, "{}", serialized)?; + write_example(&mut buffer, Tag::example())?; + Ok(()) } pub fn delete_tag_config(tag_name: &str, tag: &Tag) -> Result<(), AppError> { - let paths = fw_path()?; - paths.ensure_base_exists()?; + let paths = fw_path()?; + paths.ensure_base_exists()?; - let mut tag_file_path = paths.tags; - tag_file_path.push(PathBuf::from(&tag.tag_config_path)); - tag_file_path.push(tag_name); + let mut tag_file_path = paths.tags; + tag_file_path.push(PathBuf::from(&tag.tag_config_path)); + tag_file_path.push(tag_name); - fs::remove_file(&tag_file_path).map_err(|e| AppError::RuntimeError(format!("Failed to delete tag config from '{:?}': {}", tag_file_path, e)))?; - Ok(()) + fs::remove_file(&tag_file_path).map_err(|e| AppError::RuntimeError(format!("Failed to delete tag config from '{:?}': {}", tag_file_path, e)))?; + Ok(()) } pub fn delete_project_config(project: &Project) -> Result<(), AppError> { - let paths = fw_path()?; - paths.ensure_base_exists()?; + let paths = fw_path()?; + paths.ensure_base_exists()?; - let mut project_file_path = paths.projects; - project_file_path.push(PathBuf::from(&project.project_config_path)); - project_file_path.push(&project.name); + let mut project_file_path = paths.projects; + project_file_path.push(PathBuf::from(&project.project_config_path)); + project_file_path.push(&project.name); - fs::remove_file(project_file_path).map_err(|e| AppError::RuntimeError(format!("Failed to delete project config: {}", e)))?; - Ok(()) + fs::remove_file(project_file_path).map_err(|e| AppError::RuntimeError(format!("Failed to delete project config: {}", e)))?; + Ok(()) } fn write_example(buffer: &mut File, example: T) -> Result<(), AppError> where - T: serde::Serialize, + T: serde::Serialize, { - let example_toml = toml::to_string_pretty(&example)?; - writeln!(buffer, "\n# Example:")?; - for line in example_toml.split('\n') { - if line.trim() != "" { - writeln!(buffer, "# {}", line)?; + let example_toml = toml::to_string_pretty(&example)?; + writeln!(buffer, "\n# Example:")?; + for line in example_toml.split('\n') { + if line.trim() != "" { + writeln!(buffer, "# {}", line)?; + } } - } - Ok(()) + Ok(()) } pub fn write_project(project: &Project) -> Result<(), AppError> { - let paths = fw_path()?; - paths.ensure_base_exists()?; + let paths = fw_path()?; + paths.ensure_base_exists()?; - let mut project_path = paths.projects; - project_path.push(PathBuf::from(&project.project_config_path)); - std::fs::create_dir_all(&project_path) - .map_err(|e| AppError::RuntimeError(format!("Failed to create project config path '{}'. {}", project_path.to_string_lossy(), e)))?; + let mut project_path = paths.projects; + project_path.push(PathBuf::from(&project.project_config_path)); + std::fs::create_dir_all(&project_path) + .map_err(|e| AppError::RuntimeError(format!("Failed to create project config path '{}'. {}", project_path.to_string_lossy(), e)))?; - let mut project_file_path = project_path; - project_file_path.push(&project.name); + let mut project_file_path = project_path; + project_file_path.push(&project.name); - let mut buffer: File = File::create(&project_file_path) - .map_err(|e| AppError::RuntimeError(format!("Failed to create project config file '{}'. {}", project_file_path.to_string_lossy(), e)))?; - let serialized = toml::to_string_pretty(&project)?; + let mut buffer: File = File::create(&project_file_path) + .map_err(|e| AppError::RuntimeError(format!("Failed to create project config file '{}'. {}", project_file_path.to_string_lossy(), e)))?; + let serialized = toml::to_string_pretty(&project)?; - write!(buffer, "{}", CONF_MODE_HEADER)?; - write!(buffer, "{}", serialized)?; - write_example(&mut buffer, Project::example())?; - Ok(()) + write!(buffer, "{}", CONF_MODE_HEADER)?; + write!(buffer, "{}", serialized)?; + write_example(&mut buffer, Project::example())?; + Ok(()) } impl Config { - pub fn actual_path_to_project(&self, project: &Project) -> PathBuf { - let path = project - .override_path - .clone() - .map(PathBuf::from) - .unwrap_or_else(|| Path::new(self.resolve_workspace(project).as_str()).join(project.name.as_str())); - expand_path(path) - } - - fn resolve_workspace(&self, project: &Project) -> String { - let mut x = self.resolve_from_tags(|tag| tag.workspace.clone(), project.tags.clone()); - - x.pop().unwrap_or_else(|| self.settings.workspace.clone()) - } - pub fn resolve_after_clone(&self, project: &Project) -> Vec { - let mut commands: Vec = vec![]; - commands.extend_from_slice(&self.resolve_after_clone_from_tags(project.tags.clone())); - let commands_from_project: Vec = project.after_clone.clone().into_iter().collect(); - commands.extend_from_slice(&commands_from_project); - commands - } - pub fn resolve_after_workon(&self, project: &Project) -> Vec { - let mut commands: Vec = vec![]; - commands.extend_from_slice(&self.resolve_workon_from_tags(project.tags.clone())); - let commands_from_project: Vec = project.after_workon.clone().into_iter().collect(); - commands.extend_from_slice(&commands_from_project); - commands - } - - fn resolve_workon_from_tags(&self, maybe_tags: Option>) -> Vec { - self.resolve_from_tags(|t| t.clone().after_workon, maybe_tags) - } - fn resolve_after_clone_from_tags(&self, maybe_tags: Option>) -> Vec { - self.resolve_from_tags(|t| t.clone().after_clone, maybe_tags) - } - - fn tag_priority_or_fallback(&self, tag: &Tag) -> u8 { - tag.priority.unwrap_or(50) - } - - fn resolve_from_tags(&self, resolver: F, maybe_tags: Option>) -> Vec - where - F: Fn(&Tag) -> Option, - { - if let (Some(tags), Some(settings_tags)) = (maybe_tags, self.clone().settings.tags) { - let mut resolved_with_priority: Vec<(String, u8)> = tags - .iter() - .flat_map(|t| match settings_tags.get(t) { - None => { - eprintln!("Ignoring tag since it was not found in the config. missing_tag {}", t.clone()); - None - } - Some(actual_tag) => resolver(actual_tag).map(|val| (val, self.tag_priority_or_fallback(actual_tag))), - }) - .collect(); - resolved_with_priority.sort_by_key(|resolved_and_priority| resolved_and_priority.1); - resolved_with_priority.into_iter().map(|r| r.0).collect() - } else { - vec![] + pub fn actual_path_to_project(&self, project: &Project) -> PathBuf { + let path = project + .override_path + .clone() + .map(PathBuf::from) + .unwrap_or_else(|| Path::new(self.resolve_workspace(project).as_str()).join(project.name.as_str())); + expand_path(path) + } + + fn resolve_workspace(&self, project: &Project) -> String { + let mut x = self.resolve_from_tags(|tag| tag.workspace.clone(), project.tags.clone()); + + x.pop().unwrap_or_else(|| self.settings.workspace.clone()) + } + pub fn resolve_after_clone(&self, project: &Project) -> Vec { + let mut commands: Vec = vec![]; + commands.extend_from_slice(&self.resolve_after_clone_from_tags(project.tags.clone())); + let commands_from_project: Vec = project.after_clone.clone().into_iter().collect(); + commands.extend_from_slice(&commands_from_project); + commands + } + pub fn resolve_after_workon(&self, project: &Project) -> Vec { + let mut commands: Vec = vec![]; + commands.extend_from_slice(&self.resolve_workon_from_tags(project.tags.clone())); + let commands_from_project: Vec = project.after_workon.clone().into_iter().collect(); + commands.extend_from_slice(&commands_from_project); + commands + } + + fn resolve_workon_from_tags(&self, maybe_tags: Option>) -> Vec { + self.resolve_from_tags(|t| t.clone().after_workon, maybe_tags) + } + fn resolve_after_clone_from_tags(&self, maybe_tags: Option>) -> Vec { + self.resolve_from_tags(|t| t.clone().after_clone, maybe_tags) + } + + fn tag_priority_or_fallback(&self, tag: &Tag) -> u8 { + tag.priority.unwrap_or(50) + } + + fn resolve_from_tags(&self, resolver: F, maybe_tags: Option>) -> Vec + where + F: Fn(&Tag) -> Option, + { + if let (Some(tags), Some(settings_tags)) = (maybe_tags, self.clone().settings.tags) { + let mut resolved_with_priority: Vec<(String, u8)> = tags + .iter() + .flat_map(|t| match settings_tags.get(t) { + None => { + eprintln!("Ignoring tag since it was not found in the config. missing_tag {}", t.clone()); + None + } + Some(actual_tag) => resolver(actual_tag).map(|val| (val, self.tag_priority_or_fallback(actual_tag))), + }) + .collect(); + resolved_with_priority.sort_by_key(|resolved_and_priority| resolved_and_priority.1); + resolved_with_priority.into_iter().map(|r| r.0).collect() + } else { + vec![] + } } - } } #[cfg(test)] mod tests { - use super::*; - use maplit::btreeset; - - #[test] - fn test_workon_from_tags() { - let config = a_config(); - let resolved = config.resolve_after_workon(config.projects.get("test1").unwrap()); - assert_eq!(resolved, vec!["workon1".to_string(), "workon2".to_string()]); - } - #[test] - fn test_workon_from_tags_prioritized() { - let config = a_config(); - let resolved = config.resolve_after_workon(config.projects.get("test5").unwrap()); - assert_eq!(resolved, vec!["workon4".to_string(), "workon3".to_string()]); - } - #[test] - fn test_after_clone_from_tags() { - let config = a_config(); - let resolved = config.resolve_after_clone(config.projects.get("test1").unwrap()); - assert_eq!(resolved, vec!["clone1".to_string(), "clone2".to_string()]); - } - #[test] - fn test_after_clone_from_tags_prioritized() { - let config = a_config(); - let resolved = config.resolve_after_clone(config.projects.get("test5").unwrap()); - assert_eq!(resolved, vec!["clone4".to_string(), "clone3".to_string()]); - } - #[test] - fn test_workon_from_tags_missing_one_tag_graceful() { - let config = a_config(); - let resolved = config.resolve_after_workon(config.projects.get("test2").unwrap()); - assert_eq!(resolved, vec!["workon1".to_owned()]); - } - #[test] - fn test_workon_from_tags_missing_all_tags_graceful() { - let config = a_config(); - let resolved = config.resolve_after_workon(config.projects.get("test4").unwrap()); - assert_eq!(resolved, Vec::::new()); - } - #[test] - fn test_after_clone_from_tags_missing_all_tags_graceful() { - let config = a_config(); - let resolved = config.resolve_after_clone(config.projects.get("test4").unwrap()); - assert_eq!(resolved, Vec::::new()); - } - #[test] - fn test_after_clone_from_tags_missing_one_tag_graceful() { - let config = a_config(); - let resolved = config.resolve_after_clone(config.projects.get("test2").unwrap()); - assert_eq!(resolved, vec!["clone1".to_owned()]); - } - #[test] - fn test_workon_override_from_project() { - let config = a_config(); - let resolved = config.resolve_after_workon(config.projects.get("test3").unwrap()); - assert_eq!(resolved, vec!["workon1".to_string(), "workon override in project".to_owned()]); - } - #[test] - fn test_after_clone_override_from_project() { - let config = a_config(); - let resolved = config.resolve_after_clone(config.projects.get("test3").unwrap()); - assert_eq!(resolved, vec!["clone1".to_string(), "clone override in project".to_owned()]); - } - - fn a_config() -> Config { - let project = Project { - name: "test1".to_owned(), - git: "irrelevant".to_owned(), - tags: Some(btreeset!["tag1".to_owned(), "tag2".to_owned()]), - after_clone: None, - after_workon: None, - override_path: None, - additional_remotes: None, - bare: None, - trusted: false, - project_config_path: "".to_string(), - }; - let project2 = Project { - name: "test2".to_owned(), - git: "irrelevant".to_owned(), - tags: Some(btreeset!["tag1".to_owned(), "tag-does-not-exist".to_owned(),]), - after_clone: None, - after_workon: None, - override_path: None, - additional_remotes: None, - bare: None, - trusted: false, - project_config_path: "".to_string(), - }; - let project3 = Project { - name: "test3".to_owned(), - git: "irrelevant".to_owned(), - tags: Some(btreeset!["tag1".to_owned()]), - after_clone: Some("clone override in project".to_owned()), - after_workon: Some("workon override in project".to_owned()), - override_path: None, - additional_remotes: None, - bare: None, - trusted: false, - project_config_path: "".to_string(), - }; - let project4 = Project { - name: "test4".to_owned(), - git: "irrelevant".to_owned(), - tags: Some(btreeset!["tag-does-not-exist".to_owned()]), - after_clone: None, - after_workon: None, - override_path: None, - additional_remotes: None, - bare: None, - trusted: false, - project_config_path: "".to_string(), - }; - let project5 = Project { - name: "test5".to_owned(), - git: "irrelevant".to_owned(), - tags: Some(btreeset!["tag3".to_owned(), "tag4".to_owned()]), - after_clone: None, - after_workon: None, - override_path: None, - additional_remotes: None, - bare: None, - trusted: false, - project_config_path: "".to_string(), - }; - let tag1 = Tag { - after_clone: Some("clone1".to_owned()), - after_workon: Some("workon1".to_owned()), - priority: None, - workspace: None, - default: None, - tag_config_path: "".to_string(), - }; - let tag2 = Tag { - after_clone: Some("clone2".to_owned()), - after_workon: Some("workon2".to_owned()), - priority: None, - workspace: None, - default: None, - tag_config_path: "".to_string(), - }; - let tag3 = Tag { - after_clone: Some("clone3".to_owned()), - after_workon: Some("workon3".to_owned()), - priority: Some(100), - workspace: None, - default: None, - tag_config_path: "".to_string(), - }; - let tag4 = Tag { - after_clone: Some("clone4".to_owned()), - after_workon: Some("workon4".to_owned()), - priority: Some(0), - workspace: None, - default: None, - tag_config_path: "".to_string(), - }; - let mut projects: BTreeMap = BTreeMap::new(); - projects.insert("test1".to_owned(), project); - projects.insert("test2".to_owned(), project2); - projects.insert("test3".to_owned(), project3); - projects.insert("test4".to_owned(), project4); - projects.insert("test5".to_owned(), project5); - let mut tags: BTreeMap = BTreeMap::new(); - tags.insert("tag1".to_owned(), tag1); - tags.insert("tag2".to_owned(), tag2); - tags.insert("tag3".to_owned(), tag3); - tags.insert("tag4".to_owned(), tag4); - let settings = Settings { - workspace: "/test".to_owned(), - default_after_workon: None, - default_after_clone: None, - default_tags: None, - shell: None, - tags: Some(tags), - github_token: None, - gitlab: None, - }; - Config { projects, settings } - } + use super::*; + use maplit::btreeset; + + #[test] + fn test_workon_from_tags() { + let config = a_config(); + let resolved = config.resolve_after_workon(config.projects.get("test1").unwrap()); + assert_eq!(resolved, vec!["workon1".to_string(), "workon2".to_string()]); + } + #[test] + fn test_workon_from_tags_prioritized() { + let config = a_config(); + let resolved = config.resolve_after_workon(config.projects.get("test5").unwrap()); + assert_eq!(resolved, vec!["workon4".to_string(), "workon3".to_string()]); + } + #[test] + fn test_after_clone_from_tags() { + let config = a_config(); + let resolved = config.resolve_after_clone(config.projects.get("test1").unwrap()); + assert_eq!(resolved, vec!["clone1".to_string(), "clone2".to_string()]); + } + #[test] + fn test_after_clone_from_tags_prioritized() { + let config = a_config(); + let resolved = config.resolve_after_clone(config.projects.get("test5").unwrap()); + assert_eq!(resolved, vec!["clone4".to_string(), "clone3".to_string()]); + } + #[test] + fn test_workon_from_tags_missing_one_tag_graceful() { + let config = a_config(); + let resolved = config.resolve_after_workon(config.projects.get("test2").unwrap()); + assert_eq!(resolved, vec!["workon1".to_owned()]); + } + #[test] + fn test_workon_from_tags_missing_all_tags_graceful() { + let config = a_config(); + let resolved = config.resolve_after_workon(config.projects.get("test4").unwrap()); + assert_eq!(resolved, Vec::::new()); + } + #[test] + fn test_after_clone_from_tags_missing_all_tags_graceful() { + let config = a_config(); + let resolved = config.resolve_after_clone(config.projects.get("test4").unwrap()); + assert_eq!(resolved, Vec::::new()); + } + #[test] + fn test_after_clone_from_tags_missing_one_tag_graceful() { + let config = a_config(); + let resolved = config.resolve_after_clone(config.projects.get("test2").unwrap()); + assert_eq!(resolved, vec!["clone1".to_owned()]); + } + #[test] + fn test_workon_override_from_project() { + let config = a_config(); + let resolved = config.resolve_after_workon(config.projects.get("test3").unwrap()); + assert_eq!(resolved, vec!["workon1".to_string(), "workon override in project".to_owned()]); + } + #[test] + fn test_after_clone_override_from_project() { + let config = a_config(); + let resolved = config.resolve_after_clone(config.projects.get("test3").unwrap()); + assert_eq!(resolved, vec!["clone1".to_string(), "clone override in project".to_owned()]); + } + + fn a_config() -> Config { + let project = Project { + name: "test1".to_owned(), + git: "irrelevant".to_owned(), + tags: Some(btreeset!["tag1".to_owned(), "tag2".to_owned()]), + after_clone: None, + after_workon: None, + override_path: None, + additional_remotes: None, + bare: None, + trusted: false, + project_config_path: "".to_string(), + }; + let project2 = Project { + name: "test2".to_owned(), + git: "irrelevant".to_owned(), + tags: Some(btreeset!["tag1".to_owned(), "tag-does-not-exist".to_owned(),]), + after_clone: None, + after_workon: None, + override_path: None, + additional_remotes: None, + bare: None, + trusted: false, + project_config_path: "".to_string(), + }; + let project3 = Project { + name: "test3".to_owned(), + git: "irrelevant".to_owned(), + tags: Some(btreeset!["tag1".to_owned()]), + after_clone: Some("clone override in project".to_owned()), + after_workon: Some("workon override in project".to_owned()), + override_path: None, + additional_remotes: None, + bare: None, + trusted: false, + project_config_path: "".to_string(), + }; + let project4 = Project { + name: "test4".to_owned(), + git: "irrelevant".to_owned(), + tags: Some(btreeset!["tag-does-not-exist".to_owned()]), + after_clone: None, + after_workon: None, + override_path: None, + additional_remotes: None, + bare: None, + trusted: false, + project_config_path: "".to_string(), + }; + let project5 = Project { + name: "test5".to_owned(), + git: "irrelevant".to_owned(), + tags: Some(btreeset!["tag3".to_owned(), "tag4".to_owned()]), + after_clone: None, + after_workon: None, + override_path: None, + additional_remotes: None, + bare: None, + trusted: false, + project_config_path: "".to_string(), + }; + let tag1 = Tag { + after_clone: Some("clone1".to_owned()), + after_workon: Some("workon1".to_owned()), + priority: None, + workspace: None, + default: None, + tag_config_path: "".to_string(), + }; + let tag2 = Tag { + after_clone: Some("clone2".to_owned()), + after_workon: Some("workon2".to_owned()), + priority: None, + workspace: None, + default: None, + tag_config_path: "".to_string(), + }; + let tag3 = Tag { + after_clone: Some("clone3".to_owned()), + after_workon: Some("workon3".to_owned()), + priority: Some(100), + workspace: None, + default: None, + tag_config_path: "".to_string(), + }; + let tag4 = Tag { + after_clone: Some("clone4".to_owned()), + after_workon: Some("workon4".to_owned()), + priority: Some(0), + workspace: None, + default: None, + tag_config_path: "".to_string(), + }; + let mut projects: BTreeMap = BTreeMap::new(); + projects.insert("test1".to_owned(), project); + projects.insert("test2".to_owned(), project2); + projects.insert("test3".to_owned(), project3); + projects.insert("test4".to_owned(), project4); + projects.insert("test5".to_owned(), project5); + let mut tags: BTreeMap = BTreeMap::new(); + tags.insert("tag1".to_owned(), tag1); + tags.insert("tag2".to_owned(), tag2); + tags.insert("tag3".to_owned(), tag3); + tags.insert("tag4".to_owned(), tag4); + let settings = Settings { + workspace: "/test".to_owned(), + default_after_workon: None, + default_after_clone: None, + default_tags: None, + shell: None, + tags: Some(tags), + github_token: None, + gitlab: None, + }; + Config { projects, settings } + } } diff --git a/src/config/path.rs b/src/config/path.rs index 67d65b0e..ad80de2b 100644 --- a/src/config/path.rs +++ b/src/config/path.rs @@ -4,85 +4,85 @@ use std::env; use std::path::PathBuf; pub struct FwPaths { - pub settings: PathBuf, - pub base: PathBuf, - pub projects: PathBuf, - pub tags: PathBuf, + pub settings: PathBuf, + pub base: PathBuf, + pub projects: PathBuf, + pub tags: PathBuf, } impl FwPaths { - pub fn ensure_base_exists(&self) -> Result<(), AppError> { - std::fs::create_dir_all(&self.base).map_err(|e| AppError::RuntimeError(format!("Failed to create fw config base directory. {}", e)))?; - Ok(()) - } + pub fn ensure_base_exists(&self) -> Result<(), AppError> { + std::fs::create_dir_all(&self.base).map_err(|e| AppError::RuntimeError(format!("Failed to create fw config base directory. {}", e)))?; + Ok(()) + } } fn do_expand(path: PathBuf, home_dir: Option) -> PathBuf { - if let Some(home) = home_dir { - home.join(path.strip_prefix("~").expect("only doing this if path starts with ~")) - } else { - path - } + if let Some(home) = home_dir { + home.join(path.strip_prefix("~").expect("only doing this if path starts with ~")) + } else { + path + } } pub fn expand_path(path: PathBuf) -> PathBuf { - if path.starts_with("~") { - do_expand(path, dirs::home_dir()) - } else { - path - } + if path.starts_with("~") { + do_expand(path, dirs::home_dir()) + } else { + path + } } pub fn fw_path() -> Result { - let base = env::var("FW_CONFIG_DIR") - .map(PathBuf::from) - .ok() - .map(expand_path) - .or_else(|| { - config_dir().map(|mut c| { - c.push("fw"); - c - }) - }) - .ok_or(AppError::InternalError("Cannot resolve fw config dir"))?; + let base = env::var("FW_CONFIG_DIR") + .map(PathBuf::from) + .ok() + .map(expand_path) + .or_else(|| { + config_dir().map(|mut c| { + c.push("fw"); + c + }) + }) + .ok_or(AppError::InternalError("Cannot resolve fw config dir"))?; - let mut settings = base.clone(); + let mut settings = base.clone(); - let env: String = env::var_os("FW_ENV") - .map(|s| s.to_string_lossy().to_string()) - .map(|s| format!("{}_", s)) - .unwrap_or_default() - .replace('/', ""); + let env: String = env::var_os("FW_ENV") + .map(|s| s.to_string_lossy().to_string()) + .map(|s| format!("{}_", s)) + .unwrap_or_default() + .replace('/', ""); - settings.push(format!("{}settings.toml", env)); + settings.push(format!("{}settings.toml", env)); - let mut projects = base.clone(); - projects.push("projects"); + let mut projects = base.clone(); + projects.push("projects"); - let mut tags = base.clone(); - tags.push("tags"); + let mut tags = base.clone(); + tags.push("tags"); - Ok(FwPaths { - settings, - base, - projects, - tags, - }) + Ok(FwPaths { + settings, + base, + projects, + tags, + }) } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[test] - fn test_do_not_expand_path_without_tilde() { - let path = PathBuf::from("/foo/bar"); - assert_eq!(expand_path(path.clone()), path); - } - #[test] - fn test_do_expand_path() { - let path = PathBuf::from("~/foo/bar"); - let home = PathBuf::from("/my/home"); - assert_eq!(do_expand(path, Some(home)), PathBuf::from("/my/home/foo/bar")); - } + #[test] + fn test_do_not_expand_path_without_tilde() { + let path = PathBuf::from("/foo/bar"); + assert_eq!(expand_path(path.clone()), path); + } + #[test] + fn test_do_expand_path() { + let path = PathBuf::from("~/foo/bar"); + let home = PathBuf::from("/my/home"); + assert_eq!(do_expand(path, Some(home)), PathBuf::from("/my/home/foo/bar")); + } } diff --git a/src/config/project.rs b/src/config/project.rs index 1c824c54..dc383ead 100644 --- a/src/config/project.rs +++ b/src/config/project.rs @@ -4,46 +4,46 @@ use std::collections::BTreeSet; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Remote { - pub name: String, - pub git: String, + pub name: String, + pub git: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Project { - #[serde(skip)] - pub name: String, + #[serde(skip)] + pub name: String, - #[serde(default)] - pub trusted: bool, + #[serde(default)] + pub trusted: bool, - pub git: String, - pub after_clone: Option, - pub after_workon: Option, - pub override_path: Option, - pub bare: Option, - pub tags: Option>, - pub additional_remotes: Option>, + pub git: String, + pub after_clone: Option, + pub after_workon: Option, + pub override_path: Option, + pub bare: Option, + pub tags: Option>, + pub additional_remotes: Option>, - #[serde(skip)] - pub project_config_path: String, + #[serde(skip)] + pub project_config_path: String, } impl Project { - pub fn example() -> Project { - Project { - name: "fw".to_owned(), - git: "git@github.com:brocode/fw.git".to_owned(), - tags: Some(btreeset!["rust".to_owned(), "brocode".to_owned()]), - after_clone: Some("echo BROCODE!!".to_string()), - after_workon: Some("echo workon fw".to_string()), - override_path: Some("/some/fancy/path/to/fw".to_string()), - additional_remotes: Some(vec![Remote { - name: "upstream".to_string(), - git: "git@...".to_string(), - }]), - bare: Some(false), - trusted: false, - project_config_path: "".to_string(), // ignored + pub fn example() -> Project { + Project { + name: "fw".to_owned(), + git: "git@github.com:brocode/fw.git".to_owned(), + tags: Some(btreeset!["rust".to_owned(), "brocode".to_owned()]), + after_clone: Some("echo BROCODE!!".to_string()), + after_workon: Some("echo workon fw".to_string()), + override_path: Some("/some/fancy/path/to/fw".to_string()), + additional_remotes: Some(vec![Remote { + name: "upstream".to_string(), + git: "git@...".to_string(), + }]), + bare: Some(false), + trusted: false, + project_config_path: "".to_string(), // ignored + } } - } } diff --git a/src/config/settings.rs b/src/config/settings.rs index 5ed879e4..e1a40ed9 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -3,75 +3,75 @@ use std::collections::{BTreeMap, BTreeSet}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Tag { - pub after_clone: Option, - pub after_workon: Option, - pub priority: Option, - pub workspace: Option, - pub default: Option, + pub after_clone: Option, + pub after_workon: Option, + pub priority: Option, + pub workspace: Option, + pub default: Option, - #[serde(skip)] - pub tag_config_path: String, + #[serde(skip)] + pub tag_config_path: String, } impl Tag { - pub fn example() -> Tag { - Tag { - after_clone: Some("echo after clone from tag".to_owned()), - after_workon: Some("echo after workon from tag".to_owned()), - priority: Some(0), - workspace: Some("/home/other".to_string()), - default: Some(false), - tag_config_path: "".to_string(), // ignored + pub fn example() -> Tag { + Tag { + after_clone: Some("echo after clone from tag".to_owned()), + after_workon: Some("echo after workon from tag".to_owned()), + priority: Some(0), + workspace: Some("/home/other".to_string()), + default: Some(false), + tag_config_path: "".to_string(), // ignored + } } - } } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GitlabSettings { - pub token: String, - pub host: String, + pub token: String, + pub host: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Settings { - pub workspace: String, - pub shell: Option>, - pub default_after_workon: Option, - pub default_after_clone: Option, - pub default_tags: Option>, - pub tags: Option>, - pub github_token: Option, - pub gitlab: Option, + pub workspace: String, + pub shell: Option>, + pub default_after_workon: Option, + pub default_after_clone: Option, + pub default_tags: Option>, + pub tags: Option>, + pub github_token: Option, + pub gitlab: Option, } impl Settings { - pub fn get_shell_or_default(self: &Settings) -> Vec { - self.shell.clone().unwrap_or_else(|| vec!["sh".to_owned(), "-c".to_owned()]) - } + pub fn get_shell_or_default(self: &Settings) -> Vec { + self.shell.clone().unwrap_or_else(|| vec!["sh".to_owned(), "-c".to_owned()]) + } } #[derive(Deserialize, Serialize, Debug, Clone)] pub struct PersistedSettings { - pub workspace: String, - pub shell: Option>, - pub default_after_workon: Option, - pub default_after_clone: Option, - pub github_token: Option, - pub gitlab: Option, + pub workspace: String, + pub shell: Option>, + pub default_after_workon: Option, + pub default_after_clone: Option, + pub github_token: Option, + pub gitlab: Option, } impl PersistedSettings { - pub fn example() -> PersistedSettings { - PersistedSettings { - workspace: "~/workspace".to_owned(), - default_after_workon: Some("echo default after workon".to_string()), - default_after_clone: Some("echo default after clone".to_string()), - shell: Some(vec!["/usr/bin/zsh".to_string(), "-c".to_string()]), - github_token: Some("githubtokensecret".to_string()), - gitlab: Some(GitlabSettings { - host: "localhost".to_string(), - token: "token".to_string(), - }), + pub fn example() -> PersistedSettings { + PersistedSettings { + workspace: "~/workspace".to_owned(), + default_after_workon: Some("echo default after workon".to_string()), + default_after_clone: Some("echo default after clone".to_string()), + shell: Some(vec!["/usr/bin/zsh".to_string(), "-c".to_string()]), + github_token: Some("githubtokensecret".to_string()), + gitlab: Some(GitlabSettings { + host: "localhost".to_string(), + token: "token".to_string(), + }), + } } - } } diff --git a/src/errors/mod.rs b/src/errors/mod.rs index 0c890443..6a746ef2 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -4,77 +4,77 @@ use std::io; #[derive(Debug)] pub enum AppError { - Io(io::Error), - UserError(String), - RuntimeError(String), - BadJson(serde_json::Error), - InternalError(&'static str), - GitError(git2::Error), - Regex(regex::Error), - TomlSerError(toml::ser::Error), - TomlDeError(toml::de::Error), - WalkdirError(walkdir::Error), - ReqwestError(reqwest::Error), + Io(io::Error), + UserError(String), + RuntimeError(String), + BadJson(serde_json::Error), + InternalError(&'static str), + GitError(git2::Error), + Regex(regex::Error), + TomlSerError(toml::ser::Error), + TomlDeError(toml::de::Error), + WalkdirError(walkdir::Error), + ReqwestError(reqwest::Error), } macro_rules! app_error_from { - ($error: ty, $app_error: ident) => { - impl From<$error> for AppError { - fn from(err: $error) -> AppError { - AppError::$app_error(err) - } - } - }; + ($error: ty, $app_error: ident) => { + impl From<$error> for AppError { + fn from(err: $error) -> AppError { + AppError::$app_error(err) + } + } + }; } impl AppError { - pub fn require(option: Option, app_error: AppError) -> Result { - if let Some(value) = option { - Result::Ok(value) - } else { - Result::Err(app_error) + pub fn require(option: Option, app_error: AppError) -> Result { + if let Some(value) = option { + Result::Ok(value) + } else { + Result::Err(app_error) + } } - } } impl fmt::Display for AppError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - AppError::Io(ref err) => write!(f, "Io error: {}", err), - AppError::UserError(ref str) => write!(f, "User error: {}", str), - AppError::RuntimeError(ref str) => write!(f, "Runtime error: {}", str), - AppError::BadJson(ref err) => write!(f, "JSON error: {}", err), - AppError::InternalError(str) => write!(f, "Internal error: {}", str), - AppError::GitError(ref err) => write!(f, "Git error: {}", err), - AppError::Regex(ref err) => write!(f, "Regex error: {}", err), - AppError::TomlSerError(ref err) => write!(f, "toml serialization error: {}", err), - AppError::TomlDeError(ref err) => write!(f, "toml read error: {}", err), - AppError::WalkdirError(ref err) => write!(f, "walkdir error: {}", err), - AppError::ReqwestError(ref err) => write!(f, "reqwest error: {}", err), + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + AppError::Io(ref err) => write!(f, "Io error: {}", err), + AppError::UserError(ref str) => write!(f, "User error: {}", str), + AppError::RuntimeError(ref str) => write!(f, "Runtime error: {}", str), + AppError::BadJson(ref err) => write!(f, "JSON error: {}", err), + AppError::InternalError(str) => write!(f, "Internal error: {}", str), + AppError::GitError(ref err) => write!(f, "Git error: {}", err), + AppError::Regex(ref err) => write!(f, "Regex error: {}", err), + AppError::TomlSerError(ref err) => write!(f, "toml serialization error: {}", err), + AppError::TomlDeError(ref err) => write!(f, "toml read error: {}", err), + AppError::WalkdirError(ref err) => write!(f, "walkdir error: {}", err), + AppError::ReqwestError(ref err) => write!(f, "reqwest error: {}", err), + } } - } } impl Error for AppError { - fn cause(&self) -> Option<&dyn Error> { - match *self { - AppError::Io(ref err) => Some(err), - AppError::UserError(_) | AppError::RuntimeError(_) | AppError::InternalError(_) => None, - AppError::BadJson(ref err) => Some(err), - AppError::GitError(ref err) => Some(err), - AppError::Regex(ref err) => Some(err), - AppError::TomlSerError(ref err) => Some(err), - AppError::TomlDeError(ref err) => Some(err), - AppError::WalkdirError(ref err) => Some(err), - AppError::ReqwestError(ref err) => Some(err), + fn cause(&self) -> Option<&dyn Error> { + match *self { + AppError::Io(ref err) => Some(err), + AppError::UserError(_) | AppError::RuntimeError(_) | AppError::InternalError(_) => None, + AppError::BadJson(ref err) => Some(err), + AppError::GitError(ref err) => Some(err), + AppError::Regex(ref err) => Some(err), + AppError::TomlSerError(ref err) => Some(err), + AppError::TomlDeError(ref err) => Some(err), + AppError::WalkdirError(ref err) => Some(err), + AppError::ReqwestError(ref err) => Some(err), + } } - } } impl From for AppError { - fn from(err: core::num::ParseIntError) -> AppError { - AppError::UserError(format!("Type error: {}", err)) - } + fn from(err: core::num::ParseIntError) -> AppError { + AppError::UserError(format!("Type error: {}", err)) + } } app_error_from!(git2::Error, GitError); diff --git a/src/git/mod.rs b/src/git/mod.rs index ba22dacc..ea38e7b8 100644 --- a/src/git/mod.rs +++ b/src/git/mod.rs @@ -12,158 +12,158 @@ use std::borrow::ToOwned; use std::path::Path; pub fn repo_name_from_url(url: &str) -> Result<&str, AppError> { - let last_fragment = url.rsplit('/').next().ok_or_else(|| { - AppError::UserError(format!( - "Given URL {} does not have path fragments so cannot determine project name. Please give \ + let last_fragment = url.rsplit('/').next().ok_or_else(|| { + AppError::UserError(format!( + "Given URL {} does not have path fragments so cannot determine project name. Please give \ one.", - url - )) - })?; - - // trim_right_matches is more efficient but would fuck us up with repos like git@github.com:bauer/test.git.git (which is legal) - Ok(if last_fragment.ends_with(".git") { - last_fragment.split_at(last_fragment.len() - 4).0 - } else { - last_fragment - }) + url + )) + })?; + + // trim_right_matches is more efficient but would fuck us up with repos like git@github.com:bauer/test.git.git (which is legal) + Ok(if last_fragment.ends_with(".git") { + last_fragment.split_at(last_fragment.len() - 4).0 + } else { + last_fragment + }) } fn agent_callbacks() -> git2::RemoteCallbacks<'static> { - let mut remote_callbacks = RemoteCallbacks::new(); - remote_callbacks.credentials(move |_url, username_from_url, _allowed_types| git2::Cred::ssh_key_from_agent(username_from_url.unwrap_or("git"))); - remote_callbacks + let mut remote_callbacks = RemoteCallbacks::new(); + remote_callbacks.credentials(move |_url, username_from_url, _allowed_types| git2::Cred::ssh_key_from_agent(username_from_url.unwrap_or("git"))); + remote_callbacks } fn agent_fetch_options() -> git2::FetchOptions<'static> { - let remote_callbacks = agent_callbacks(); - let mut proxy_options = ProxyOptions::new(); - proxy_options.auto(); - let mut fetch_options = FetchOptions::new(); - fetch_options.remote_callbacks(remote_callbacks); - fetch_options.proxy_options(proxy_options); - - fetch_options + let remote_callbacks = agent_callbacks(); + let mut proxy_options = ProxyOptions::new(); + proxy_options.auto(); + let mut fetch_options = FetchOptions::new(); + fetch_options.remote_callbacks(remote_callbacks); + fetch_options.proxy_options(proxy_options); + + fetch_options } fn builder() -> RepoBuilder<'static> { - let options = agent_fetch_options(); - let mut repo_builder = RepoBuilder::new(); - repo_builder.fetch_options(options); - repo_builder + let options = agent_fetch_options(); + let mut repo_builder = RepoBuilder::new(); + repo_builder.fetch_options(options); + repo_builder } fn update_remote(remote: &mut Remote<'_>) -> Result<(), AppError> { - let remote_callbacks = agent_callbacks(); - let mut proxy_options = ProxyOptions::new(); - proxy_options.auto(); - remote - .connect_auth(Direction::Fetch, Some(remote_callbacks), Some(proxy_options)) - .map_err(AppError::GitError)?; - let mut options = agent_fetch_options(); - remote.download::(&[], Some(&mut options)).map_err(AppError::GitError)?; - remote.disconnect()?; - remote.update_tips(None, true, AutotagOption::Unspecified, None)?; - Ok(()) + let remote_callbacks = agent_callbacks(); + let mut proxy_options = ProxyOptions::new(); + proxy_options.auto(); + remote + .connect_auth(Direction::Fetch, Some(remote_callbacks), Some(proxy_options)) + .map_err(AppError::GitError)?; + let mut options = agent_fetch_options(); + remote.download::(&[], Some(&mut options)).map_err(AppError::GitError)?; + remote.disconnect()?; + remote.update_tips(None, true, AutotagOption::Unspecified, None)?; + Ok(()) } pub fn update_project_remotes(project: &Project, path: &Path, ff_merge: bool) -> Result<(), AppError> { - let local: Repository = Repository::open(path).map_err(AppError::GitError)?; - for desired_remote in project.additional_remotes.clone().unwrap_or_default().into_iter().chain( - vec![crate::config::project::Remote { - name: "origin".to_string(), - git: project.git.to_owned(), - }] - .into_iter(), - ) { - let remote = local - .find_remote(&desired_remote.name) - .or_else(|_| local.remote(&desired_remote.name, &desired_remote.git))?; - - let mut remote = match remote.url() { - Some(url) if url == desired_remote.git => remote, - _ => { - local.remote_set_url(&desired_remote.name, &desired_remote.git)?; - local.find_remote(&desired_remote.name)? - } - }; - - update_remote(&mut remote)?; - } - - if ff_merge { - // error does not matter. fast forward not possible - let _ = fast_forward_merge(&local); - } - - Ok(()) + let local: Repository = Repository::open(path).map_err(AppError::GitError)?; + for desired_remote in project.additional_remotes.clone().unwrap_or_default().into_iter().chain( + vec![crate::config::project::Remote { + name: "origin".to_string(), + git: project.git.to_owned(), + }] + .into_iter(), + ) { + let remote = local + .find_remote(&desired_remote.name) + .or_else(|_| local.remote(&desired_remote.name, &desired_remote.git))?; + + let mut remote = match remote.url() { + Some(url) if url == desired_remote.git => remote, + _ => { + local.remote_set_url(&desired_remote.name, &desired_remote.git)?; + local.find_remote(&desired_remote.name)? + } + }; + + update_remote(&mut remote)?; + } + + if ff_merge { + // error does not matter. fast forward not possible + let _ = fast_forward_merge(&local); + } + + Ok(()) } fn fast_forward_merge(local: &Repository) -> Result<(), AppError> { - let head_ref = local.head()?; - if head_ref.is_branch() { - let branch = Branch::wrap(head_ref); - let upstream = branch.upstream()?; - let upstream_commit = local.reference_to_annotated_commit(upstream.get())?; - - let (analysis_result, _) = local.merge_analysis(&[&upstream_commit])?; - if MergeAnalysis::is_fast_forward(&analysis_result) { - let target_id = upstream_commit.id(); - local.checkout_tree(&local.find_object(upstream_commit.id(), None)?, None)?; - local.head()?.set_target(target_id, "fw fast-forward")?; + let head_ref = local.head()?; + if head_ref.is_branch() { + let branch = Branch::wrap(head_ref); + let upstream = branch.upstream()?; + let upstream_commit = local.reference_to_annotated_commit(upstream.get())?; + + let (analysis_result, _) = local.merge_analysis(&[&upstream_commit])?; + if MergeAnalysis::is_fast_forward(&analysis_result) { + let target_id = upstream_commit.id(); + local.checkout_tree(&local.find_object(upstream_commit.id(), None)?, None)?; + local.head()?.set_target(target_id, "fw fast-forward")?; + } } - } - Ok(()) + Ok(()) } pub fn clone_project(config: &Config, project: &Project, path: &Path) -> Result<(), AppError> { - let shell = config.settings.get_shell_or_default(); - let mut repo_builder = builder(); - repo_builder - .bare(project.bare.unwrap_or_default()) - .clone(project.git.as_str(), path) - .map_err(AppError::GitError) - .and_then(|repo| init_additional_remotes(project, repo)) - .and_then(|_| { - let after_clone = config.resolve_after_clone(project); - if !after_clone.is_empty() { - spawn_maybe(&shell, &after_clone.join(" && "), path, &project.name, random_color()) - .map_err(|error| AppError::UserError(format!("Post-clone hook failed (nonzero exit code). Cause: {:?}", error))) - } else { - Ok(()) - } - }) + let shell = config.settings.get_shell_or_default(); + let mut repo_builder = builder(); + repo_builder + .bare(project.bare.unwrap_or_default()) + .clone(project.git.as_str(), path) + .map_err(AppError::GitError) + .and_then(|repo| init_additional_remotes(project, repo)) + .and_then(|_| { + let after_clone = config.resolve_after_clone(project); + if !after_clone.is_empty() { + spawn_maybe(&shell, &after_clone.join(" && "), path, &project.name, random_color()) + .map_err(|error| AppError::UserError(format!("Post-clone hook failed (nonzero exit code). Cause: {:?}", error))) + } else { + Ok(()) + } + }) } fn init_additional_remotes(project: &Project, repository: Repository) -> Result<(), AppError> { - if let Some(additional_remotes) = &project.additional_remotes { - for remote in additional_remotes { - let mut git_remote = repository.remote(&remote.name, &remote.git)?; - update_remote(&mut git_remote)?; + if let Some(additional_remotes) = &project.additional_remotes { + for remote in additional_remotes { + let mut git_remote = repository.remote(&remote.name, &remote.git)?; + update_remote(&mut git_remote)?; + } } - } - Ok(()) + Ok(()) } #[cfg(test)] mod tests { - use super::*; - - #[test] - fn test_repo_name_from_url() { - let https_url = "https://github.com/mriehl/fw"; - let name = repo_name_from_url(https_url).unwrap().to_owned(); - assert_eq!(name, "fw".to_owned()); - } - #[test] - fn test_repo_name_from_ssh_pragma() { - let ssh_pragma = "git@github.com:mriehl/fw.git"; - let name = repo_name_from_url(ssh_pragma).unwrap().to_owned(); - assert_eq!(name, "fw".to_owned()); - } - #[test] - fn test_repo_name_from_ssh_pragma_with_multiple_git_endings() { - let ssh_pragma = "git@github.com:mriehl/fw.git.git"; - let name = repo_name_from_url(ssh_pragma).unwrap().to_owned(); - assert_eq!(name, "fw.git".to_owned()); - } + use super::*; + + #[test] + fn test_repo_name_from_url() { + let https_url = "https://github.com/mriehl/fw"; + let name = repo_name_from_url(https_url).unwrap().to_owned(); + assert_eq!(name, "fw".to_owned()); + } + #[test] + fn test_repo_name_from_ssh_pragma() { + let ssh_pragma = "git@github.com:mriehl/fw.git"; + let name = repo_name_from_url(ssh_pragma).unwrap().to_owned(); + assert_eq!(name, "fw".to_owned()); + } + #[test] + fn test_repo_name_from_ssh_pragma_with_multiple_git_endings() { + let ssh_pragma = "git@github.com:mriehl/fw.git.git"; + let name = repo_name_from_url(ssh_pragma).unwrap().to_owned(); + assert_eq!(name, "fw.git".to_owned()); + } } diff --git a/src/intellij/mod.rs b/src/intellij/mod.rs index 9f39344b..47b873cf 100644 --- a/src/intellij/mod.rs +++ b/src/intellij/mod.rs @@ -6,59 +6,59 @@ use std::option::Option::Some; use std::path::PathBuf; pub fn intellij(maybe_config: Result, warn: bool) -> Result<(), AppError> { - let config: Config = maybe_config?; - let projects_paths: Vec = config.projects.values().map(|p| config.actual_path_to_project(p)).collect(); - let recent_projects_candidates = get_recent_projects_candidates()?; - for candidate in recent_projects_candidates { - let mut writer = fs::File::create(candidate)?; - writeln!( - writer, - "")?; - } - let number_of_projects = projects_paths.len(); + let number_of_projects = projects_paths.len(); - if number_of_projects > 50 && warn { - print_number_of_projects_warning(number_of_projects) - } + if number_of_projects > 50 && warn { + print_number_of_projects_warning(number_of_projects) + } - Ok(()) + Ok(()) } fn get_recent_projects_candidates() -> Result, AppError> { - let mut recent_projects_candidates: Vec = Vec::new(); - let mut jetbrains_dir: PathBuf = dirs::config_dir().ok_or(AppError::InternalError("Could not resolve user configuration directory"))?; - jetbrains_dir.push("JetBrains"); - for entry in fs::read_dir(jetbrains_dir)? { - let path = entry?.path(); - if let Some(directory_name) = path.file_name() { - let dir = directory_name.to_string_lossy(); - if dir.starts_with("IntelliJ") || dir.starts_with("Idea") { - let mut recent_projects_path = path.clone(); - recent_projects_path.push("options"); - recent_projects_path.push("recentProjects.xml"); - if recent_projects_path.exists() { - recent_projects_candidates.push(recent_projects_path); + let mut recent_projects_candidates: Vec = Vec::new(); + let mut jetbrains_dir: PathBuf = dirs::config_dir().ok_or(AppError::InternalError("Could not resolve user configuration directory"))?; + jetbrains_dir.push("JetBrains"); + for entry in fs::read_dir(jetbrains_dir)? { + let path = entry?.path(); + if let Some(directory_name) = path.file_name() { + let dir = directory_name.to_string_lossy(); + if dir.starts_with("IntelliJ") || dir.starts_with("Idea") { + let mut recent_projects_path = path.clone(); + recent_projects_path.push("options"); + recent_projects_path.push("recentProjects.xml"); + if recent_projects_path.exists() { + recent_projects_candidates.push(recent_projects_path); + } + } } - } } - } - Ok(recent_projects_candidates) + Ok(recent_projects_candidates) } fn print_number_of_projects_warning(number_of_projects: usize) { - print!("WARNING: {} ", number_of_projects); - print!("projects were added to the list. Intellij only lists 50 projects by default. You can change this in Intellij by going to "); - print!(r#"Settings -> Search for "recent" -> Pick the "Advanced Settings" -> adjust "Maximum number of recent projects"."#); - println!("A high number is recommended since it won't do any harm to the system."); + print!("WARNING: {} ", number_of_projects); + print!("projects were added to the list. Intellij only lists 50 projects by default. You can change this in Intellij by going to "); + print!(r#"Settings -> Search for "recent" -> Pick the "Advanced Settings" -> adjust "Maximum number of recent projects"."#); + println!("A high number is recommended since it won't do any harm to the system."); } diff --git a/src/main.rs b/src/main.rs index 0957fbc1..047eff7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,215 +3,215 @@ use setup::ProjectState; use std::collections::BTreeSet; fn main() { - openssl_probe::init_ssl_cert_env_vars(); - let return_code = _main(); - std::process::exit(return_code) + openssl_probe::init_ssl_cert_env_vars(); + let return_code = _main(); + std::process::exit(return_code) } fn _main() -> i32 { - let matches = crate::app::app().get_matches(); + let matches = crate::app::app().get_matches(); - let config = config::read_config(); - if config.is_err() { - eprintln!( - "Could not read v2.0 config: {:?}. If you are running the setup right now this is expected.", - config - ); - }; + let config = config::read_config(); + if config.is_err() { + eprintln!( + "Could not read v2.0 config: {:?}. If you are running the setup right now this is expected.", + config + ); + }; - let subcommand_name = matches.subcommand_name().expect("subcommand required by clap.rs").to_owned(); - let subcommand_matches = matches.subcommand_matches(&subcommand_name).expect("subcommand matches enforced by clap.rs"); + let subcommand_name = matches.subcommand_name().expect("subcommand required by clap.rs").to_owned(); + let subcommand_matches = matches.subcommand_matches(&subcommand_name).expect("subcommand matches enforced by clap.rs"); - let result: Result<(), AppError> = match subcommand_name.as_ref() { - "sync" => { - let worker = subcommand_matches.get_one::("parallelism").expect("enforced by clap.rs").to_owned(); + let result: Result<(), AppError> = match subcommand_name.as_ref() { + "sync" => { + let worker = subcommand_matches.get_one::("parallelism").expect("enforced by clap.rs").to_owned(); - sync::synchronize( - config, - subcommand_matches.get_flag("only-new"), - !subcommand_matches.get_flag("no-fast-forward-merge"), - &subcommand_matches - .get_many::("tag") - .unwrap_or_default() - .map(ToOwned::to_owned) - .collect(), - worker, - ) + sync::synchronize( + config, + subcommand_matches.get_flag("only-new"), + !subcommand_matches.get_flag("no-fast-forward-merge"), + &subcommand_matches + .get_many::("tag") + .unwrap_or_default() + .map(ToOwned::to_owned) + .collect(), + worker, + ) + } + "add-remote" => { + let name: &str = subcommand_matches.get_one::("NAME").expect("argument required by clap.rs"); + let remote_name: &str = subcommand_matches.get_one::("REMOTE_NAME").expect("argument required by clap.rs"); + let url: &str = subcommand_matches.get_one::("URL").expect("argument required by clap.rs"); + project::add_remote(config, name, remote_name.to_string(), url.to_string()) + } + "remove-remote" => { + let name: &str = subcommand_matches.get_one::("NAME").expect("argument required by clap.rs"); + let remote_name: &str = subcommand_matches.get_one::("REMOTE_NAME").expect("argument required by clap.rs"); + project::remove_remote(config, name, remote_name.to_string()) + } + "add" => { + let name: Option = subcommand_matches.get_one::("NAME").map(ToOwned::to_owned); + let url: &str = subcommand_matches.get_one::("URL").expect("argument required by clap.rs"); + let after_workon: Option = subcommand_matches.get_one::("after-workon").map(ToOwned::to_owned); + let after_clone: Option = subcommand_matches.get_one::("after-clone").map(ToOwned::to_owned); + let override_path: Option = subcommand_matches.get_one::("override-path").map(ToOwned::to_owned); + let tags: Option> = subcommand_matches + .get_many::("tag") + .map(|v| v.into_iter().map(ToOwned::to_owned).collect()); + let trusted = subcommand_matches.get_flag("trusted"); + project::add_entry(config, name, url, after_workon, after_clone, override_path, tags, trusted) + } + "remove" => project::remove_project( + config, + subcommand_matches.get_one::("NAME").expect("argument required by clap.rs"), + subcommand_matches.get_flag("purge-directory"), + ), + "update" => { + let name: &str = subcommand_matches.get_one::("NAME").expect("argument required by clap.rs"); + let git: Option = subcommand_matches.get_one::("git").map(ToOwned::to_owned); + let after_workon: Option = subcommand_matches.get_one::("after-workon").map(ToOwned::to_owned); + let after_clone: Option = subcommand_matches.get_one::("after-clone").map(ToOwned::to_owned); + let override_path: Option = subcommand_matches.get_one::("override-path").map(ToOwned::to_owned); + project::update_entry(config, name, git, after_workon, after_clone, override_path) + } + "setup" => setup::setup(subcommand_matches.get_one::("WORKSPACE_DIR").expect("argument required by clap.rs")), + "import" => setup::import( + config, + subcommand_matches.get_one::("PROJECT_DIR").expect("argument required by clap.rs"), + ), + "org-import" => setup::org_import( + config, + subcommand_matches.get_one::("ORG_NAME").expect("argument required by clap.rs"), + subcommand_matches.get_flag("include-archived"), + ), + "gitlab-import" => { + let state = *subcommand_matches.get_one::("include").expect("argument required by clap.rs"); + setup::gitlab_import(config, state) + } + "gen-workon" => workon::gen( + subcommand_matches.get_one::("PROJECT_NAME").expect("argument required by clap.rs"), + config, + subcommand_matches.get_flag("quick"), + ), + "gen-reworkon" => workon::gen_reworkon(config), + "reworkon" => workon::reworkon(config), + "inspect" => project::inspect( + subcommand_matches.get_one::("PROJECT_NAME").expect("argument required by clap.rs"), + config, + subcommand_matches.get_flag("json"), + ), + "projectile" => projectile::projectile(config), + "intellij" => intellij::intellij(config, !subcommand_matches.get_flag("no-warn")), + "print-path" => project::print_path( + config, + subcommand_matches.get_one::("PROJECT_NAME").expect("argument required by clap.rs"), + ), + "foreach" => spawn::foreach( + config, + subcommand_matches.get_one::("CMD").expect("argument required by clap.rs"), + &subcommand_matches + .get_many::("tag") + .unwrap_or_default() + .map(ToOwned::to_owned) + .collect(), + &subcommand_matches.get_one::("parallel").map(ToOwned::to_owned), + ), + "print-zsh-setup" => crate::shell::print_zsh_setup(subcommand_matches.get_flag("with-fzf"), subcommand_matches.get_flag("with-skim")), + "print-bash-setup" => crate::shell::print_bash_setup(subcommand_matches.get_flag("with-fzf"), subcommand_matches.get_flag("with-skim")), + "print-fish-setup" => crate::shell::print_fish_setup(subcommand_matches.get_flag("with-fzf"), subcommand_matches.get_flag("with-skim")), + "tag" => { + let subsubcommand_name: String = subcommand_matches.subcommand_name().expect("subcommand matches enforced by clap.rs").to_owned(); + let subsubcommand_matches: clap::ArgMatches = subcommand_matches + .subcommand_matches(&subsubcommand_name) + .expect("subcommand matches enforced by clap.rs") + .to_owned(); + execute_tag_subcommand(config, &subsubcommand_name, &subsubcommand_matches) + } + "ls" => project::ls( + config, + &subcommand_matches + .get_many::("tag") + .unwrap_or_default() + .map(ToOwned::to_owned) + .collect(), + ), + _ => Err(AppError::InternalError("Command not implemented")), } - "add-remote" => { - let name: &str = subcommand_matches.get_one::("NAME").expect("argument required by clap.rs"); - let remote_name: &str = subcommand_matches.get_one::("REMOTE_NAME").expect("argument required by clap.rs"); - let url: &str = subcommand_matches.get_one::("URL").expect("argument required by clap.rs"); - project::add_remote(config, name, remote_name.to_string(), url.to_string()) - } - "remove-remote" => { - let name: &str = subcommand_matches.get_one::("NAME").expect("argument required by clap.rs"); - let remote_name: &str = subcommand_matches.get_one::("REMOTE_NAME").expect("argument required by clap.rs"); - project::remove_remote(config, name, remote_name.to_string()) - } - "add" => { - let name: Option = subcommand_matches.get_one::("NAME").map(ToOwned::to_owned); - let url: &str = subcommand_matches.get_one::("URL").expect("argument required by clap.rs"); - let after_workon: Option = subcommand_matches.get_one::("after-workon").map(ToOwned::to_owned); - let after_clone: Option = subcommand_matches.get_one::("after-clone").map(ToOwned::to_owned); - let override_path: Option = subcommand_matches.get_one::("override-path").map(ToOwned::to_owned); - let tags: Option> = subcommand_matches - .get_many::("tag") - .map(|v| v.into_iter().map(ToOwned::to_owned).collect()); - let trusted = subcommand_matches.get_flag("trusted"); - project::add_entry(config, name, url, after_workon, after_clone, override_path, tags, trusted) - } - "remove" => project::remove_project( - config, - subcommand_matches.get_one::("NAME").expect("argument required by clap.rs"), - subcommand_matches.get_flag("purge-directory"), - ), - "update" => { - let name: &str = subcommand_matches.get_one::("NAME").expect("argument required by clap.rs"); - let git: Option = subcommand_matches.get_one::("git").map(ToOwned::to_owned); - let after_workon: Option = subcommand_matches.get_one::("after-workon").map(ToOwned::to_owned); - let after_clone: Option = subcommand_matches.get_one::("after-clone").map(ToOwned::to_owned); - let override_path: Option = subcommand_matches.get_one::("override-path").map(ToOwned::to_owned); - project::update_entry(config, name, git, after_workon, after_clone, override_path) - } - "setup" => setup::setup(subcommand_matches.get_one::("WORKSPACE_DIR").expect("argument required by clap.rs")), - "import" => setup::import( - config, - subcommand_matches.get_one::("PROJECT_DIR").expect("argument required by clap.rs"), - ), - "org-import" => setup::org_import( - config, - subcommand_matches.get_one::("ORG_NAME").expect("argument required by clap.rs"), - subcommand_matches.get_flag("include-archived"), - ), - "gitlab-import" => { - let state = *subcommand_matches.get_one::("include").expect("argument required by clap.rs"); - setup::gitlab_import(config, state) - } - "gen-workon" => workon::gen( - subcommand_matches.get_one::("PROJECT_NAME").expect("argument required by clap.rs"), - config, - subcommand_matches.get_flag("quick"), - ), - "gen-reworkon" => workon::gen_reworkon(config), - "reworkon" => workon::reworkon(config), - "inspect" => project::inspect( - subcommand_matches.get_one::("PROJECT_NAME").expect("argument required by clap.rs"), - config, - subcommand_matches.get_flag("json"), - ), - "projectile" => projectile::projectile(config), - "intellij" => intellij::intellij(config, !subcommand_matches.get_flag("no-warn")), - "print-path" => project::print_path( - config, - subcommand_matches.get_one::("PROJECT_NAME").expect("argument required by clap.rs"), - ), - "foreach" => spawn::foreach( - config, - subcommand_matches.get_one::("CMD").expect("argument required by clap.rs"), - &subcommand_matches - .get_many::("tag") - .unwrap_or_default() - .map(ToOwned::to_owned) - .collect(), - &subcommand_matches.get_one::("parallel").map(ToOwned::to_owned), - ), - "print-zsh-setup" => crate::shell::print_zsh_setup(subcommand_matches.get_flag("with-fzf"), subcommand_matches.get_flag("with-skim")), - "print-bash-setup" => crate::shell::print_bash_setup(subcommand_matches.get_flag("with-fzf"), subcommand_matches.get_flag("with-skim")), - "print-fish-setup" => crate::shell::print_fish_setup(subcommand_matches.get_flag("with-fzf"), subcommand_matches.get_flag("with-skim")), - "tag" => { - let subsubcommand_name: String = subcommand_matches.subcommand_name().expect("subcommand matches enforced by clap.rs").to_owned(); - let subsubcommand_matches: clap::ArgMatches = subcommand_matches - .subcommand_matches(&subsubcommand_name) - .expect("subcommand matches enforced by clap.rs") - .to_owned(); - execute_tag_subcommand(config, &subsubcommand_name, &subsubcommand_matches) - } - "ls" => project::ls( - config, - &subcommand_matches - .get_many::("tag") - .unwrap_or_default() - .map(ToOwned::to_owned) - .collect(), - ), - _ => Err(AppError::InternalError("Command not implemented")), - } - .map(|_| ()); + .map(|_| ()); - match result { - Ok(()) => 0, - Err(error) => { - eprintln!("Error running command: error {}", error); - 1 + match result { + Ok(()) => 0, + Err(error) => { + eprintln!("Error running command: error {}", error); + 1 + } } - } } fn execute_tag_subcommand(maybe_config: Result, tag_command_name: &str, tag_matches: &clap::ArgMatches) -> Result<(), AppError> { - match tag_command_name { - "ls" => { - let maybe_project_name: Option = tag_matches.get_one::("PROJECT_NAME").map(ToOwned::to_owned); - tag::list_tags(maybe_config, maybe_project_name) - } - "tag-project" => { - let project_name: String = tag_matches - .get_one::("PROJECT_NAME") - .map(ToOwned::to_owned) - .expect("argument enforced by clap.rs"); - let tag_name: String = tag_matches - .get_one::("tag-name") - .map(ToOwned::to_owned) - .expect("argument enforced by clap.rs"); - tag::add_tag(&maybe_config?, project_name, tag_name) - } - "untag-project" => { - let project_name: String = tag_matches - .get_one::("PROJECT_NAME") - .map(ToOwned::to_owned) - .expect("argument enforced by clap.rs"); - let tag_name: String = tag_matches - .get_one::("tag-name") - .map(ToOwned::to_owned) - .expect("argument enforced by clap.rs"); - tag::remove_tag(maybe_config, project_name, &tag_name) - } - "inspect" => { - let tag_name: String = tag_matches - .get_one::("tag-name") - .map(ToOwned::to_owned) - .expect("argument enforced by clap.rs"); - tag::inspect_tag(maybe_config, &tag_name) - } - "rm" => { - let tag_name: String = tag_matches - .get_one::("tag-name") - .map(ToOwned::to_owned) - .expect("argument enforced by clap.rs"); - tag::delete_tag(maybe_config, &tag_name) - } - "add" => { - let tag_name: String = tag_matches - .get_one::("tag-name") - .map(ToOwned::to_owned) - .expect("argument enforced by clap.rs"); - let after_workon: Option = tag_matches.get_one::("after-workon").map(ToOwned::to_owned); - let after_clone: Option = tag_matches.get_one::("after-clone").map(ToOwned::to_owned); - let tag_workspace: Option = tag_matches.get_one::("workspace").map(ToOwned::to_owned); - let priority: Option = tag_matches.get_one::("priority").map(ToOwned::to_owned); - tag::create_tag(maybe_config, tag_name, after_workon, after_clone, priority, tag_workspace) + match tag_command_name { + "ls" => { + let maybe_project_name: Option = tag_matches.get_one::("PROJECT_NAME").map(ToOwned::to_owned); + tag::list_tags(maybe_config, maybe_project_name) + } + "tag-project" => { + let project_name: String = tag_matches + .get_one::("PROJECT_NAME") + .map(ToOwned::to_owned) + .expect("argument enforced by clap.rs"); + let tag_name: String = tag_matches + .get_one::("tag-name") + .map(ToOwned::to_owned) + .expect("argument enforced by clap.rs"); + tag::add_tag(&maybe_config?, project_name, tag_name) + } + "untag-project" => { + let project_name: String = tag_matches + .get_one::("PROJECT_NAME") + .map(ToOwned::to_owned) + .expect("argument enforced by clap.rs"); + let tag_name: String = tag_matches + .get_one::("tag-name") + .map(ToOwned::to_owned) + .expect("argument enforced by clap.rs"); + tag::remove_tag(maybe_config, project_name, &tag_name) + } + "inspect" => { + let tag_name: String = tag_matches + .get_one::("tag-name") + .map(ToOwned::to_owned) + .expect("argument enforced by clap.rs"); + tag::inspect_tag(maybe_config, &tag_name) + } + "rm" => { + let tag_name: String = tag_matches + .get_one::("tag-name") + .map(ToOwned::to_owned) + .expect("argument enforced by clap.rs"); + tag::delete_tag(maybe_config, &tag_name) + } + "add" => { + let tag_name: String = tag_matches + .get_one::("tag-name") + .map(ToOwned::to_owned) + .expect("argument enforced by clap.rs"); + let after_workon: Option = tag_matches.get_one::("after-workon").map(ToOwned::to_owned); + let after_clone: Option = tag_matches.get_one::("after-clone").map(ToOwned::to_owned); + let tag_workspace: Option = tag_matches.get_one::("workspace").map(ToOwned::to_owned); + let priority: Option = tag_matches.get_one::("priority").map(ToOwned::to_owned); + tag::create_tag(maybe_config, tag_name, after_workon, after_clone, priority, tag_workspace) + } + "autotag" => tag::autotag( + maybe_config, + tag_matches.get_one::("CMD").expect("argument required by clap.rs"), + &tag_matches + .get_one::("tag-name") + .map(ToOwned::to_owned) + .expect("argument enforced by clap.rs"), + &tag_matches.get_one::("parallel").map(ToOwned::to_owned), + ), + _ => Result::Err(AppError::InternalError("Command not implemented")), } - "autotag" => tag::autotag( - maybe_config, - tag_matches.get_one::("CMD").expect("argument required by clap.rs"), - &tag_matches - .get_one::("tag-name") - .map(ToOwned::to_owned) - .expect("argument enforced by clap.rs"), - &tag_matches.get_one::("parallel").map(ToOwned::to_owned), - ), - _ => Result::Err(AppError::InternalError("Command not implemented")), - } } mod app; diff --git a/src/project/mod.rs b/src/project/mod.rs index e5f76282..53924587 100644 --- a/src/project/mod.rs +++ b/src/project/mod.rs @@ -8,197 +8,197 @@ use std::fs; use yansi::Paint; pub fn add_entry( - maybe_config: Result, - maybe_name: Option, - url: &str, - after_workon: Option, - after_clone: Option, - override_path: Option, - tags: Option>, - trusted: bool, + maybe_config: Result, + maybe_name: Option, + url: &str, + after_workon: Option, + after_clone: Option, + override_path: Option, + tags: Option>, + trusted: bool, ) -> Result<(), AppError> { - let name = maybe_name - .ok_or_else(|| AppError::UserError(format!("No project name specified for {}", url))) - .or_else(|_| repo_name_from_url(url).map(ToOwned::to_owned))?; - let config: Config = maybe_config?; - if config.projects.contains_key(&name) { - Err(AppError::UserError(format!( - "Project key {} already exists, not gonna overwrite it for you", - name - ))) - } else { - let default_after_clone = config.settings.default_after_clone.clone(); - let default_after_workon = config.settings.default_after_workon.clone(); - - let project_tags: Option> = if tags.is_some() && config.settings.default_tags.is_some() { - tags.zip(config.settings.default_tags).map(|(t1, t2)| t1.union(&t2).cloned().collect()) + let name = maybe_name + .ok_or_else(|| AppError::UserError(format!("No project name specified for {}", url))) + .or_else(|_| repo_name_from_url(url).map(ToOwned::to_owned))?; + let config: Config = maybe_config?; + if config.projects.contains_key(&name) { + Err(AppError::UserError(format!( + "Project key {} already exists, not gonna overwrite it for you", + name + ))) } else { - tags.or(config.settings.default_tags) - }; + let default_after_clone = config.settings.default_after_clone.clone(); + let default_after_workon = config.settings.default_after_workon.clone(); - config::write_project(&Project { - git: url.to_owned(), - name, - after_clone: after_clone.or(default_after_clone), - after_workon: after_workon.or(default_after_workon), - override_path, - tags: project_tags, - bare: None, - additional_remotes: None, - trusted, - project_config_path: "default".to_string(), - })?; - Ok(()) - } + let project_tags: Option> = if tags.is_some() && config.settings.default_tags.is_some() { + tags.zip(config.settings.default_tags).map(|(t1, t2)| t1.union(&t2).cloned().collect()) + } else { + tags.or(config.settings.default_tags) + }; + + config::write_project(&Project { + git: url.to_owned(), + name, + after_clone: after_clone.or(default_after_clone), + after_workon: after_workon.or(default_after_workon), + override_path, + tags: project_tags, + bare: None, + additional_remotes: None, + trusted, + project_config_path: "default".to_string(), + })?; + Ok(()) + } } pub fn remove_project(maybe_config: Result, project_name: &str, purge_directory: bool) -> Result<(), AppError> { - let config: Config = maybe_config?; + let config: Config = maybe_config?; - if !config.projects.contains_key(project_name) { - Err(AppError::UserError(format!("Project key {} does not exist in config", project_name))) - } else if let Some(project) = config.projects.get(&project_name.to_owned()).cloned() { - if purge_directory { - let path = config.actual_path_to_project(&project); + if !config.projects.contains_key(project_name) { + Err(AppError::UserError(format!("Project key {} does not exist in config", project_name))) + } else if let Some(project) = config.projects.get(&project_name.to_owned()).cloned() { + if purge_directory { + let path = config.actual_path_to_project(&project); - if path.exists() { - fs::remove_dir_all(&path)?; - } + if path.exists() { + fs::remove_dir_all(&path)?; + } + } + config::delete_project_config(&project) + } else { + Err(AppError::UserError(format!("Unknown project {}", project_name))) } - config::delete_project_config(&project) - } else { - Err(AppError::UserError(format!("Unknown project {}", project_name))) - } } pub fn add_remote(maybe_config: Result, name: &str, remote_name: String, git: String) -> Result<(), AppError> { - let config: Config = maybe_config?; - if !config.projects.contains_key(name) { - return Err(AppError::UserError(format!("Project key {} does not exists. Can not update.", name))); - } - let mut project_config: Project = config.projects.get(name).expect("Already checked in the if above").clone(); - let mut additional_remotes = project_config.additional_remotes.unwrap_or_default(); - if additional_remotes.iter().any(|r| r.name == remote_name) { - return Err(AppError::UserError(format!( - "Remote {} for project {} does already exist. Can not add.", - remote_name, name - ))); - } - additional_remotes.push(Remote { name: remote_name, git }); - project_config.additional_remotes = Some(additional_remotes); + let config: Config = maybe_config?; + if !config.projects.contains_key(name) { + return Err(AppError::UserError(format!("Project key {} does not exists. Can not update.", name))); + } + let mut project_config: Project = config.projects.get(name).expect("Already checked in the if above").clone(); + let mut additional_remotes = project_config.additional_remotes.unwrap_or_default(); + if additional_remotes.iter().any(|r| r.name == remote_name) { + return Err(AppError::UserError(format!( + "Remote {} for project {} does already exist. Can not add.", + remote_name, name + ))); + } + additional_remotes.push(Remote { name: remote_name, git }); + project_config.additional_remotes = Some(additional_remotes); - config::write_project(&project_config)?; - Ok(()) + config::write_project(&project_config)?; + Ok(()) } pub fn remove_remote(maybe_config: Result, name: &str, remote_name: String) -> Result<(), AppError> { - let config: Config = maybe_config?; - if !config.projects.contains_key(name) { - return Err(AppError::UserError(format!("Project key {} does not exists. Can not update.", name))); - } - let mut project_config: Project = config.projects.get(name).expect("Already checked in the if above").clone(); - let additional_remotes = project_config.additional_remotes.unwrap_or_default(); - let additional_remotes = additional_remotes.into_iter().filter(|r| r.name != remote_name).collect(); - project_config.additional_remotes = Some(additional_remotes); + let config: Config = maybe_config?; + if !config.projects.contains_key(name) { + return Err(AppError::UserError(format!("Project key {} does not exists. Can not update.", name))); + } + let mut project_config: Project = config.projects.get(name).expect("Already checked in the if above").clone(); + let additional_remotes = project_config.additional_remotes.unwrap_or_default(); + let additional_remotes = additional_remotes.into_iter().filter(|r| r.name != remote_name).collect(); + project_config.additional_remotes = Some(additional_remotes); - config::write_project(&project_config)?; - Ok(()) + config::write_project(&project_config)?; + Ok(()) } pub fn update_entry( - maybe_config: Result, - name: &str, - git: Option, - after_workon: Option, - after_clone: Option, - override_path: Option, + maybe_config: Result, + name: &str, + git: Option, + after_workon: Option, + after_clone: Option, + override_path: Option, ) -> Result<(), AppError> { - let config: Config = maybe_config?; - if name.starts_with("http") || name.starts_with("git@") { - Err(AppError::UserError(format!( - "{} looks like a repo URL and not like a project name, please fix", - name - ))) - } else if !config.projects.contains_key(name) { - Err(AppError::UserError(format!("Project key {} does not exists. Can not update.", name))) - } else { - let old_project_config: Project = config.projects.get(name).expect("Already checked in the if above").clone(); - config::write_project(&Project { - git: git.unwrap_or(old_project_config.git), - name: old_project_config.name, - after_clone: after_clone.or(old_project_config.after_clone), - after_workon: after_workon.or(old_project_config.after_workon), - override_path: override_path.or(old_project_config.override_path), - tags: old_project_config.tags, - bare: old_project_config.bare, - trusted: old_project_config.trusted, - additional_remotes: old_project_config.additional_remotes, - project_config_path: old_project_config.project_config_path, - })?; - Ok(()) - } + let config: Config = maybe_config?; + if name.starts_with("http") || name.starts_with("git@") { + Err(AppError::UserError(format!( + "{} looks like a repo URL and not like a project name, please fix", + name + ))) + } else if !config.projects.contains_key(name) { + Err(AppError::UserError(format!("Project key {} does not exists. Can not update.", name))) + } else { + let old_project_config: Project = config.projects.get(name).expect("Already checked in the if above").clone(); + config::write_project(&Project { + git: git.unwrap_or(old_project_config.git), + name: old_project_config.name, + after_clone: after_clone.or(old_project_config.after_clone), + after_workon: after_workon.or(old_project_config.after_workon), + override_path: override_path.or(old_project_config.override_path), + tags: old_project_config.tags, + bare: old_project_config.bare, + trusted: old_project_config.trusted, + additional_remotes: old_project_config.additional_remotes, + project_config_path: old_project_config.project_config_path, + })?; + Ok(()) + } } pub fn ls(maybe_config: Result, tags: &BTreeSet) -> Result<(), AppError> { - let config = maybe_config?; - for (name, project) in config.projects { - if tags.is_empty() || project.tags.unwrap_or_default().intersection(tags).count() > 0 { - println!("{}", name) + let config = maybe_config?; + for (name, project) in config.projects { + if tags.is_empty() || project.tags.unwrap_or_default().intersection(tags).count() > 0 { + println!("{}", name) + } } - } - Ok(()) + Ok(()) } pub fn print_path(maybe_config: Result, name: &str) -> Result<(), AppError> { - let config = maybe_config?; - let project = config - .projects - .get(name) - .ok_or_else(|| AppError::UserError(format!("project {} not found", name)))?; - let canonical_project_path = config.actual_path_to_project(project); - let path = canonical_project_path - .to_str() - .ok_or(AppError::InternalError("project path is not valid unicode"))?; - println!("{}", path); - Ok(()) + let config = maybe_config?; + let project = config + .projects + .get(name) + .ok_or_else(|| AppError::UserError(format!("project {} not found", name)))?; + let canonical_project_path = config.actual_path_to_project(project); + let path = canonical_project_path + .to_str() + .ok_or(AppError::InternalError("project path is not valid unicode"))?; + println!("{}", path); + Ok(()) } pub fn inspect(name: &str, maybe_config: Result, json: bool) -> Result<(), AppError> { - let config = maybe_config?; - let project = config - .projects - .get(name) - .ok_or_else(|| AppError::UserError(format!("project {} not found", name)))?; - if json { - println!("{}", serde_json::to_string(project)?); - return Ok(()); - } - let canonical_project_path = config.actual_path_to_project(project); - let path = canonical_project_path - .to_str() - .ok_or(AppError::InternalError("project path is not valid unicode"))?; - println!("{}", Paint::new(project.name.to_owned()).bold().underline()); - println!("{:<20}: {}", "Path", path); - println!("{:<20}: {}", "config path", project.project_config_path); - let tags = project - .tags - .clone() - .map(|t| { - let project_tags: Vec = t.into_iter().collect(); - project_tags.join(", ") - }) - .unwrap_or_else(|| "None".to_owned()); - println!("{:<20}: {}", "Tags", tags); - let additional_remotes = project - .additional_remotes - .clone() - .map(|t| { - let project_tags: Vec = t.into_iter().map(|r| format!("{} - {}", r.name, r.git)).collect(); - project_tags.join(", ") - }) - .unwrap_or_else(|| "None".to_owned()); - println!("{:<20}: {}", "Additional remotes", additional_remotes); - let git = project.git.clone(); - println!("{:<20}: {}", "Git", git); - Ok(()) + let config = maybe_config?; + let project = config + .projects + .get(name) + .ok_or_else(|| AppError::UserError(format!("project {} not found", name)))?; + if json { + println!("{}", serde_json::to_string(project)?); + return Ok(()); + } + let canonical_project_path = config.actual_path_to_project(project); + let path = canonical_project_path + .to_str() + .ok_or(AppError::InternalError("project path is not valid unicode"))?; + println!("{}", Paint::new(project.name.to_owned()).bold().underline()); + println!("{:<20}: {}", "Path", path); + println!("{:<20}: {}", "config path", project.project_config_path); + let tags = project + .tags + .clone() + .map(|t| { + let project_tags: Vec = t.into_iter().collect(); + project_tags.join(", ") + }) + .unwrap_or_else(|| "None".to_owned()); + println!("{:<20}: {}", "Tags", tags); + let additional_remotes = project + .additional_remotes + .clone() + .map(|t| { + let project_tags: Vec = t.into_iter().map(|r| format!("{} - {}", r.name, r.git)).collect(); + project_tags.join(", ") + }) + .unwrap_or_else(|| "None".to_owned()); + println!("{:<20}: {}", "Additional remotes", additional_remotes); + let git = project.git.clone(); + println!("{:<20}: {}", "Git", git); + Ok(()) } diff --git a/src/projectile/mod.rs b/src/projectile/mod.rs index 0273f611..419af430 100644 --- a/src/projectile/mod.rs +++ b/src/projectile/mod.rs @@ -8,66 +8,66 @@ use std::io::Write; use std::path::{Path, PathBuf}; pub fn projectile(maybe_config: Result) -> Result<(), AppError> { - let config: Config = maybe_config?; - let projects_paths: Vec = config.projects.values().map(|p| config.actual_path_to_project(p)).collect(); - let home_dir: PathBuf = dirs::home_dir().ok_or_else(|| AppError::UserError("$HOME not set".to_owned()))?; - let mut projectile_bookmarks: PathBuf = home_dir.clone(); - projectile_bookmarks.push(".emacs.d"); - projectile_bookmarks.push("projectile-bookmarks.eld"); - let writer = fs::File::create(projectile_bookmarks)?; - persist(&home_dir, writer, projects_paths) + let config: Config = maybe_config?; + let projects_paths: Vec = config.projects.values().map(|p| config.actual_path_to_project(p)).collect(); + let home_dir: PathBuf = dirs::home_dir().ok_or_else(|| AppError::UserError("$HOME not set".to_owned()))?; + let mut projectile_bookmarks: PathBuf = home_dir.clone(); + projectile_bookmarks.push(".emacs.d"); + projectile_bookmarks.push("projectile-bookmarks.eld"); + let writer = fs::File::create(projectile_bookmarks)?; + persist(&home_dir, writer, projects_paths) } fn persist(home_dir: &Path, writer: W, paths: Vec) -> Result<(), AppError> where - W: io::Write, + W: io::Write, { - let paths: Vec = paths.into_iter().flat_map(|path_buf| path_buf.to_str().map(ToOwned::to_owned)).collect(); - let mut buffer = io::BufWriter::new(writer); - buffer.write_all(b"(")?; - for path in paths { - let path = replace_path_with_tilde(&path, home_dir.to_path_buf()).unwrap_or(path); - buffer.write_all(format!("\"{}/\"", path).as_bytes())?; - buffer.write_all(b" ")?; - } - buffer.write_all(b")")?; - Ok(()) + let paths: Vec = paths.into_iter().flat_map(|path_buf| path_buf.to_str().map(ToOwned::to_owned)).collect(); + let mut buffer = io::BufWriter::new(writer); + buffer.write_all(b"(")?; + for path in paths { + let path = replace_path_with_tilde(&path, home_dir.to_path_buf()).unwrap_or(path); + buffer.write_all(format!("\"{}/\"", path).as_bytes())?; + buffer.write_all(b" ")?; + } + buffer.write_all(b")")?; + Ok(()) } fn replace_path_with_tilde(path: &str, path_to_replace: PathBuf) -> Result { - let replace_string = path_to_replace.into_os_string().into_string().expect("path should be a valid string"); - let mut pattern: String = "^".to_string(); - pattern.push_str(&replace_string); - let regex = Regex::new(&pattern)?; - Ok(regex.replace_all(path, "~").into_owned()) + let replace_string = path_to_replace.into_os_string().into_string().expect("path should be a valid string"); + let mut pattern: String = "^".to_string(); + pattern.push_str(&replace_string); + let regex = Regex::new(&pattern)?; + Ok(regex.replace_all(path, "~").into_owned()) } #[cfg(test)] mod tests { - use super::*; - use std::path::Path; + use super::*; + use std::path::Path; - #[test] - fn test_persists_projectile_config() { - use std::io::Cursor; - use std::str; - let mut buffer = Cursor::new(vec![0; 61]); - let paths = vec![PathBuf::from("/home/mriehl/test"), PathBuf::from("/home/mriehl/go/src/github.com/test2")]; + #[test] + fn test_persists_projectile_config() { + use std::io::Cursor; + use std::str; + let mut buffer = Cursor::new(vec![0; 61]); + let paths = vec![PathBuf::from("/home/mriehl/test"), PathBuf::from("/home/mriehl/go/src/github.com/test2")]; - let home_dir = Path::new("/home/blubb").to_path_buf(); - persist(&home_dir, &mut buffer, paths).unwrap(); + let home_dir = Path::new("/home/blubb").to_path_buf(); + persist(&home_dir, &mut buffer, paths).unwrap(); - assert_eq!( - str::from_utf8(buffer.get_ref()).unwrap(), - "(\"/home/mriehl/test/\" \"/home/mriehl/go/src/github.com/test2/\" )" - ); - } + assert_eq!( + str::from_utf8(buffer.get_ref()).unwrap(), + "(\"/home/mriehl/test/\" \"/home/mriehl/go/src/github.com/test2/\" )" + ); + } - #[test] - fn test_replace_path_with_tilde() { - let home_dir = Path::new("/home/blubb").to_path_buf(); + #[test] + fn test_replace_path_with_tilde() { + let home_dir = Path::new("/home/blubb").to_path_buf(); - let replaced_string = replace_path_with_tilde("/home/blubb/moep/home/blubb/test.txt", home_dir).expect("should succeed"); - assert_eq!(replaced_string, "~/moep/home/blubb/test.txt".to_string()); - } + let replaced_string = replace_path_with_tilde("/home/blubb/moep/home/blubb/test.txt", home_dir).expect("should succeed"); + assert_eq!(replaced_string, "~/moep/home/blubb/test.txt".to_string()); + } } diff --git a/src/setup/mod.rs b/src/setup/mod.rs index 5d5dbf96..2b861358 100644 --- a/src/setup/mod.rs +++ b/src/setup/mod.rs @@ -11,241 +11,241 @@ use std::path::{Path, PathBuf}; #[derive(Copy, Clone)] pub enum ProjectState { - Active, - Archived, - Both, + Active, + Archived, + Both, } impl clap::ValueEnum for ProjectState { - fn value_variants<'a>() -> &'a [Self] { - &[Self::Active, Self::Archived, Self::Both] - } - - fn to_possible_value(&self) -> Option { - match self { - Self::Active => Some(PossibleValue::new("active")), - Self::Archived => Some(PossibleValue::new("archived")), - Self::Both => Some(PossibleValue::new("both")), + fn value_variants<'a>() -> &'a [Self] { + &[Self::Active, Self::Archived, Self::Both] + } + + fn to_possible_value(&self) -> Option { + match self { + Self::Active => Some(PossibleValue::new("active")), + Self::Archived => Some(PossibleValue::new("archived")), + Self::Both => Some(PossibleValue::new("both")), + } } - } } impl std::str::FromStr for ProjectState { - type Err = AppError; - - fn from_str(s: &str) -> Result { - match s { - "active" => Ok(Self::Active), - "archived" => Ok(Self::Archived), - "both" => Ok(Self::Both), - _ => Err(AppError::InternalError("invalid value for ProjectState")), // TODO should this be unreachable?, + type Err = AppError; + + fn from_str(s: &str) -> Result { + match s { + "active" => Ok(Self::Active), + "archived" => Ok(Self::Archived), + "both" => Ok(Self::Both), + _ => Err(AppError::InternalError("invalid value for ProjectState")), // TODO should this be unreachable?, + } } - } } pub fn setup(workspace_dir: &str) -> Result<(), AppError> { - let path = PathBuf::from(workspace_dir); - let maybe_path = if path.exists() { - Ok(path) - } else { - Err(AppError::UserError(format!("Given workspace path {} does not exist", workspace_dir))) - }; - - maybe_path - .and_then(|path| { - if path.is_absolute() { + let path = PathBuf::from(workspace_dir); + let maybe_path = if path.exists() { Ok(path) - } else { - Err(AppError::UserError(format!("Workspace path {} needs to be absolute", workspace_dir))) - } - }) - .and_then(determine_projects) - .and_then(|projects| write_new_config_with_projects(projects, workspace_dir)) + } else { + Err(AppError::UserError(format!("Given workspace path {} does not exist", workspace_dir))) + }; + + maybe_path + .and_then(|path| { + if path.is_absolute() { + Ok(path) + } else { + Err(AppError::UserError(format!("Workspace path {} needs to be absolute", workspace_dir))) + } + }) + .and_then(determine_projects) + .and_then(|projects| write_new_config_with_projects(projects, workspace_dir)) } fn determine_projects(path: PathBuf) -> Result, AppError> { - let workspace_path = path.clone(); - - let project_entries: Vec = fs::read_dir(path).and_then(Iterator::collect).map_err(AppError::Io)?; - - let mut projects: BTreeMap = BTreeMap::new(); - for entry in project_entries { - let path = entry.path(); - if path.is_dir() { - match entry.file_name().into_string() { - Ok(name) => { - let mut path_to_repo = workspace_path.clone(); - path_to_repo.push(&name); - match load_project(None, path_to_repo, &name) { - Ok(project) => { - projects.insert(project.name.clone(), project); + let workspace_path = path.clone(); + + let project_entries: Vec = fs::read_dir(path).and_then(Iterator::collect).map_err(AppError::Io)?; + + let mut projects: BTreeMap = BTreeMap::new(); + for entry in project_entries { + let path = entry.path(); + if path.is_dir() { + match entry.file_name().into_string() { + Ok(name) => { + let mut path_to_repo = workspace_path.clone(); + path_to_repo.push(&name); + match load_project(None, path_to_repo, &name) { + Ok(project) => { + projects.insert(project.name.clone(), project); + } + Err(e) => eprintln!("Error while importing folder. Skipping it. {}", e), + } + } + Err(_) => eprintln!("Failed to parse directory name as unicode. Skipping it."), } - Err(e) => eprintln!("Error while importing folder. Skipping it. {}", e), - } } - Err(_) => eprintln!("Failed to parse directory name as unicode. Skipping it."), - } } - } - Ok(projects) + Ok(projects) } pub fn gitlab_import(maybe_config: Result, state: ProjectState) -> Result<(), AppError> { - use gitlab::api::Query; - let current_config = maybe_config?; - - let gitlab_config = current_config.settings.gitlab.clone().ok_or_else(|| { - AppError::UserError( - r#"Can't call Gitlab API, because no gitlab settings ("gitlab": { "token": "some-token", "url": "some-url" }) specified in the configuration."# - .to_string(), - ) - })?; - - let gitlab_client = - gitlab::Gitlab::new(gitlab_config.host, gitlab_config.token).map_err(|e| AppError::RuntimeError(format!("Failed to create gitlab client: {}", e)))?; - - let mut builder = gitlab::api::projects::Projects::builder(); - builder.owned(true); - match state { - ProjectState::Active => { - builder.archived(false); - } - ProjectState::Archived => { - builder.archived(true); + use gitlab::api::Query; + let current_config = maybe_config?; + + let gitlab_config = current_config.settings.gitlab.clone().ok_or_else(|| { + AppError::UserError( + r#"Can't call Gitlab API, because no gitlab settings ("gitlab": { "token": "some-token", "url": "some-url" }) specified in the configuration."# + .to_string(), + ) + })?; + + let gitlab_client = + gitlab::Gitlab::new(gitlab_config.host, gitlab_config.token).map_err(|e| AppError::RuntimeError(format!("Failed to create gitlab client: {}", e)))?; + + let mut builder = gitlab::api::projects::Projects::builder(); + builder.owned(true); + match state { + ProjectState::Active => { + builder.archived(false); + } + ProjectState::Archived => { + builder.archived(true); + } + ProjectState::Both => {} } - ProjectState::Both => {} - } - - // owned repos and your organizations repositories - let owned_projects: Vec = gitlab::api::paged(builder.build().unwrap(), gitlab::api::Pagination::All) - .query(&gitlab_client) - .map_err(|e| AppError::RuntimeError(format!("Failed to query gitlab: {}", e)))?; - - let names_and_urls: Vec<(String, String)> = owned_projects - .iter() - .map(|repo| (repo.name.to_owned(), repo.ssh_url_to_repo.to_owned())) - .collect(); - - let after_clone = current_config.settings.default_after_clone.clone(); - let after_workon = current_config.settings.default_after_workon.clone(); - let tags = current_config.settings.default_tags.clone(); - let mut current_projects = current_config.projects; - - for (name, url) in names_and_urls { - let p = Project { - name, - git: url, - after_clone: after_clone.clone(), - after_workon: after_workon.clone(), - override_path: None, - tags: tags.clone(), - additional_remotes: None, - bare: None, - trusted: false, - project_config_path: "gitlab".to_string(), - }; - if current_projects.contains_key(&p.name) { - //Skipping new project from Gitlab import because it already exists in the current fw config - } else { - config::write_project(&p)?; // TODO not sure if this should be default or gitlab subfolder? or even user specified? - current_projects.insert(p.name.clone(), p); // to ensure no duplicated name encountered during processing + // owned repos and your organizations repositories + let owned_projects: Vec = gitlab::api::paged(builder.build().unwrap(), gitlab::api::Pagination::All) + .query(&gitlab_client) + .map_err(|e| AppError::RuntimeError(format!("Failed to query gitlab: {}", e)))?; + + let names_and_urls: Vec<(String, String)> = owned_projects + .iter() + .map(|repo| (repo.name.to_owned(), repo.ssh_url_to_repo.to_owned())) + .collect(); + + let after_clone = current_config.settings.default_after_clone.clone(); + let after_workon = current_config.settings.default_after_workon.clone(); + let tags = current_config.settings.default_tags.clone(); + let mut current_projects = current_config.projects; + + for (name, url) in names_and_urls { + let p = Project { + name, + git: url, + after_clone: after_clone.clone(), + after_workon: after_workon.clone(), + override_path: None, + tags: tags.clone(), + additional_remotes: None, + bare: None, + trusted: false, + project_config_path: "gitlab".to_string(), + }; + + if current_projects.contains_key(&p.name) { + //Skipping new project from Gitlab import because it already exists in the current fw config + } else { + config::write_project(&p)?; // TODO not sure if this should be default or gitlab subfolder? or even user specified? + current_projects.insert(p.name.clone(), p); // to ensure no duplicated name encountered during processing + } } - } - Ok(()) + Ok(()) } pub fn org_import(maybe_config: Result, org_name: &str, include_archived: bool) -> Result<(), AppError> { - let current_config = maybe_config?; - let token = env::var_os("FW_GITHUB_TOKEN") - .map(|s| s.to_string_lossy().to_string()) - .or_else(|| current_config.settings.github_token.clone()) - .ok_or_else(|| { - AppError::UserError(format!( - "Can't call GitHub API for org {} because no github oauth token (settings.github_token) specified in the configuration.", - org_name - )) - })?; - let mut api = github::github_api(&token)?; - let org_repository_names: Vec = api.list_repositories(org_name, include_archived)?; - let after_clone = current_config.settings.default_after_clone.clone(); - let after_workon = current_config.settings.default_after_workon.clone(); - let tags = current_config.settings.default_tags.clone(); - let mut current_projects = current_config.projects; - - for name in org_repository_names { - let p = Project { - name: name.clone(), - git: format!("git@github.com:{}/{}.git", org_name, name), - after_clone: after_clone.clone(), - after_workon: after_workon.clone(), - override_path: None, - tags: tags.clone(), - additional_remotes: None, - bare: None, - trusted: false, - project_config_path: org_name.to_string(), - }; - - if current_projects.contains_key(&p.name) { - // "Skipping new project from Github import because it already exists in the current fw config - } else { - config::write_project(&p)?; - current_projects.insert(p.name.clone(), p); // to ensure no duplicated name encountered during processing + let current_config = maybe_config?; + let token = env::var_os("FW_GITHUB_TOKEN") + .map(|s| s.to_string_lossy().to_string()) + .or_else(|| current_config.settings.github_token.clone()) + .ok_or_else(|| { + AppError::UserError(format!( + "Can't call GitHub API for org {} because no github oauth token (settings.github_token) specified in the configuration.", + org_name + )) + })?; + let mut api = github::github_api(&token)?; + let org_repository_names: Vec = api.list_repositories(org_name, include_archived)?; + let after_clone = current_config.settings.default_after_clone.clone(); + let after_workon = current_config.settings.default_after_workon.clone(); + let tags = current_config.settings.default_tags.clone(); + let mut current_projects = current_config.projects; + + for name in org_repository_names { + let p = Project { + name: name.clone(), + git: format!("git@github.com:{}/{}.git", org_name, name), + after_clone: after_clone.clone(), + after_workon: after_workon.clone(), + override_path: None, + tags: tags.clone(), + additional_remotes: None, + bare: None, + trusted: false, + project_config_path: org_name.to_string(), + }; + + if current_projects.contains_key(&p.name) { + // "Skipping new project from Github import because it already exists in the current fw config + } else { + config::write_project(&p)?; + current_projects.insert(p.name.clone(), p); // to ensure no duplicated name encountered during processing + } } - } - Ok(()) + Ok(()) } pub fn import(maybe_config: Result, path: &str) -> Result<(), AppError> { - let path = fs::canonicalize(Path::new(path))?; - let project_path = path.to_str().ok_or(AppError::InternalError("project path is not valid unicode"))?.to_owned(); - let file_name = AppError::require(path.file_name(), AppError::UserError("Import path needs to be valid".to_string()))?; - let project_name: String = file_name.to_string_lossy().into_owned(); - let maybe_settings = maybe_config.ok().map(|c| c.settings); - let new_project = load_project(maybe_settings, path.clone(), &project_name)?; - let new_project_with_path = Project { - override_path: Some(project_path), - ..new_project - }; - config::write_project(&new_project_with_path)?; - Ok(()) + let path = fs::canonicalize(Path::new(path))?; + let project_path = path.to_str().ok_or(AppError::InternalError("project path is not valid unicode"))?.to_owned(); + let file_name = AppError::require(path.file_name(), AppError::UserError("Import path needs to be valid".to_string()))?; + let project_name: String = file_name.to_string_lossy().into_owned(); + let maybe_settings = maybe_config.ok().map(|c| c.settings); + let new_project = load_project(maybe_settings, path.clone(), &project_name)?; + let new_project_with_path = Project { + override_path: Some(project_path), + ..new_project + }; + config::write_project(&new_project_with_path)?; + Ok(()) } fn load_project(maybe_settings: Option, path_to_repo: PathBuf, name: &str) -> Result { - let repo: Repository = Repository::open(path_to_repo)?; - let remote = repo.find_remote("origin")?; - let url = remote - .url() - .ok_or_else(|| AppError::UserError(format!("invalid remote origin at {:?}", repo.path())))?; - Ok(Project { - name: name.to_owned(), - git: url.to_owned(), - after_clone: maybe_settings.clone().and_then(|s| s.default_after_clone), - after_workon: maybe_settings.clone().and_then(|s| s.default_after_workon), - override_path: None, - additional_remotes: None, // TODO: use remotes - tags: maybe_settings.and_then(|s| s.default_tags), - bare: None, - trusted: false, - project_config_path: "default".to_string(), - }) + let repo: Repository = Repository::open(path_to_repo)?; + let remote = repo.find_remote("origin")?; + let url = remote + .url() + .ok_or_else(|| AppError::UserError(format!("invalid remote origin at {:?}", repo.path())))?; + Ok(Project { + name: name.to_owned(), + git: url.to_owned(), + after_clone: maybe_settings.clone().and_then(|s| s.default_after_clone), + after_workon: maybe_settings.clone().and_then(|s| s.default_after_workon), + override_path: None, + additional_remotes: None, // TODO: use remotes + tags: maybe_settings.and_then(|s| s.default_tags), + bare: None, + trusted: false, + project_config_path: "default".to_string(), + }) } fn write_new_config_with_projects(projects: BTreeMap, workspace_dir: &str) -> Result<(), AppError> { - let settings: config::settings::PersistedSettings = config::settings::PersistedSettings { - workspace: workspace_dir.to_owned(), - default_after_workon: None, - default_after_clone: None, - shell: None, - github_token: None, - gitlab: None, - }; - config::write_settings(&settings)?; - for p in projects.values() { - config::write_project(p)?; - } - Ok(()) + let settings: config::settings::PersistedSettings = config::settings::PersistedSettings { + workspace: workspace_dir.to_owned(), + default_after_workon: None, + default_after_clone: None, + shell: None, + github_token: None, + gitlab: None, + }; + config::write_settings(&settings)?; + for p in projects.values() { + config::write_project(p)?; + } + Ok(()) } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 15893b26..e9885afb 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,53 +1,53 @@ use crate::errors::AppError; pub fn print_zsh_setup(use_fzf: bool, use_skim: bool) -> Result<(), AppError> { - let fw_completion = include_str!("setup.zsh"); - let basic_workon = include_str!("workon.zsh"); - let fzf_workon = include_str!("workon-fzf.zsh"); - let skim_workon = include_str!("workon-sk.zsh"); - println!("{}", fw_completion); - if use_fzf { - println!("{}", fzf_workon); - } else if use_skim { - println!("{}", skim_workon); - } else { - println!("{}", basic_workon); - } - Ok(()) + let fw_completion = include_str!("setup.zsh"); + let basic_workon = include_str!("workon.zsh"); + let fzf_workon = include_str!("workon-fzf.zsh"); + let skim_workon = include_str!("workon-sk.zsh"); + println!("{}", fw_completion); + if use_fzf { + println!("{}", fzf_workon); + } else if use_skim { + println!("{}", skim_workon); + } else { + println!("{}", basic_workon); + } + Ok(()) } pub fn print_bash_setup(use_fzf: bool, use_skim: bool) -> Result<(), AppError> { - let setup = include_str!("setup.bash"); - let basic = include_str!("workon.bash"); - let fzf = include_str!("workon-fzf.bash"); - let skim = include_str!("workon-sk.bash"); + let setup = include_str!("setup.bash"); + let basic = include_str!("workon.bash"); + let fzf = include_str!("workon-fzf.bash"); + let skim = include_str!("workon-sk.bash"); - println!("{}", setup); - if use_fzf { - println!("{}", fzf); - } else if use_skim { - println!("{}", skim) - } else { - println!("{}", basic); - } + println!("{}", setup); + if use_fzf { + println!("{}", fzf); + } else if use_skim { + println!("{}", skim) + } else { + println!("{}", basic); + } - Ok(()) + Ok(()) } pub fn print_fish_setup(use_fzf: bool, use_skim: bool) -> Result<(), AppError> { - let setup = include_str!("setup.fish"); - let basic = include_str!("workon.fish"); - let fzf = include_str!("workon-fzf.fish"); - let skim = include_str!("workon-sk.fish"); + let setup = include_str!("setup.fish"); + let basic = include_str!("workon.fish"); + let fzf = include_str!("workon-fzf.fish"); + let skim = include_str!("workon-sk.fish"); - println!("{}", setup); - if use_fzf { - println!("{}", fzf); - } else if use_skim { - println!("{}", skim); - } else { - println!("{}", basic); - } + println!("{}", setup); + if use_fzf { + println!("{}", fzf); + } else if use_skim { + println!("{}", skim); + } else { + println!("{}", basic); + } - Ok(()) + Ok(()) } diff --git a/src/spawn/mod.rs b/src/spawn/mod.rs index 5ef37e1c..efc5f064 100644 --- a/src/spawn/mod.rs +++ b/src/spawn/mod.rs @@ -16,107 +16,107 @@ use std::process::{Child, Command, Stdio}; use std::thread; fn forward_process_output_to_stdout(read: T, prefix: &str, color: Color, atty: bool, mark_err: bool) -> Result<(), AppError> { - let mut buf = BufReader::new(read); - loop { - let mut line = String::new(); - let read: usize = buf.read_line(&mut line)?; - if read == 0 { - break; + let mut buf = BufReader::new(read); + loop { + let mut line = String::new(); + let read: usize = buf.read_line(&mut line)?; + if read == 0 { + break; + } + if mark_err { + let prefix = format!("{:>21.21} |", prefix); + if atty { + print!("{} {} {}", Paint::red("ERR"), color.paint(prefix), line); + } else { + print!("ERR {} {}", prefix, line); + }; + } else { + let prefix = format!("{:>25.25} |", prefix); + if atty { + print!("{} {}", color.paint(prefix), line); + } else { + print!("{} {}", prefix, line); + }; + } } - if mark_err { - let prefix = format!("{:>21.21} |", prefix); - if atty { - print!("{} {} {}", Paint::red("ERR"), color.paint(prefix), line); - } else { - print!("ERR {} {}", prefix, line); - }; - } else { - let prefix = format!("{:>25.25} |", prefix); - if atty { - print!("{} {}", color.paint(prefix), line); - } else { - print!("{} {}", prefix, line); - }; - } - } - Ok(()) + Ok(()) } fn is_stdout_a_tty() -> bool { - std::io::stdout().is_terminal() + std::io::stdout().is_terminal() } fn is_stderr_a_tty() -> bool { - std::io::stderr().is_terminal() + std::io::stderr().is_terminal() } pub fn spawn_maybe(shell: &[String], cmd: &str, workdir: &Path, project_name: &str, color: Color) -> Result<(), AppError> { - let program: &str = shell - .first() - .ok_or_else(|| AppError::UserError("shell entry in project settings must have at least one element".to_owned()))?; - let rest: &[String] = shell.split_at(1).1; - let mut result: Child = Command::new(program) - .args(rest) - .arg(cmd) - .current_dir(workdir) - .env("FW_PROJECT", project_name) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .stdin(Stdio::null()) - .spawn()?; - - let stdout_child = if let Some(stdout) = result.stdout.take() { - let project_name = project_name.to_owned(); - Some(thread::spawn(move || { - let atty: bool = is_stdout_a_tty(); - forward_process_output_to_stdout(stdout, &project_name, color, atty, false) - })) - } else { - None - }; - - // stream stderr in this thread. no need to spawn another one. - if let Some(stderr) = result.stderr.take() { - let atty: bool = is_stderr_a_tty(); - forward_process_output_to_stdout(stderr, project_name, color, atty, true)? - } - - if let Some(child) = stdout_child { - child.join().expect("Must be able to join child")?; - } - - let status = result.wait()?; - if status.code().unwrap_or(0) > 0 { - Err(AppError::UserError("External command failed.".to_owned())) - } else { - Ok(()) - } + let program: &str = shell + .first() + .ok_or_else(|| AppError::UserError("shell entry in project settings must have at least one element".to_owned()))?; + let rest: &[String] = shell.split_at(1).1; + let mut result: Child = Command::new(program) + .args(rest) + .arg(cmd) + .current_dir(workdir) + .env("FW_PROJECT", project_name) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .spawn()?; + + let stdout_child = if let Some(stdout) = result.stdout.take() { + let project_name = project_name.to_owned(); + Some(thread::spawn(move || { + let atty: bool = is_stdout_a_tty(); + forward_process_output_to_stdout(stdout, &project_name, color, atty, false) + })) + } else { + None + }; + + // stream stderr in this thread. no need to spawn another one. + if let Some(stderr) = result.stderr.take() { + let atty: bool = is_stderr_a_tty(); + forward_process_output_to_stdout(stderr, project_name, color, atty, true)? + } + + if let Some(child) = stdout_child { + child.join().expect("Must be able to join child")?; + } + + let status = result.wait()?; + if status.code().unwrap_or(0) > 0 { + Err(AppError::UserError("External command failed.".to_owned())) + } else { + Ok(()) + } } pub fn init_threads(parallel_raw: &Option) -> Result<(), AppError> { - if let Some(ref raw_num) = *parallel_raw { - let num_threads = raw_num.parse::()?; - rayon::ThreadPoolBuilder::new().num_threads(num_threads).build_global().expect( - "Tried to initialize rayon more than once (this is a software bug on fw side, please file an issue at https://github.com/brocode/fw/issues/new )", - ); - } - Ok(()) + if let Some(ref raw_num) = *parallel_raw { + let num_threads = raw_num.parse::()?; + rayon::ThreadPoolBuilder::new().num_threads(num_threads).build_global().expect( + "Tried to initialize rayon more than once (this is a software bug on fw side, please file an issue at https://github.com/brocode/fw/issues/new )", + ); + } + Ok(()) } pub fn foreach(maybe_config: Result, cmd: &str, tags: &BTreeSet, parallel_raw: &Option) -> Result<(), AppError> { - let config = maybe_config?; - init_threads(parallel_raw)?; - - let projects: Vec<&Project> = config.projects.values().collect(); - let script_results = projects - .par_iter() - .filter(|p| tags.is_empty() || p.tags.clone().unwrap_or_default().intersection(tags).count() > 0) - .map(|p| { - let shell = config.settings.get_shell_or_default(); - let path = config.actual_path_to_project(p); - spawn_maybe(&shell, cmd, &path, &p.name, random_color()) - }) - .collect::>>(); - - script_results.into_iter().fold(Ok(()), Result::and) + let config = maybe_config?; + init_threads(parallel_raw)?; + + let projects: Vec<&Project> = config.projects.values().collect(); + let script_results = projects + .par_iter() + .filter(|p| tags.is_empty() || p.tags.clone().unwrap_or_default().intersection(tags).count() > 0) + .map(|p| { + let shell = config.settings.get_shell_or_default(); + let path = config.actual_path_to_project(p); + spawn_maybe(&shell, cmd, &path, &p.name, random_color()) + }) + .collect::>>(); + + script_results.into_iter().fold(Ok(()), Result::and) } diff --git a/src/sync/mod.rs b/src/sync/mod.rs index 7947e9c3..ee7cce2c 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs @@ -22,129 +22,129 @@ use std::thread; use std::os::unix::fs::FileTypeExt; fn sync_project(config: &Config, project: &Project, only_new: bool, ff_merge: bool) -> Result<(), AppError> { - let path = config.actual_path_to_project(project); - let exists = path.exists(); - let result = if exists { - if only_new { - Ok(()) + let path = config.actual_path_to_project(project); + let exists = path.exists(); + let result = if exists { + if only_new { + Ok(()) + } else { + update_project_remotes(project, &path, ff_merge).and_then(|_| synchronize_metadata_if_trusted(project, &path)) + } } else { - update_project_remotes(project, &path, ff_merge).and_then(|_| synchronize_metadata_if_trusted(project, &path)) - } - } else { - clone_project(config, project, &path).and_then(|_| synchronize_metadata_if_trusted(project, &path)) - }; - result.map_err(|e| AppError::RuntimeError(format!("Failed to sync {}: {}", project.name, e))) + clone_project(config, project, &path).and_then(|_| synchronize_metadata_if_trusted(project, &path)) + }; + result.map_err(|e| AppError::RuntimeError(format!("Failed to sync {}: {}", project.name, e))) } pub fn synchronize_metadata_if_trusted(project: &Project, path: &Path) -> Result<(), AppError> { - if !project.trusted { - Ok(()) - } else { - let metadata_file = path.join("fw.toml"); + if !project.trusted { + Ok(()) + } else { + let metadata_file = path.join("fw.toml"); - if metadata_file.exists() { - let content = read_to_string(metadata_file)?; - let metadata_from_repository = toml::from_str::(&content)?; + if metadata_file.exists() { + let content = read_to_string(metadata_file)?; + let metadata_from_repository = toml::from_str::(&content)?; - let new_project = Project { - tags: metadata_from_repository.tags, - ..project.to_owned() - }; + let new_project = Project { + tags: metadata_from_repository.tags, + ..project.to_owned() + }; - config::write_project(&new_project) - } else { - Ok(()) + config::write_project(&new_project) + } else { + Ok(()) + } } - } } pub fn synchronize(maybe_config: Result, only_new: bool, ff_merge: bool, tags: &BTreeSet, worker: i32) -> Result<(), AppError> { - eprintln!("Synchronizing everything"); - if !ssh_agent_running() { - eprintln!("SSH Agent not running. Process may hang.") - } - let config = Arc::new(maybe_config?); - - let projects: Vec = config.projects.values().map(ToOwned::to_owned).collect(); - let q: Arc> = Arc::new(SegQueue::new()); - let projects_count = projects.len() as u64; - - projects - .into_iter() - .filter(|p| tags.is_empty() || p.tags.clone().unwrap_or_default().intersection(tags).count() > 0) - .for_each(|p| q.push(p)); - - let spinner_style = ProgressStyle::default_spinner() - .tick_chars("⣾⣽⣻⢿⡿⣟⣯⣷⣿") - .template("{prefix:.bold.dim} {spinner} {wide_msg}") - .map_err(|e| AppError::RuntimeError(format!("Invalid Template: {}", e)))?; - - let m = MultiProgress::new(); - m.set_draw_target(ProgressDrawTarget::stderr()); - - let job_results: Arc>> = Arc::new(SegQueue::new()); - let progress_bars = (1..=worker).map(|i| { - let pb = m.add(ProgressBar::new(projects_count)); - pb.set_style(spinner_style.clone()); - pb.set_prefix(format!("[{: >2}/{}]", i, worker)); - pb.set_message("initializing..."); - pb.tick(); - pb.enable_steady_tick(Duration::from_millis(250)); - pb - }); - let mut thread_handles: Vec> = Vec::new(); - for pb in progress_bars { - let job_q = Arc::clone(&q); - let job_config = Arc::clone(&config); - let job_result_queue = Arc::clone(&job_results); - thread_handles.push(thread::spawn(move || { - let mut job_result: Result<(), AppError> = Result::Ok(()); - loop { - if let Some(project) = job_q.pop() { - pb.set_message(project.name.to_string()); - let sync_result = sync_project(&job_config, &project, only_new, ff_merge); - let msg = match sync_result { - Ok(_) => format!("DONE: {}", project.name), - Err(ref e) => format!("FAILED: {} - {}", project.name, e), - }; - pb.println(&msg); - job_result = job_result.and(sync_result); - } else { - pb.finish_and_clear(); - break; - } - } - job_result_queue.push(job_result); - })); - } + eprintln!("Synchronizing everything"); + if !ssh_agent_running() { + eprintln!("SSH Agent not running. Process may hang.") + } + let config = Arc::new(maybe_config?); + + let projects: Vec = config.projects.values().map(ToOwned::to_owned).collect(); + let q: Arc> = Arc::new(SegQueue::new()); + let projects_count = projects.len() as u64; + + projects + .into_iter() + .filter(|p| tags.is_empty() || p.tags.clone().unwrap_or_default().intersection(tags).count() > 0) + .for_each(|p| q.push(p)); + + let spinner_style = ProgressStyle::default_spinner() + .tick_chars("⣾⣽⣻⢿⡿⣟⣯⣷⣿") + .template("{prefix:.bold.dim} {spinner} {wide_msg}") + .map_err(|e| AppError::RuntimeError(format!("Invalid Template: {}", e)))?; + + let m = MultiProgress::new(); + m.set_draw_target(ProgressDrawTarget::stderr()); + + let job_results: Arc>> = Arc::new(SegQueue::new()); + let progress_bars = (1..=worker).map(|i| { + let pb = m.add(ProgressBar::new(projects_count)); + pb.set_style(spinner_style.clone()); + pb.set_prefix(format!("[{: >2}/{}]", i, worker)); + pb.set_message("initializing..."); + pb.tick(); + pb.enable_steady_tick(Duration::from_millis(250)); + pb + }); + let mut thread_handles: Vec> = Vec::new(); + for pb in progress_bars { + let job_q = Arc::clone(&q); + let job_config = Arc::clone(&config); + let job_result_queue = Arc::clone(&job_results); + thread_handles.push(thread::spawn(move || { + let mut job_result: Result<(), AppError> = Result::Ok(()); + loop { + if let Some(project) = job_q.pop() { + pb.set_message(project.name.to_string()); + let sync_result = sync_project(&job_config, &project, only_new, ff_merge); + let msg = match sync_result { + Ok(_) => format!("DONE: {}", project.name), + Err(ref e) => format!("FAILED: {} - {}", project.name, e), + }; + pb.println(&msg); + job_result = job_result.and(sync_result); + } else { + pb.finish_and_clear(); + break; + } + } + job_result_queue.push(job_result); + })); + } - while let Some(cur_thread) = thread_handles.pop() { - cur_thread.join().unwrap(); - } + while let Some(cur_thread) = thread_handles.pop() { + cur_thread.join().unwrap(); + } - let mut synchronize_result: Result<(), AppError> = Result::Ok(()); - while let Some(result) = job_results.pop() { - synchronize_result = synchronize_result.and(result); - } + let mut synchronize_result: Result<(), AppError> = Result::Ok(()); + while let Some(result) = job_results.pop() { + synchronize_result = synchronize_result.and(result); + } - m.clear().unwrap(); + m.clear().unwrap(); - synchronize_result + synchronize_result } fn ssh_agent_running() -> bool { - match std::env::var("SSH_AUTH_SOCK") { - Ok(auth_socket) => is_socket(&auth_socket), - Err(_) => false, - } + match std::env::var("SSH_AUTH_SOCK") { + Ok(auth_socket) => is_socket(&auth_socket), + Err(_) => false, + } } #[cfg(unix)] fn is_socket(path: &str) -> bool { - std::fs::metadata(path).map(|m| m.file_type().is_socket()).unwrap_or(false) + std::fs::metadata(path).map(|m| m.file_type().is_socket()).unwrap_or(false) } #[cfg(not(unix))] fn is_socket(_: &str) -> bool { - false + false } diff --git a/src/tag/mod.rs b/src/tag/mod.rs index 14671ed6..9232ca68 100644 --- a/src/tag/mod.rs +++ b/src/tag/mod.rs @@ -10,171 +10,171 @@ use std::collections::{BTreeMap, BTreeSet}; use yansi::Paint; pub fn list_tags(maybe_config: Result, maybe_project_name: Option) -> Result<(), AppError> { - let config: Config = maybe_config?; - if let Some(project_name) = maybe_project_name { - list_project_tags(&config, &project_name) - } else { - list_all_tags(config); - Ok(()) - } + let config: Config = maybe_config?; + if let Some(project_name) = maybe_project_name { + list_project_tags(&config, &project_name) + } else { + list_all_tags(config); + Ok(()) + } } pub fn delete_tag(maybe_config: Result, tag_name: &str) -> Result<(), AppError> { - let config: Config = maybe_config?; - let tags: BTreeMap = config.settings.tags.unwrap_or_default(); - - // remove tags from projects - for mut project in config.projects.values().cloned() { - let mut new_tags: BTreeSet = project.tags.clone().unwrap_or_default(); - if new_tags.remove(tag_name) { - project.tags = Some(new_tags); - config::write_project(&project)?; + let config: Config = maybe_config?; + let tags: BTreeMap = config.settings.tags.unwrap_or_default(); + + // remove tags from projects + for mut project in config.projects.values().cloned() { + let mut new_tags: BTreeSet = project.tags.clone().unwrap_or_default(); + if new_tags.remove(tag_name) { + project.tags = Some(new_tags); + config::write_project(&project)?; + } } - } - if let Some(tag) = tags.get(tag_name) { - config::delete_tag_config(tag_name, tag) - } else { - Ok(()) - } + if let Some(tag) = tags.get(tag_name) { + config::delete_tag_config(tag_name, tag) + } else { + Ok(()) + } } fn list_all_tags(config: Config) { - if let Some(tags) = config.settings.tags { - for tag_name in tags.keys() { - println!("{}", tag_name); + if let Some(tags) = config.settings.tags { + for tag_name in tags.keys() { + println!("{}", tag_name); + } } - } } pub fn add_tag(config: &Config, project_name: String, tag_name: String) -> Result<(), AppError> { - if let Some(mut project) = config.projects.get(&project_name).cloned() { - let tags: BTreeMap = config.settings.tags.clone().unwrap_or_default(); - if tags.contains_key(&tag_name) { - let mut new_tags: BTreeSet = project.tags.clone().unwrap_or_default(); - new_tags.insert(tag_name); - project.tags = Some(new_tags); - config::write_project(&project)?; - Ok(()) + if let Some(mut project) = config.projects.get(&project_name).cloned() { + let tags: BTreeMap = config.settings.tags.clone().unwrap_or_default(); + if tags.contains_key(&tag_name) { + let mut new_tags: BTreeSet = project.tags.clone().unwrap_or_default(); + new_tags.insert(tag_name); + project.tags = Some(new_tags); + config::write_project(&project)?; + Ok(()) + } else { + Err(AppError::UserError(format!("Unknown tag {}", tag_name))) + } } else { - Err(AppError::UserError(format!("Unknown tag {}", tag_name))) + Err(AppError::UserError(format!("Unknown project {}", project_name))) } - } else { - Err(AppError::UserError(format!("Unknown project {}", project_name))) - } } pub fn create_tag( - maybe_config: Result, - tag_name: String, - after_workon: Option, - after_clone: Option, - priority: Option, - tag_workspace: Option, + maybe_config: Result, + tag_name: String, + after_workon: Option, + after_clone: Option, + priority: Option, + tag_workspace: Option, ) -> Result<(), AppError> { - let config: Config = maybe_config?; - let tags: BTreeMap = config.settings.tags.unwrap_or_default(); - - if tags.contains_key(&tag_name) { - Err(AppError::UserError(format!("Tag {} already exists, not gonna overwrite it for you", tag_name))) - } else { - let new_tag = Tag { - after_clone, - after_workon, - priority, - workspace: tag_workspace, - default: None, - tag_config_path: "default".to_string(), - }; - config::write_tag(&tag_name, &new_tag)?; - Ok(()) - } + let config: Config = maybe_config?; + let tags: BTreeMap = config.settings.tags.unwrap_or_default(); + + if tags.contains_key(&tag_name) { + Err(AppError::UserError(format!("Tag {} already exists, not gonna overwrite it for you", tag_name))) + } else { + let new_tag = Tag { + after_clone, + after_workon, + priority, + workspace: tag_workspace, + default: None, + tag_config_path: "default".to_string(), + }; + config::write_tag(&tag_name, &new_tag)?; + Ok(()) + } } pub fn inspect_tag(maybe_config: Result, tag_name: &str) -> Result<(), AppError> { - let config: Config = maybe_config?; - let tags: BTreeMap = config.settings.tags.unwrap_or_default(); - if let Some(tag) = tags.get(tag_name) { - println!("{}", Paint::new(tag_name).bold().underline()); - println!("{:<20}: {}", "config path", tag.tag_config_path); - println!("{:<20}: {}", "after workon", tag.after_workon.clone().unwrap_or_default()); - println!("{:<20}: {}", "after clone", tag.after_clone.clone().unwrap_or_default()); - println!("{:<20}: {}", "priority", tag.priority.map(|n| n.to_string()).unwrap_or_default()); - println!("{:<20}: {}", "workspace", tag.workspace.clone().unwrap_or_default()); - println!("{:<20}: {}", "default", tag.default.map(|n| n.to_string()).unwrap_or_default()); - println!(); - println!("{}", Paint::new("projects".to_string()).bold().underline()); - for project in config.projects.values().cloned() { - if project.tags.unwrap_or_default().contains(tag_name) { - println!("{}", project.name) - } + let config: Config = maybe_config?; + let tags: BTreeMap = config.settings.tags.unwrap_or_default(); + if let Some(tag) = tags.get(tag_name) { + println!("{}", Paint::new(tag_name).bold().underline()); + println!("{:<20}: {}", "config path", tag.tag_config_path); + println!("{:<20}: {}", "after workon", tag.after_workon.clone().unwrap_or_default()); + println!("{:<20}: {}", "after clone", tag.after_clone.clone().unwrap_or_default()); + println!("{:<20}: {}", "priority", tag.priority.map(|n| n.to_string()).unwrap_or_default()); + println!("{:<20}: {}", "workspace", tag.workspace.clone().unwrap_or_default()); + println!("{:<20}: {}", "default", tag.default.map(|n| n.to_string()).unwrap_or_default()); + println!(); + println!("{}", Paint::new("projects".to_string()).bold().underline()); + for project in config.projects.values().cloned() { + if project.tags.unwrap_or_default().contains(tag_name) { + println!("{}", project.name) + } + } + Ok(()) + } else { + Err(AppError::UserError(format!("Unkown tag {}", tag_name))) } - Ok(()) - } else { - Err(AppError::UserError(format!("Unkown tag {}", tag_name))) - } } pub fn remove_tag(maybe_config: Result, project_name: String, tag_name: &str) -> Result<(), AppError> { - let config: Config = maybe_config?; - - if let Some(mut project) = config.projects.get(&project_name).cloned() { - let mut new_tags: BTreeSet = project.tags.clone().unwrap_or_default(); - if new_tags.remove(tag_name) { - project.tags = Some(new_tags); - config::write_project(&project) + let config: Config = maybe_config?; + + if let Some(mut project) = config.projects.get(&project_name).cloned() { + let mut new_tags: BTreeSet = project.tags.clone().unwrap_or_default(); + if new_tags.remove(tag_name) { + project.tags = Some(new_tags); + config::write_project(&project) + } else { + Ok(()) + } } else { - Ok(()) + Err(AppError::UserError(format!("Unknown project {}", project_name))) } - } else { - Err(AppError::UserError(format!("Unknown project {}", project_name))) - } } fn list_project_tags(config: &Config, project_name: &str) -> Result<(), AppError> { - if let Some(project) = config.projects.get(project_name) { - if let Some(tags) = project.clone().tags { - for tag_name in tags { - println!("{}", tag_name); - } + if let Some(project) = config.projects.get(project_name) { + if let Some(tags) = project.clone().tags { + for tag_name in tags { + println!("{}", tag_name); + } + } + Ok(()) + } else { + Err(AppError::UserError(format!("Unknown project {}", project_name))) } - Ok(()) - } else { - Err(AppError::UserError(format!("Unknown project {}", project_name))) - } } pub fn autotag(maybe_config: Result, cmd: &str, tag_name: &str, parallel_raw: &Option) -> Result<(), AppError> { - let config = maybe_config?; - - let tags: BTreeMap = config.settings.tags.clone().unwrap_or_default(); - if tags.contains_key(tag_name) { - init_threads(parallel_raw)?; - - let projects: Vec<&Project> = config.projects.values().collect(); - - let script_results = projects - .par_iter() - .map(|p| { - let shell = config.settings.get_shell_or_default(); - let path = &config.actual_path_to_project(p); - spawn_maybe(&shell, cmd, path, &p.name, random_color()) - }) - .collect::>>(); - - // map with projects and filter if result == 0 - let filtered_projects: Vec<&Project> = script_results - .into_iter() - .zip(projects) - .filter(|(x, _)| x.is_ok()) - .map(|(_, p)| p) - .collect::>(); - - for project in filtered_projects.iter() { - add_tag(&config, project.name.clone(), tag_name.to_string())?; + let config = maybe_config?; + + let tags: BTreeMap = config.settings.tags.clone().unwrap_or_default(); + if tags.contains_key(tag_name) { + init_threads(parallel_raw)?; + + let projects: Vec<&Project> = config.projects.values().collect(); + + let script_results = projects + .par_iter() + .map(|p| { + let shell = config.settings.get_shell_or_default(); + let path = &config.actual_path_to_project(p); + spawn_maybe(&shell, cmd, path, &p.name, random_color()) + }) + .collect::>>(); + + // map with projects and filter if result == 0 + let filtered_projects: Vec<&Project> = script_results + .into_iter() + .zip(projects) + .filter(|(x, _)| x.is_ok()) + .map(|(_, p)| p) + .collect::>(); + + for project in filtered_projects.iter() { + add_tag(&config, project.name.clone(), tag_name.to_string())?; + } + Ok(()) + } else { + Err(AppError::UserError(format!("Unknown tag {}", tag_name))) } - Ok(()) - } else { - Err(AppError::UserError(format!("Unknown tag {}", tag_name))) - } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 0a7a3453..69c0c067 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -5,21 +5,21 @@ use rand::seq::SliceRandom; use std::borrow::ToOwned; pub static COLOURS: [Color; 12] = [ - Color::Green, - Color::Cyan, - Color::Blue, - Color::Yellow, - Color::Red, - Color::RGB(255, 165, 0), - Color::RGB(255, 99, 71), - Color::RGB(0, 153, 255), - Color::RGB(153, 102, 51), - Color::RGB(102, 153, 0), - Color::RGB(255, 153, 255), - Color::Magenta, + Color::Green, + Color::Cyan, + Color::Blue, + Color::Yellow, + Color::Red, + Color::RGB(255, 165, 0), + Color::RGB(255, 99, 71), + Color::RGB(0, 153, 255), + Color::RGB(153, 102, 51), + Color::RGB(102, 153, 0), + Color::RGB(255, 153, 255), + Color::Magenta, ]; pub fn random_color() -> Color { - let mut rng = rand::thread_rng(); - COLOURS.choose(&mut rng).map(ToOwned::to_owned).unwrap_or(Color::Black) + let mut rng = rand::thread_rng(); + COLOURS.choose(&mut rng).map(ToOwned::to_owned).unwrap_or(Color::Black) } diff --git a/src/workon/mod.rs b/src/workon/mod.rs index 715ff747..b1448344 100644 --- a/src/workon/mod.rs +++ b/src/workon/mod.rs @@ -8,52 +8,52 @@ use std::env; use yansi::Color; pub fn gen_reworkon(maybe_config: Result) -> Result<(), AppError> { - let config = maybe_config?; - let project = current_project(&config)?; - gen(&project.name, Ok(config), false) + let config = maybe_config?; + let project = current_project(&config)?; + gen(&project.name, Ok(config), false) } fn current_project(config: &config::Config) -> Result { - let os_current_dir = env::current_dir()?; - let current_dir = os_current_dir.to_string_lossy().into_owned(); - let maybe_match = config - .projects - .values() - .find(|&p| config.actual_path_to_project(p).to_string_lossy().eq(¤t_dir)); - maybe_match - .map(ToOwned::to_owned) - .ok_or_else(|| AppError::UserError(format!("No project matching expanded path {} found in config", current_dir))) + let os_current_dir = env::current_dir()?; + let current_dir = os_current_dir.to_string_lossy().into_owned(); + let maybe_match = config + .projects + .values() + .find(|&p| config.actual_path_to_project(p).to_string_lossy().eq(¤t_dir)); + maybe_match + .map(ToOwned::to_owned) + .ok_or_else(|| AppError::UserError(format!("No project matching expanded path {} found in config", current_dir))) } pub fn reworkon(maybe_config: Result) -> Result<(), AppError> { - let config = maybe_config?; - let project = current_project(&config)?; - let path = config.actual_path_to_project(&project); - let mut commands: Vec = vec![format!("cd {}", path.to_string_lossy())]; - commands.extend_from_slice(&config.resolve_after_workon(&project)); + let config = maybe_config?; + let project = current_project(&config)?; + let path = config.actual_path_to_project(&project); + let mut commands: Vec = vec![format!("cd {}", path.to_string_lossy())]; + commands.extend_from_slice(&config.resolve_after_workon(&project)); - let shell = config.settings.get_shell_or_default(); - spawn_maybe(&shell, &commands.join(" && "), &path, &project.name, Color::Yellow) + let shell = config.settings.get_shell_or_default(); + spawn_maybe(&shell, &commands.join(" && "), &path, &project.name, Color::Yellow) } pub fn gen(name: &str, maybe_config: Result, quick: bool) -> Result<(), AppError> { - let config = maybe_config?; - let project: &Project = config - .projects - .get(name) - .ok_or_else(|| AppError::UserError(format!("project key {} not found in fw.json", name)))?; - let canonical_project_path = config.actual_path_to_project(project); - let path = canonical_project_path - .to_str() - .ok_or(AppError::InternalError("project path is not valid unicode"))?; - if !canonical_project_path.exists() { - Err(AppError::UserError(format!("project key {} found but path {} does not exist", name, path))) - } else { - let mut commands: Vec = vec![format!("cd '{}'", path)]; - if !quick { - commands.extend_from_slice(&config.resolve_after_workon(project)) + let config = maybe_config?; + let project: &Project = config + .projects + .get(name) + .ok_or_else(|| AppError::UserError(format!("project key {} not found in fw.json", name)))?; + let canonical_project_path = config.actual_path_to_project(project); + let path = canonical_project_path + .to_str() + .ok_or(AppError::InternalError("project path is not valid unicode"))?; + if !canonical_project_path.exists() { + Err(AppError::UserError(format!("project key {} found but path {} does not exist", name, path))) + } else { + let mut commands: Vec = vec![format!("cd '{}'", path)]; + if !quick { + commands.extend_from_slice(&config.resolve_after_workon(project)) + } + println!("{}", commands.join(" && ")); + Ok(()) } - println!("{}", commands.join(" && ")); - Ok(()) - } } diff --git a/src/ws/github/mod.rs b/src/ws/github/mod.rs index d594918a..3746dd58 100644 --- a/src/ws/github/mod.rs +++ b/src/ws/github/mod.rs @@ -6,123 +6,123 @@ use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; pub fn github_api(token: &str) -> Result { - let client = reqwest::blocking::Client::new(); - Ok(GithubApi { - client, - token: token.to_string(), - }) + let client = reqwest::blocking::Client::new(); + Ok(GithubApi { + client, + token: token.to_string(), + }) } pub struct GithubApi { - client: reqwest::blocking::Client, - token: String, + client: reqwest::blocking::Client, + token: String, } struct PageResult { - repository_names: Vec, - next_cursor: Option, + repository_names: Vec, + next_cursor: Option, } #[derive(Serialize, Deserialize, Debug)] struct OrganizationQueryResponse { - data: OrganizationQueryResponseData, + data: OrganizationQueryResponseData, } #[derive(Serialize, Deserialize, Debug)] struct OrganizationQueryResponseData { - organization: OrganizationRepositoriesResponseData, + organization: OrganizationRepositoriesResponseData, } #[derive(Serialize, Deserialize, Debug)] struct OrganizationRepositoriesResponseData { - repositories: RepositoriesResponseData, + repositories: RepositoriesResponseData, } #[derive(Serialize, Deserialize, Debug)] struct RepositoriesResponseData { - nodes: Vec, - #[serde(rename = "pageInfo")] - page_info: PageInfo, + nodes: Vec, + #[serde(rename = "pageInfo")] + page_info: PageInfo, } #[derive(Serialize, Deserialize, Debug)] struct Repository { - name: String, - #[serde(rename = "isArchived")] - is_archived: bool, + name: String, + #[serde(rename = "isArchived")] + is_archived: bool, } #[derive(Serialize, Deserialize, Debug)] struct PageInfo { - #[serde(rename = "endCursor")] - end_cursor: String, - #[serde(rename = "hasNextPage")] - has_next_page: bool, + #[serde(rename = "endCursor")] + end_cursor: String, + #[serde(rename = "hasNextPage")] + has_next_page: bool, } impl GithubApi { - fn query(&self, query: &str) -> Result { - //escaping new lines and quotation marks for json - let mut escaped = query.to_string(); - escaped = escaped.replace('\n', "\\n"); - escaped = escaped.replace('\"', "\\\""); - - let mut q = String::from("{ \"query\": \""); - q.push_str(&escaped); - q.push_str("\" }"); - - let res = self - .client - .post("https://api.github.com/graphql") - .body(reqwest::blocking::Body::from(q)) - .header("Content-Type", "application/json") - .header("User-Agent", "github-rs") - .header("Authorization", format!("token {}", self.token)) - .send()?; - - if res.status().is_success() { - res.json::().map_err(|e| AppError::RuntimeError(format!("Failed to parse response: {}", e))) - } else { - Err(AppError::RuntimeError(format!("Bad status from github {}", res.status()))) + fn query(&self, query: &str) -> Result { + //escaping new lines and quotation marks for json + let mut escaped = query.to_string(); + escaped = escaped.replace('\n', "\\n"); + escaped = escaped.replace('\"', "\\\""); + + let mut q = String::from("{ \"query\": \""); + q.push_str(&escaped); + q.push_str("\" }"); + + let res = self + .client + .post("https://api.github.com/graphql") + .body(reqwest::blocking::Body::from(q)) + .header("Content-Type", "application/json") + .header("User-Agent", "github-rs") + .header("Authorization", format!("token {}", self.token)) + .send()?; + + if res.status().is_success() { + res.json::().map_err(|e| AppError::RuntimeError(format!("Failed to parse response: {}", e))) + } else { + Err(AppError::RuntimeError(format!("Bad status from github {}", res.status()))) + } } - } - pub fn list_repositories(&mut self, org: &str, include_archived: bool) -> Result, AppError> { - let initial_page = self.page_repositories(org, None, include_archived)?; - let mut initial_names = initial_page.repository_names; + pub fn list_repositories(&mut self, org: &str, include_archived: bool) -> Result, AppError> { + let initial_page = self.page_repositories(org, None, include_archived)?; + let mut initial_names = initial_page.repository_names; - let mut next: Option = initial_page.next_cursor; - while next.is_some() { - let next_repos = self.page_repositories(org, next.clone(), include_archived)?; - initial_names.extend(next_repos.repository_names); - next = next_repos.next_cursor; - } + let mut next: Option = initial_page.next_cursor; + while next.is_some() { + let next_repos = self.page_repositories(org, next.clone(), include_archived)?; + initial_names.extend(next_repos.repository_names); + next = next_repos.next_cursor; + } - Ok(initial_names) - } - fn page_repositories(&mut self, org: &str, after: Option, include_archived: bool) -> Result { - let after_refinement = after.map(|a| format!(", after:\"{}\"", a)).unwrap_or_else(|| "".to_owned()); - let response: OrganizationQueryResponse = self.query( - &("query {organization(login: \"".to_owned() - + org - + "\"){repositories(first: 100" - + &after_refinement - + ") {nodes {name, isArchived} pageInfo {endCursor hasNextPage}}}}"), - )?; - let repositories: Vec = response.data.organization.repositories.nodes; - let repo_names: Vec = repositories - .into_iter() - .filter(|r| include_archived || !r.is_archived) - .map(|r| r.name) - .collect(); - - Ok(PageResult { - repository_names: repo_names, - next_cursor: if response.data.organization.repositories.page_info.has_next_page { - Some(response.data.organization.repositories.page_info.end_cursor) - } else { - None - }, - }) - } + Ok(initial_names) + } + fn page_repositories(&mut self, org: &str, after: Option, include_archived: bool) -> Result { + let after_refinement = after.map(|a| format!(", after:\"{}\"", a)).unwrap_or_else(|| "".to_owned()); + let response: OrganizationQueryResponse = self.query( + &("query {organization(login: \"".to_owned() + + org + + "\"){repositories(first: 100" + + &after_refinement + + ") {nodes {name, isArchived} pageInfo {endCursor hasNextPage}}}}"), + )?; + let repositories: Vec = response.data.organization.repositories.nodes; + let repo_names: Vec = repositories + .into_iter() + .filter(|r| include_archived || !r.is_archived) + .map(|r| r.name) + .collect(); + + Ok(PageResult { + repository_names: repo_names, + next_cursor: if response.data.organization.repositories.page_info.has_next_page { + Some(response.data.organization.repositories.page_info.end_cursor) + } else { + None + }, + }) + } }