feat(haskell): Add Haskell module (#3587)
* feat(haskell): add haskell module and implement ghc version detection * feat(haskell): implement stack resolver version detection * fix(haskell): handle more complex resolvers * fix(haskell): rename resolver_version to snapshot * fix(haskell): change default color to bold purple * feat(haskell): add tests * fix(haskell): format * fix(haskell): replace incorrect `or` with `or_else` * fix(haskell): use write_all instead of write * fix(haskell): λ as Haskell icon by default * fix(haskell): fix tests and add a real stack.yaml testcase * fix(haskell): make clippy happy
This commit is contained in:
parent
6b6ad39958
commit
72fec559c5
|
@ -49,6 +49,9 @@ format = '([\[$all_status$ahead_behind\]]($style))'
|
|||
[golang]
|
||||
format = '\[[$symbol($version)]($style)\]'
|
||||
|
||||
[haskell]
|
||||
format = '\[[$symbol($version)]($style)\]'
|
||||
|
||||
[helm]
|
||||
format = '\[[$symbol($version)]($style)\]'
|
||||
|
||||
|
|
|
@ -28,6 +28,9 @@ symbol = " "
|
|||
[golang]
|
||||
symbol = " "
|
||||
|
||||
[haskell]
|
||||
symbol = " "
|
||||
|
||||
[hg_branch]
|
||||
symbol = " "
|
||||
|
||||
|
|
|
@ -217,6 +217,7 @@ $elixir\
|
|||
$elm\
|
||||
$erlang\
|
||||
$golang\
|
||||
$haskell\
|
||||
$helm\
|
||||
$java\
|
||||
$julia\
|
||||
|
@ -1672,6 +1673,39 @@ By default the module will be shown if any of the following conditions are met:
|
|||
format = "via [🏎💨 $version](bold cyan) "
|
||||
```
|
||||
|
||||
## Haskell
|
||||
|
||||
The `haskell` module finds the current selected GHC version and/or the selected Stack snapshot.
|
||||
|
||||
By default the module will be shown if any of the following conditions are met:
|
||||
|
||||
- The current directory contains a `stack.yaml` file
|
||||
- The current directory contains any `.hs`, `.cabal`, or `.hs-boot` file
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Default | Description |
|
||||
| ------------------- | ------------------------------------ | -------------------------------------------------- |
|
||||
| `format` | `"via [$symbol($version )]($style)"` | The format for the module. |
|
||||
| `symbol` | `"λ "` | A format string representing the symbol of Haskell |
|
||||
| `detect_extensions` | `["hs", "cabal", "hs-boot"]` | Which extensions should trigger this module. |
|
||||
| `detect_files` | `["stack.yaml", "cabal.project"]` | Which filenames should trigger this module. |
|
||||
| `detect_folders` | `[]` | Which folders should trigger this module. |
|
||||
| `style` | `"bold purple"` | The style for the module. |
|
||||
| `disabled` | `false` | Disables the `haskell` module. |
|
||||
|
||||
### Variables
|
||||
|
||||
| Variable | Example | Description |
|
||||
| ------------ | ----------- | --------------------------------------------------------------------------------------- |
|
||||
| version | | `ghc_version` or `snapshot` depending on whether the current project is a Stack project |
|
||||
| snapshot | `lts-18.12` | Currently selected Stack snapshot |
|
||||
| ghc\_version | `9.2.1` | Currently installed GHC version |
|
||||
| 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
|
||||
|
||||
## Helm
|
||||
|
||||
The `helm` module shows the currently installed version of [Helm](https://helm.sh/).
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
use crate::config::ModuleConfig;
|
||||
|
||||
use serde::Serialize;
|
||||
use starship_module_config_derive::ModuleConfig;
|
||||
|
||||
#[derive(Clone, ModuleConfig, Serialize)]
|
||||
pub struct HaskellConfig<'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 HaskellConfig<'a> {
|
||||
fn default() -> Self {
|
||||
HaskellConfig {
|
||||
format: "via [$symbol($version )]($style)",
|
||||
version_format: "v${raw}",
|
||||
symbol: "λ ",
|
||||
style: "bold purple",
|
||||
disabled: false,
|
||||
detect_extensions: vec!["hs", "cabal", "hs-boot"],
|
||||
detect_files: vec!["stack.yaml", "cabal.project"],
|
||||
detect_folders: vec![],
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ pub mod git_metrics;
|
|||
pub mod git_state;
|
||||
pub mod git_status;
|
||||
pub mod go;
|
||||
pub mod haskell;
|
||||
pub mod helm;
|
||||
pub mod hg_branch;
|
||||
pub mod hostname;
|
||||
|
@ -116,6 +117,7 @@ pub struct FullConfig<'a> {
|
|||
git_state: git_state::GitStateConfig<'a>,
|
||||
git_status: git_status::GitStatusConfig<'a>,
|
||||
golang: go::GoConfig<'a>,
|
||||
haskell: haskell::HaskellConfig<'a>,
|
||||
helm: helm::HelmConfig<'a>,
|
||||
hg_branch: hg_branch::HgBranchConfig<'a>,
|
||||
hostname: hostname::HostnameConfig<'a>,
|
||||
|
@ -198,6 +200,7 @@ impl<'a> Default for FullConfig<'a> {
|
|||
git_state: Default::default(),
|
||||
git_status: Default::default(),
|
||||
golang: Default::default(),
|
||||
haskell: Default::default(),
|
||||
helm: Default::default(),
|
||||
hg_branch: Default::default(),
|
||||
hostname: Default::default(),
|
||||
|
|
|
@ -45,6 +45,7 @@ pub const PROMPT_ORDER: &[&str] = &[
|
|||
"elm",
|
||||
"erlang",
|
||||
"golang",
|
||||
"haskell",
|
||||
"helm",
|
||||
"java",
|
||||
"julia",
|
||||
|
|
|
@ -37,6 +37,7 @@ pub const ALL_MODULES: &[&str] = &[
|
|||
"git_state",
|
||||
"git_status",
|
||||
"golang",
|
||||
"haskell",
|
||||
"helm",
|
||||
"hg_branch",
|
||||
"hostname",
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
use super::{Context, Module, RootModuleConfig};
|
||||
|
||||
use crate::configs::haskell::HaskellConfig;
|
||||
use crate::formatter::StringFormatter;
|
||||
use crate::utils;
|
||||
|
||||
/// Creates a module with the current Haskell version
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
let mut module = context.new_module("haskell");
|
||||
let config = HaskellConfig::try_load(module.config);
|
||||
|
||||
let is_hs_project = context
|
||||
.try_begin_scan()?
|
||||
.set_files(&config.detect_files)
|
||||
.set_extensions(&config.detect_extensions)
|
||||
.set_folders(&config.detect_folders)
|
||||
.is_match();
|
||||
|
||||
if !is_hs_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" => get_version(context).map(Ok),
|
||||
"ghc_version" => get_ghc_version(context).map(Ok),
|
||||
"snapshot" => get_snapshot(context).map(Ok),
|
||||
_ => None,
|
||||
})
|
||||
.parse(None, Some(context))
|
||||
});
|
||||
|
||||
module.set_segments(match parsed {
|
||||
Ok(segments) => segments,
|
||||
Err(error) => {
|
||||
log::warn!("Error in module `haskell`:\n{}", error);
|
||||
return None;
|
||||
}
|
||||
});
|
||||
|
||||
Some(module)
|
||||
}
|
||||
|
||||
fn get_ghc_version(context: &Context) -> Option<String> {
|
||||
Some(
|
||||
context
|
||||
.exec_cmd("ghc", &["--numeric-version"])?
|
||||
.stdout
|
||||
.trim()
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_snapshot(context: &Context) -> Option<String> {
|
||||
if !is_stack_project(context) {
|
||||
return None;
|
||||
}
|
||||
let file_contents = utils::read_file(context.current_dir.join("stack.yaml")).ok()?;
|
||||
let yaml = yaml_rust::YamlLoader::load_from_str(&file_contents).ok()?;
|
||||
let version = yaml.first()?["resolver"]
|
||||
.as_str()
|
||||
.or_else(|| yaml.first()?["snapshot"].as_str())
|
||||
.filter(|s| s.starts_with("lts") || s.starts_with("nightly") || s.starts_with("ghc"))
|
||||
.unwrap_or("<custom snapshot>");
|
||||
Some(version.to_string())
|
||||
}
|
||||
|
||||
fn get_version(context: &Context) -> Option<String> {
|
||||
get_snapshot(context).or_else(|| get_ghc_version(context))
|
||||
}
|
||||
|
||||
fn is_stack_project(context: &Context) -> bool {
|
||||
match context.dir_contents() {
|
||||
Ok(dir) => dir.has_file_name("stack.yaml"),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::test::ModuleRenderer;
|
||||
use ansi_term::Color;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn folder_without_hs_files() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let actual = ModuleRenderer::new("haskell").path(dir.path()).collect();
|
||||
let expected = None;
|
||||
assert_eq!(expected, actual);
|
||||
dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_stack() -> io::Result<()> {
|
||||
let cases = vec![
|
||||
("resolver: lts-18.12\n", "lts-18.12"),
|
||||
("snapshot:\tnightly-2011-11-11", "nightly-2011-11-11"),
|
||||
("snapshot: ghc-8.10.7", "ghc-8.10.7"),
|
||||
(
|
||||
"snapshot: https://github.com/whatever/xxx.yaml\n",
|
||||
"<custom snapshot>",
|
||||
),
|
||||
(
|
||||
"resolver:\n url: https://github.com/whatever/xxx.yaml\n",
|
||||
"<custom snapshot>",
|
||||
),
|
||||
(REANIMATE_STACK_YAML, "lts-14.27"),
|
||||
];
|
||||
for (yaml, resolver) in &cases {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let mut file = File::create(dir.path().join("stack.yaml"))?;
|
||||
file.write_all(yaml.as_bytes())?;
|
||||
file.sync_all()?;
|
||||
let actual = ModuleRenderer::new("haskell").path(dir.path()).collect();
|
||||
let expected = Some(format!(
|
||||
"via {}",
|
||||
Color::Purple.bold().paint(format!("λ {} ", resolver))
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
dir.close()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_cabal() -> io::Result<()> {
|
||||
let should_trigger = vec!["a.hs", "b.hs-boot", "cabal.project"];
|
||||
for hs_file in &should_trigger {
|
||||
let dir = tempfile::tempdir()?;
|
||||
File::create(dir.path().join(hs_file))?.sync_all()?;
|
||||
let actual = ModuleRenderer::new("haskell").path(dir.path()).collect();
|
||||
let expected = Some(format!("via {}", Color::Purple.bold().paint("λ 9.2.1 ")));
|
||||
assert_eq!(expected, actual);
|
||||
dir.close()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
static REANIMATE_STACK_YAML: &str = r"
|
||||
resolver: lts-14.27
|
||||
|
||||
allow-newer: false
|
||||
|
||||
packages:
|
||||
- .
|
||||
|
||||
extra-deps:
|
||||
- reanimate-svg-0.13.0.1
|
||||
- chiphunk-0.1.2.1
|
||||
- cubicbezier-0.6.0.6@sha256:2191ff47144d9a13a2784651a33d340cd31be1926a6c188925143103eb3c8db3
|
||||
- fast-math-1.0.2@sha256:91181eb836e54413cc5a841e797c42b2264954e893ea530b6fc4da0dccf6a8b7
|
||||
- matrices-0.5.0@sha256:b2761813f6a61c84224559619cc60a16a858ac671c8436bbac8ec89e85473058
|
||||
- hmatrix-0.20.0.0@sha256:d79a9218e314f1a2344457c3851bd1d2536518ecb5f1a2fcd81daa45e46cd025,4870
|
||||
- earcut-0.1.0.4@sha256:d5118b3eecf24d130263d81fb30f1ff56b1db43036582bfd1d8cc9ba3adae8be,1010
|
||||
- tasty-rerun-1.1.17@sha256:d4a3ccb0f63f499f36edc71b33c0f91c850eddb22dd92b928aa33b8459f3734a,1373
|
||||
- hgeometry-0.11.0.0@sha256:09ead201a6ac3492c0be8dda5a6b32792b9ae87cab730b8362d46ee8d5c2acb4,11714
|
||||
- hgeometry-combinatorial-0.11.0.0@sha256:03176f235a1c49a415fe1266274dafca84deb917cbcbf9a654452686b4cd2bfe,8286
|
||||
- vinyl-0.13.0@sha256:0f247cd3f8682b30881a07de18e6fec52d540646fbcb328420049cc8d63cd407,3724
|
||||
- hashable-1.3.0.0@sha256:4c70f1407881059e93550d3742191254296b2737b793a742bd901348fb3e1fb1,5206
|
||||
- network-3.1.2.1@sha256:188d6daea8cd91bc3553efd5a90a1e7c6d0425fa66a53baa74db5b6d9fd75c8b,4968
|
||||
";
|
||||
}
|
|
@ -27,6 +27,7 @@ mod git_metrics;
|
|||
mod git_state;
|
||||
mod git_status;
|
||||
mod golang;
|
||||
mod haskell;
|
||||
mod helm;
|
||||
mod hg_branch;
|
||||
mod hostname;
|
||||
|
@ -115,6 +116,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
|
|||
"git_state" => git_state::module(context),
|
||||
"git_status" => git_status::module(context),
|
||||
"golang" => golang::module(context),
|
||||
"haskell" => haskell::module(context),
|
||||
"helm" => helm::module(context),
|
||||
"hg_branch" => hg_branch::module(context),
|
||||
"hostname" => hostname::module(context),
|
||||
|
@ -208,6 +210,7 @@ pub fn description(module: &str) -> &'static str {
|
|||
"git_state" => "The current git operation, and it's progress",
|
||||
"git_status" => "Symbol representing the state of the repo",
|
||||
"golang" => "The currently installed version of Golang",
|
||||
"haskell" => "The selected version of the Haskell toolchain",
|
||||
"helm" => "The currently installed version of Helm",
|
||||
"hg_branch" => "The active branch of the repo in your current directory",
|
||||
"hostname" => "The system hostname",
|
||||
|
|
|
@ -187,6 +187,10 @@ Elixir 1.10 (compiled with Erlang/OTP 22)\n",
|
|||
stdout: String::from("go version go1.12.1 linux/amd64\n"),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
"ghc --numeric-version" => Some(CommandOutput {
|
||||
stdout: String::from("9.2.1\n"),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
"helm version --short --client" => Some(CommandOutput {
|
||||
stdout: String::from("v3.1.1+gafe7058\n"),
|
||||
stderr: String::default(),
|
||||
|
|
Loading…
Reference in New Issue