diff --git a/docs/config/README.md b/docs/config/README.md index b54fe3db..8b7a6106 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -502,6 +502,37 @@ the module will be activated if any of the following conditions are met: \*: This variable can only be used as a part of a style string +## COBOL / GNUCOBOL + +The `cobol` module shows the currently installed version of COBOL. +By default, the module will be shown if any of the following conditions are met: + +- The current directory contains any files ending in `.cob` or `.COB` +- The current directory contains any files ending in `.cbl` or `.CBL` + +### Options + +| Option | Default | Description | +| ------------------- | ------------------------------------ | ------------------------------------------------------------------------ | +| `symbol` | `"⚙️ "` | The symbol used before displaying the version of COBOL. | +| `format` | `"via [$symbol($version )]($style)"` | The format for the module. | +| `version_format` | `"v${raw}"` | The version format. Available vars are `raw`, `major`, `minor`, & `patch`| +| `style` | `"bold blue"` | The style for the module. | +| `detect_extensions` | `["cbl", "cob", "CBL", "COB"]` | Which extensions should trigger this module. | +| `detect_files` | `[]` | Which filenames should trigger this module. | +| `detect_folders` | `[]` | Which folders should trigger this module. | +| `disabled` | `false` | Disables the `cobol` module. | + +### Variables + +| Variable | Example | Description | +| -------- | --------- | ------------------------------------ | +| version | `v3.1.2.0`| The version of `cobol` | +| symbol | | Mirrors the value of option `symbol` | +| style\* | | Mirrors the value of option `style` | + +\*: This variable can only be used as a part of a style string + ## Command Duration The `cmd_duration` module shows how long the last command took to execute. diff --git a/docs/presets/README.md b/docs/presets/README.md index 4754f452..7c8ce2ee 100644 --- a/docs/presets/README.md +++ b/docs/presets/README.md @@ -115,6 +115,9 @@ format = '\[[$symbol($version)]($style)\]' [cmd_duration] format = '\[[⏱ $duration ]($style)\]' +[cobol] +format = '\[[$symbol($version)]($style)\]' + [conda] format = '\[[$symbol$environment]($style)\]' @@ -276,6 +279,9 @@ deleted = "x" [aws] symbol = "aws " +[cobol] +symbol = "cobol " + [conda] symbol = "conda " @@ -379,6 +385,9 @@ This preset hides the version of language runtimes. If you work in containers or [cmake] format = "via [$symbol]($style)" +[cobol] +format = "via [$symbol]($style)" + [crystal] format = "via [$symbol]($style)" diff --git a/src/configs/cobol.rs b/src/configs/cobol.rs new file mode 100644 index 00000000..b5612398 --- /dev/null +++ b/src/configs/cobol.rs @@ -0,0 +1,31 @@ +use crate::config::ModuleConfig; + +use serde::Serialize; +use starship_module_config_derive::ModuleConfig; + +#[derive(Clone, ModuleConfig, Serialize)] +pub struct CobolConfig<'a> { + pub format: &'a str, + pub version_format: &'a str, + pub symbol: &'a str, + pub style: &'a str, + pub disabled: bool, + pub detect_extensions: Vec<&'a str>, + pub detect_files: Vec<&'a str>, + pub detect_folders: Vec<&'a str>, +} + +impl<'a> Default for CobolConfig<'a> { + fn default() -> Self { + CobolConfig { + format: "via [$symbol($version )]($style)", + version_format: "v${raw}", + symbol: "⚙️ ", + style: "bold blue", + disabled: false, + detect_extensions: vec!["cbl", "cob", "CBL", "COB"], + detect_files: vec![], + detect_folders: vec![], + } + } +} diff --git a/src/configs/mod.rs b/src/configs/mod.rs index efa41cf3..f403da00 100644 --- a/src/configs/mod.rs +++ b/src/configs/mod.rs @@ -8,6 +8,7 @@ pub mod battery; pub mod character; pub mod cmake; pub mod cmd_duration; +pub mod cobol; pub mod conda; pub mod crystal; pub mod custom; @@ -83,6 +84,7 @@ pub struct FullConfig<'a> { character: character::CharacterConfig<'a>, cmake: cmake::CMakeConfig<'a>, cmd_duration: cmd_duration::CmdDurationConfig<'a>, + cobol: cobol::CobolConfig<'a>, conda: conda::CondaConfig<'a>, crystal: crystal::CrystalConfig<'a>, dart: dart::DartConfig<'a>, @@ -155,6 +157,7 @@ impl<'a> Default for FullConfig<'a> { character: Default::default(), cmake: Default::default(), cmd_duration: Default::default(), + cobol: Default::default(), conda: Default::default(), crystal: Default::default(), dart: Default::default(), diff --git a/src/configs/starship_root.rs b/src/configs/starship_root.rs index b4478db3..07500fbf 100644 --- a/src/configs/starship_root.rs +++ b/src/configs/starship_root.rs @@ -34,6 +34,7 @@ pub const PROMPT_ORDER: &[&str] = &[ // ↓ Toolchain version modules ↓ // (Let's keep these sorted alphabetically) "cmake", + "cobol", "dart", "deno", "dotnet", diff --git a/src/module.rs b/src/module.rs index eb8874c4..b8110932 100644 --- a/src/module.rs +++ b/src/module.rs @@ -14,6 +14,7 @@ pub const ALL_MODULES: &[&str] = &[ "character", "cmake", "cmd_duration", + "cobol", "conda", "crystal", "dart", diff --git a/src/modules/cobol.rs b/src/modules/cobol.rs new file mode 100644 index 00000000..7f0642c3 --- /dev/null +++ b/src/modules/cobol.rs @@ -0,0 +1,141 @@ +use super::{Context, Module, RootModuleConfig}; + +use crate::configs::cobol::CobolConfig; +use crate::formatter::StringFormatter; +use crate::formatter::VersionFormatter; + +/// Creates a module with the current COBOL version +pub fn module<'a>(context: &'a Context) -> Option> { + let mut module = context.new_module("cobol"); + let config = CobolConfig::try_load(module.config); + let is_cobol_project = context + .try_begin_scan()? + .set_files(&config.detect_files) + .set_extensions(&config.detect_extensions) + .set_folders(&config.detect_folders) + .is_match(); + + if !is_cobol_project { + return None; + } + + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_meta(|var, _| match var { + "symbol" => Some(config.symbol), + _ => None, + }) + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + }) + .map(|variable| match variable { + "version" => { + let cobol_version = + get_cobol_version(&context.exec_cmd("cobc", &["-version"])?.stdout)?; + + VersionFormatter::format_module_version( + module.get_name(), + &cobol_version, + config.version_format, + ) + .map(Ok) + } + _ => None, + }) + .parse(None) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `cobol`:\n{}", error); + return None; + } + }); + + Some(module) +} + +fn get_cobol_version(cobol_stdout: &str) -> Option { + // cobol output looks like this: + // cobc (GnuCOBOL) 3.1.2.0 + // ... + + Some( + cobol_stdout + // split into ["cobc", "(GNUCOBOL)", "3.1.2.0"...] + .split_whitespace() + // return "3.1.2.0" + .nth(2)? + .to_string(), + ) +} + +#[cfg(test)] +mod tests { + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::fs::File; + use std::io; + + #[test] + fn folder_without_cobol_files() -> io::Result<()> { + let dir = tempfile::tempdir()?; + + let actual = ModuleRenderer::new("cobol").path(dir.path()).collect(); + + let expected = None; + + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_lowercase_cob_files() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.cob"))?.sync_all()?; + + let actual = ModuleRenderer::new("cobol").path(dir.path()).collect(); + + let expected = Some(format!("via {}", Color::Blue.bold().paint("⚙️ v3.1.2.0 "))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_lowercase_cbl_files() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.cbl"))?.sync_all()?; + + let actual = ModuleRenderer::new("cobol").path(dir.path()).collect(); + + let expected = Some(format!("via {}", Color::Blue.bold().paint("⚙️ v3.1.2.0 "))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_capital_cob_files() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("MAIN.COB"))?.sync_all()?; + + let actual = ModuleRenderer::new("cobol").path(dir.path()).collect(); + + let expected = Some(format!("via {}", Color::Blue.bold().paint("⚙️ v3.1.2.0 "))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_capital_cbl_files() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("MAIN.CBL"))?.sync_all()?; + + let actual = ModuleRenderer::new("cobol").path(dir.path()).collect(); + + let expected = Some(format!("via {}", Color::Blue.bold().paint("⚙️ v3.1.2.0 "))); + assert_eq!(expected, actual); + dir.close() + } +} diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 3742fe13..ccc36070 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -3,6 +3,7 @@ mod aws; mod character; mod cmake; mod cmd_duration; +mod cobol; mod conda; mod crystal; pub(crate) mod custom; @@ -85,6 +86,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option> { "character" => character::module(context), "cmake" => cmake::module(context), "cmd_duration" => cmd_duration::module(context), + "cobol" => cobol::module(context), "conda" => conda::module(context), "dart" => dart::module(context), "deno" => deno::module(context), @@ -169,6 +171,7 @@ pub fn description(module: &str) -> &'static str { } "cmake" => "The currently installed version of CMake", "cmd_duration" => "How long the last command took to execute", + "cobol" => "The currently installed version of COBOL/GNUCOBOL", "conda" => "The current conda environment, if $CONDA_DEFAULT_ENV is set", "crystal" => "The currently installed version of Crystal", "dart" => "The currently installed version of Dart", diff --git a/src/utils.rs b/src/utils.rs index 147b6818..a6c26ded 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -102,6 +102,19 @@ pub fn exec_cmd + Debug, U: AsRef + Debug>( ) -> Option { let command = display_command(&cmd, args); match command.as_str() { + "cobc -version" => Some(CommandOutput { + stdout: String::from("\ +cobc (GnuCOBOL) 3.1.2.0 +Copyright (C) 2020 Free Software Foundation, Inc. +License GPLv3+: GNU GPL version 3 or later +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +Written by Keisuke Nishida, Roger While, Ron Norman, Simon Sobisch, Edward Hart +Built Dec 24 2020 19:08:58 +Packaged Dec 23 2020 12:04:58 UTC +C version \"10.2.0\""), + stderr: String::default(), + }), "crystal --version" => Some(CommandOutput { stdout: String::from( "\