diff --git a/.github/config-schema.json b/.github/config-schema.json index 2733295f9..2c01b2d34 100644 --- a/.github/config-schema.json +++ b/.github/config-schema.json @@ -1045,6 +1045,25 @@ } ] }, + "mojo": { + "default": { + "detect_extensions": [ + "mojo", + "🔥" + ], + "detect_files": [], + "detect_folders": [], + "disabled": false, + "format": "with [$symbol($version )]($style)", + "style": "bold 208", + "symbol": "🔥 " + }, + "allOf": [ + { + "$ref": "#/definitions/MojoConfig" + } + ] + }, "nats": { "default": { "disabled": true, @@ -4493,6 +4512,52 @@ }, "additionalProperties": false }, + "MojoConfig": { + "type": "object", + "properties": { + "format": { + "default": "with [$symbol($version )]($style)", + "type": "string" + }, + "symbol": { + "default": "🔥 ", + "type": "string" + }, + "style": { + "default": "bold 208", + "type": "string" + }, + "disabled": { + "default": false, + "type": "boolean" + }, + "detect_extensions": { + "default": [ + "mojo", + "🔥" + ], + "type": "array", + "items": { + "type": "string" + } + }, + "detect_files": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "detect_folders": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, "NatsConfig": { "type": "object", "properties": { diff --git a/docs/config/README.md b/docs/config/README.md index 2246edcc5..279dd0fca 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -2948,6 +2948,41 @@ truncation_length = 4 truncation_symbol = '' ``` +## Mojo + +The `mojo` module shows the current version of [Mojo programming language](https://www.modular.com/mojo) installed + +### Options + +| Option | Default | Description | +| ------------------- | ------------------------------------- | ------------------------------------------------------ | +| `format` | `'with [$symbol($version )]($style)'` | The format for the module. | +| `symbol` | `'🔥 '` | The symbol used before displaying the version of Mojo. | +| `style` | `'bold 208'` | The style for the module. | +| `disabled` | `false` | Disables the `mojo` module. | +| `detect_extensions` | `['mojo', '🔥']` | Which extensions should trigger this module. | +| `detect_files` | `[]` | Which filenames should trigger this module. | +| `detect_folders` | `[]` | Which folders should trigger this module. | + +### Variables + +| Variable | Example | Description | +| -------- | -------- | ------------------------------------ | +| version | `24.4.0` | The version of `mojo` | +| 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 + +### Example + +```toml +# ~/.config/starship.toml + +[mojo] +format = 'via [mojo ($version )($hash )]($style)' +``` + ## NATS The `nats` module shows the name of the current [NATS](https://nats.io) context. diff --git a/src/configs/mod.rs b/src/configs/mod.rs index 6f15638e0..820d7f5b0 100644 --- a/src/configs/mod.rs +++ b/src/configs/mod.rs @@ -55,6 +55,7 @@ pub mod localip; pub mod lua; pub mod memory_usage; pub mod meson; +pub mod mojo; pub mod nats; pub mod nim; pub mod nix_shell; @@ -219,6 +220,8 @@ pub struct FullConfig<'a> { #[serde(borrow)] meson: meson::MesonConfig<'a>, #[serde(borrow)] + mojo: mojo::MojoConfig<'a>, + #[serde(borrow)] nats: nats::NatsConfig<'a>, #[serde(borrow)] nim: nim::NimConfig<'a>, diff --git a/src/configs/mojo.rs b/src/configs/mojo.rs new file mode 100644 index 000000000..885b89f47 --- /dev/null +++ b/src/configs/mojo.rs @@ -0,0 +1,35 @@ +use nu_ansi_term::Color; +use serde::{Deserialize, Serialize}; + +pub const MOJO_DEFAULT_COLOR: Color = Color::Fixed(208); + +#[derive(Clone, Deserialize, Serialize)] +#[cfg_attr( + feature = "config-schema", + derive(schemars::JsonSchema), + schemars(deny_unknown_fields) +)] +#[serde(default)] +pub struct MojoConfig<'a> { + pub 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 MojoConfig<'a> { + fn default() -> Self { + MojoConfig { + format: "with [$symbol($version )]($style)", + symbol: "🔥 ", + style: "bold 208", + disabled: false, + detect_extensions: vec!["mojo", "🔥"], + detect_files: vec![], + detect_folders: vec![], + } + } +} diff --git a/src/configs/starship_root.rs b/src/configs/starship_root.rs index 8acbbe114..8bbe0948b 100644 --- a/src/configs/starship_root.rs +++ b/src/configs/starship_root.rs @@ -75,6 +75,7 @@ pub const PROMPT_ORDER: &[&str] = &[ "julia", "kotlin", "lua", + "mojo", "nim", "nodejs", "ocaml", diff --git a/src/module.rs b/src/module.rs index 43d3c2caf..e5e0f392c 100644 --- a/src/module.rs +++ b/src/module.rs @@ -60,6 +60,7 @@ pub const ALL_MODULES: &[&str] = &[ "lua", "memory_usage", "meson", + "mojo", "nats", "nim", "nix_shell", diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 60eed9e55..1301ce8f2 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -52,6 +52,7 @@ mod localip; mod lua; mod memory_usage; mod meson; +mod mojo; mod nats; mod nim; mod nix_shell; @@ -163,6 +164,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option> { "lua" => lua::module(context), "memory_usage" => memory_usage::module(context), "meson" => meson::module(context), + "mojo" => mojo::module(context), "nats" => nats::module(context), "nim" => nim::module(context), "nix_shell" => nix_shell::module(context), @@ -287,6 +289,7 @@ pub fn description(module: &str) -> &'static str { "meson" => { "The current Meson environment, if $MESON_DEVENV and $MESON_PROJECT_NAME are set" } + "mojo" => "The currently installed version of Mojo", "nats" => "The current NATS context", "nim" => "The currently installed version of Nim", "nix_shell" => "The nix-shell environment", diff --git a/src/modules/mojo.rs b/src/modules/mojo.rs new file mode 100644 index 000000000..9627bf9a7 --- /dev/null +++ b/src/modules/mojo.rs @@ -0,0 +1,170 @@ +use super::{Context, Module, ModuleConfig}; + +use crate::configs::mojo::MojoConfig; +use crate::formatter::StringFormatter; + +use once_cell::sync::Lazy; +use std::ops::Deref; + +/// Creates a module with the current Mojo version +pub fn module<'a>(context: &'a Context) -> Option> { + let mut module = context.new_module("mojo"); + let config = MojoConfig::try_load(module.config); + + let is_mojo_project = context + .try_begin_scan()? + .set_files(&config.detect_files) + .set_extensions(&config.detect_extensions) + .set_folders(&config.detect_folders) + .is_match(); + + if !is_mojo_project { + return None; + } + + let version_hash = Lazy::new(|| get_mojo_version(context)); + + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_meta(|variable, _| match variable { + "symbol" => Some(config.symbol), + _ => None, + }) + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + }) + .map(|variable| match variable { + "version" => match version_hash.deref() { + Some((version, _)) => Some(Ok(version)), + _ => None, + }, + "hash" => match version_hash.deref() { + Some((_, Some(hash))) => Some(Ok(hash)), + _ => None, + }, + _ => None, + }) + .parse(None, Some(context)) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `mojo`:\n{}", error); + return None; + } + }); + + Some(module) +} + +fn get_mojo_version(context: &Context) -> Option<(String, Option)> { + let mojo_version_output = context.exec_cmd("mojo", &["--version"])?.stdout; + + let version_items = mojo_version_output + .split_ascii_whitespace() + .collect::>(); + + let (version, hash) = match version_items[..] { + [_, version] => (version.trim().to_string(), None), + [_, version, hash, ..] => (version.trim().to_string(), Some(hash.trim().to_string())), + _ => { + log::debug!("Unexpected `mojo --version` output: {mojo_version_output}"); + return None; + } + }; + + Some((version, hash)) +} + +#[cfg(test)] +mod tests { + use crate::configs::mojo::MOJO_DEFAULT_COLOR; + use crate::test::ModuleRenderer; + use crate::utils::CommandOutput; + use std::fs::File; + use std::io; + + #[test] + fn folder_without_mojo() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("mojo.txt"))?.sync_all()?; + let actual = ModuleRenderer::new("mojo").path(dir.path()).collect(); + let expected = None; + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_mojo_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.mojo"))?.sync_all()?; + let actual = ModuleRenderer::new("mojo") + .path(dir.path()) + .collect() + .unwrap(); + let expected = format!("with {}", MOJO_DEFAULT_COLOR.bold().paint("🔥 24.4.0 ")); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_mojo_file_emoji() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.🔥"))?.sync_all()?; + let actual = ModuleRenderer::new("mojo") + .path(dir.path()) + .collect() + .unwrap(); + let expected = format!("with {}", MOJO_DEFAULT_COLOR.bold().paint("🔥 24.4.0 ")); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_mojo_file_with_commit() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.mojo"))?.sync_all()?; + let actual = ModuleRenderer::new("mojo") + .config(toml::toml! { + [mojo] + format = "with [$symbol($version )($hash )]($style)" + }) + .path(dir.path()) + .collect() + .unwrap(); + let expected = format!( + "with {}", + MOJO_DEFAULT_COLOR.bold().paint("🔥 24.4.0 (2cb57382) ") + ); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_mojo_file_with_no_commit_available() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.mojo"))?.sync_all()?; + let actual = ModuleRenderer::new("mojo") + .config(toml::toml! { + [mojo] + show_commit = true + }) + .cmd( + "mojo --version", + Some(CommandOutput { + stdout: String::from("mojo 24.4.0\n"), + stderr: String::default(), + }), + ) + .path(dir.path()) + .collect(); + let expected = Some(format!( + "with {}", + MOJO_DEFAULT_COLOR.bold().paint("🔥 24.4.0 ") + )); + assert_eq!(expected, actual); + dir.close() + } +} diff --git a/src/utils.rs b/src/utils.rs index be42539e5..c74e3fc5f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -315,6 +315,10 @@ Elixir 1.10 (compiled with Erlang/OTP 22)\n", stdout: String::from("LuaJIT 2.0.5 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/\n"), stderr: String::default(), }), + "mojo --version" => Some(CommandOutput { + stdout: String::from("mojo 24.4.0 (2cb57382)\n"), + stderr: String::default(), + }), "nats context info --json" => Some(CommandOutput{ stdout: String::from("{\"name\":\"localhost\",\"url\":\"nats://localhost:4222\"}"), stderr: String::default(),