feat: add pijul_channel module (#4765)

* feat: Pijul VCS support

* Extra bits needed for new module.

* Format Markdown table.

* Fix lint.

* Don't test Pijul module so thoroughly.

Installing from source is too expensive, and compiled binaries are only
available for Windows (and unofficially as well). Perhaps once Pijul
1.0.0 comes out of beta there will be more binaries available in package
  repos.

* Format!

* Bad rebase, remove Pijul install from workflow.

* Mock Pijul commands for code coverage.

* Make fake .pijul directory in fixture.

* Truly mock `pijul` command.

* Rename module from `pijul` to `pijul_channel`.

* Format!

* Fix config-schema.json.

* Missed changing module name in docs/ folder.
This commit is contained in:
Lyle Mantooth 2022-12-31 09:55:23 -05:00 committed by GitHub
parent 8d2256ab1d
commit 67b6376e2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 348 additions and 0 deletions

View File

@ -1215,6 +1215,21 @@
} }
] ]
}, },
"pijul_channel": {
"default": {
"disabled": true,
"format": "on [$symbol$channel]($style) ",
"style": "bold purple",
"symbol": " ",
"truncation_length": 9223372036854775807,
"truncation_symbol": "…"
},
"allOf": [
{
"$ref": "#/definitions/PijulConfig"
}
]
},
"pulumi": { "pulumi": {
"default": { "default": {
"disabled": false, "disabled": false,
@ -4495,6 +4510,37 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"PijulConfig": {
"type": "object",
"properties": {
"symbol": {
"default": " ",
"type": "string"
},
"style": {
"default": "bold purple",
"type": "string"
},
"format": {
"default": "on [$symbol$channel]($style) ",
"type": "string"
},
"truncation_length": {
"default": 9223372036854775807,
"type": "integer",
"format": "int64"
},
"truncation_symbol": {
"default": "…",
"type": "string"
},
"disabled": {
"default": true,
"type": "boolean"
}
},
"additionalProperties": false
},
"PulumiConfig": { "PulumiConfig": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -130,6 +130,9 @@ format = '\[[$symbol($version)]($style)\]'
[php] [php]
format = '\[[$symbol($version)]($style)\]' format = '\[[$symbol($version)]($style)\]'
[pijul_channel]
format = '\[[$symbol$channel]($style)\]'
[pulumi] [pulumi]
format = '\[[$symbol$stack]($style)\]' format = '\[[$symbol$stack]($style)\]'

View File

@ -108,6 +108,9 @@ Windows = " "
[package] [package]
symbol = " " symbol = " "
[pijul_channel]
symbol = "🪺 "
[python] [python]
symbol = " " symbol = " "

View File

@ -156,6 +156,9 @@ symbol = "pl "
[php] [php]
symbol = "php " symbol = "php "
[pijul_channel]
symbol = "pijul "
[pulumi] [pulumi]
symbol = "pulumi " symbol = "pulumi "

View File

@ -271,6 +271,7 @@ $git_state\
$git_metrics\ $git_metrics\
$git_status\ $git_status\
$hg_branch\ $hg_branch\
$pijul_channel\
$docker_context\ $docker_context\
$package\ $package\
$c\ $c\
@ -3175,6 +3176,21 @@ By default the module will be shown if any of the following conditions are met:
format = 'via [🔹 $version](147 bold) ' format = 'via [🔹 $version](147 bold) '
``` ```
## Pijul Channel
The `pijul_channel` module shows the active channel of the repo in your current directory.
### Options
| Option | Default | Description |
| ------------------- | --------------------------------- | ------------------------------------------------------------------------------------ |
| `symbol` | `' '` | The symbol used before the pijul channel name of the repo in your current directory. |
| `style` | `'bold purple'` | The style for the module. |
| `format` | `'on [$symbol$channel]($style) '` | The format for the module. |
| `truncation_length` | `2^63 - 1` | Truncates the pijul channel name to `N` graphemes |
| `truncation_symbol` | `'…'` | The symbol used to indicate a branch name was truncated. |
| `disabled` | `true` | Disables the `pijul` module. |
## Pulumi ## Pulumi
The `pulumi` module shows the current username, selected [Pulumi Stack](https://www.pulumi.com/docs/intro/concepts/stack/), and version. The `pulumi` module shows the current username, selected [Pulumi Stack](https://www.pulumi.com/docs/intro/concepts/stack/), and version.

View File

@ -61,6 +61,7 @@ pub mod os;
pub mod package; pub mod package;
pub mod perl; pub mod perl;
pub mod php; pub mod php;
pub mod pijul_channel;
pub mod pulumi; pub mod pulumi;
pub mod purescript; pub mod purescript;
pub mod python; pub mod python;
@ -221,6 +222,8 @@ pub struct FullConfig<'a> {
#[serde(borrow)] #[serde(borrow)]
php: php::PhpConfig<'a>, php: php::PhpConfig<'a>,
#[serde(borrow)] #[serde(borrow)]
pijul_channel: pijul_channel::PijulConfig<'a>,
#[serde(borrow)]
pulumi: pulumi::PulumiConfig<'a>, pulumi: pulumi::PulumiConfig<'a>,
#[serde(borrow)] #[serde(borrow)]
purescript: purescript::PureScriptConfig<'a>, purescript: purescript::PureScriptConfig<'a>,

View File

@ -0,0 +1,30 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Deserialize, Serialize)]
#[cfg_attr(
feature = "config-schema",
derive(schemars::JsonSchema),
schemars(deny_unknown_fields)
)]
#[serde(default)]
pub struct PijulConfig<'a> {
pub symbol: &'a str,
pub style: &'a str,
pub format: &'a str,
pub truncation_length: i64,
pub truncation_symbol: &'a str,
pub disabled: bool,
}
impl<'a> Default for PijulConfig<'a> {
fn default() -> Self {
PijulConfig {
symbol: "",
style: "bold purple",
format: "on [$symbol$channel]($style) ",
truncation_length: std::i64::MAX,
truncation_symbol: "",
disabled: true,
}
}
}

View File

@ -42,6 +42,7 @@ pub const PROMPT_ORDER: &[&str] = &[
"git_metrics", "git_metrics",
"git_status", "git_status",
"hg_branch", "hg_branch",
"pijul_channel",
"docker_context", "docker_context",
"package", "package",
// ↓ Toolchain version modules ↓ // ↓ Toolchain version modules ↓

View File

@ -68,6 +68,7 @@ pub const ALL_MODULES: &[&str] = &[
"package", "package",
"perl", "perl",
"php", "php",
"pijul_channel",
"pulumi", "pulumi",
"purescript", "purescript",
"python", "python",

View File

@ -58,6 +58,7 @@ mod os;
mod package; mod package;
mod perl; mod perl;
mod php; mod php;
mod pijul_channel;
mod pulumi; mod pulumi;
mod purescript; mod purescript;
mod python; mod python;
@ -159,6 +160,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
"package" => package::module(context), "package" => package::module(context),
"perl" => perl::module(context), "perl" => perl::module(context),
"php" => php::module(context), "php" => php::module(context),
"pijul_channel" => pijul_channel::module(context),
"pulumi" => pulumi::module(context), "pulumi" => pulumi::module(context),
"purescript" => purescript::module(context), "purescript" => purescript::module(context),
"python" => python::module(context), "python" => python::module(context),
@ -273,6 +275,7 @@ pub fn description(module: &str) -> &'static str {
"package" => "The package version of the current directory's project", "package" => "The package version of the current directory's project",
"perl" => "The currently installed version of Perl", "perl" => "The currently installed version of Perl",
"php" => "The currently installed version of PHP", "php" => "The currently installed version of PHP",
"pijul_channel" => "The current channel of the repo in the current directory",
"pulumi" => "The current username, stack, and installed version of Pulumi", "pulumi" => "The current username, stack, and installed version of Pulumi",
"purescript" => "The currently installed version of PureScript", "purescript" => "The currently installed version of PureScript",
"python" => "The currently installed version of Python", "python" => "The currently installed version of Python",

View File

@ -0,0 +1,220 @@
use super::utils::truncate::truncate_text;
use super::{Context, Module, ModuleConfig};
use crate::configs::pijul_channel::PijulConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the Pijul channel in the current directory
///
/// Will display the channel lame if the current directory is a pijul repo
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let is_repo = context
.try_begin_scan()?
.set_folders(&[".pijul"])
.is_match();
if !is_repo {
return None;
}
let mut module = context.new_module("pijul_channel");
let config: PijulConfig = PijulConfig::try_load(module.config);
// We default to disabled=true, so we have to check after loading our config module.
if config.disabled {
return None;
};
let channel_name = get_pijul_current_channel(context)?;
let truncated_text = truncate_text(
&channel_name,
config.truncation_length as usize,
config.truncation_symbol,
);
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 {
"channel" => Some(Ok(&truncated_text)),
_ => None,
})
.parse(None, Some(context))
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `pijul_channel`:\n{}", error);
return None;
}
});
Some(module)
}
fn get_pijul_current_channel(ctx: &Context) -> Option<String> {
let output = ctx.exec_cmd("pijul", &["channel"])?.stdout;
output
.lines()
.find_map(|l| l.strip_prefix("* "))
.map(str::to_owned)
}
#[cfg(test)]
mod tests {
use nu_ansi_term::{Color, Style};
use std::io;
use std::path::Path;
use crate::test::{fixture_repo, FixtureProvider, ModuleRenderer};
enum Expect<'a> {
ChannelName(&'a str),
Empty,
NoTruncation,
Symbol(&'a str),
Style(Style),
TruncationSymbol(&'a str),
}
#[test]
fn show_nothing_on_empty_dir() -> io::Result<()> {
let repo_dir = tempfile::tempdir()?;
let actual = ModuleRenderer::new("pijul_channel")
.path(repo_dir.path())
.collect();
let expected = None;
assert_eq!(expected, actual);
repo_dir.close()
}
#[test]
fn test_pijul_disabled_per_default() -> io::Result<()> {
let tempdir = fixture_repo(FixtureProvider::Pijul)?;
let repo_dir = tempdir.path();
expect_pijul_with_config(
repo_dir,
Some(toml::toml! {
[pijul_channel]
truncation_length = 14
}),
&[Expect::Empty],
);
tempdir.close()
}
#[test]
fn test_pijul_autodisabled() -> io::Result<()> {
let tempdir = tempfile::tempdir()?;
expect_pijul_with_config(tempdir.path(), None, &[Expect::Empty]);
tempdir.close()
}
#[test]
fn test_pijul_channel() -> io::Result<()> {
let tempdir = fixture_repo(FixtureProvider::Pijul)?;
let repo_dir = tempdir.path();
run_pijul(&["channel", "new", "tributary-48198"], repo_dir)?;
run_pijul(&["channel", "switch", "tributary-48198"], repo_dir)?;
expect_pijul_with_config(
repo_dir,
None,
&[Expect::ChannelName("tributary-48198"), Expect::NoTruncation],
);
tempdir.close()
}
#[test]
fn test_pijul_configured() -> io::Result<()> {
let tempdir = fixture_repo(FixtureProvider::Pijul)?;
let repo_dir = tempdir.path();
run_pijul(&["channel", "new", "tributary-48198"], repo_dir)?;
run_pijul(&["channel", "switch", "tributary-48198"], repo_dir)?;
expect_pijul_with_config(
repo_dir,
Some(toml::toml! {
[pijul_channel]
style = "underline blue"
symbol = "P "
truncation_length = 14
truncation_symbol = "%"
disabled = false
}),
&[
Expect::ChannelName("tributary-4819"),
Expect::Style(Color::Blue.underline()),
Expect::Symbol("P"),
Expect::TruncationSymbol("%"),
],
);
tempdir.close()
}
fn expect_pijul_with_config(
repo_dir: &Path,
config: Option<toml::Value>,
expectations: &[Expect],
) {
let actual = ModuleRenderer::new("pijul_channel")
.path(repo_dir.to_str().unwrap())
.config(config.unwrap_or_else(|| {
toml::toml! {
[pijul_channel]
disabled = false
}
}))
.collect();
let mut expect_channel_name = "main";
let mut expect_style = Color::Purple.bold();
let mut expect_symbol = "\u{e0a0}";
let mut expect_truncation_symbol = "";
for expect in expectations {
match expect {
Expect::Empty => {
assert_eq!(None, actual);
return;
}
Expect::Symbol(symbol) => {
expect_symbol = symbol;
}
Expect::TruncationSymbol(truncation_symbol) => {
expect_truncation_symbol = truncation_symbol;
}
Expect::NoTruncation => {
expect_truncation_symbol = "";
}
Expect::ChannelName(channel_name) => {
expect_channel_name = channel_name;
}
Expect::Style(style) => expect_style = *style,
}
}
let expected = Some(format!(
"on {} ",
expect_style.paint(format!(
"{expect_symbol} {expect_channel_name}{expect_truncation_symbol}"
)),
));
assert_eq!(expected, actual);
}
fn run_pijul(args: &[&str], _repo_dir: &Path) -> io::Result<()> {
crate::utils::mock_cmd("pijul", args).ok_or(io::ErrorKind::Unsupported)?;
Ok(())
}
}

View File

@ -7,6 +7,7 @@ use crate::{
}; };
use log::{Level, LevelFilter}; use log::{Level, LevelFilter};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::fs;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tempfile::TempDir; use tempfile::TempDir;
@ -165,6 +166,7 @@ impl<'a> ModuleRenderer<'a> {
pub enum FixtureProvider { pub enum FixtureProvider {
Git, Git,
Hg, Hg,
Pijul,
} }
pub fn fixture_repo(provider: FixtureProvider) -> io::Result<TempDir> { pub fn fixture_repo(provider: FixtureProvider) -> io::Result<TempDir> {
@ -223,5 +225,10 @@ pub fn fixture_repo(provider: FixtureProvider) -> io::Result<TempDir> {
Ok(path) Ok(path)
} }
FixtureProvider::Pijul => {
let path = tempfile::tempdir()?;
fs::create_dir(path.path().join(".pijul"))?;
Ok(path)
}
} }
} }

View File

@ -337,6 +337,18 @@ WebAssembly: unavailable
stderr: String::default(), stderr: String::default(),
}) })
}, },
"pijul channel" => Some(CommandOutput{
stdout: String::from(" main\n* tributary-48198"),
stderr: String::default(),
}),
"pijul channel new tributary-48198" => Some(CommandOutput{
stdout: String::default(),
stderr: String::default(),
}),
"pijul channel switch tributary-48198" => Some(CommandOutput{
stdout: String::from("Outputting repository ↖"),
stderr: String::default(),
}),
"pulumi version" => Some(CommandOutput{ "pulumi version" => Some(CommandOutput{
stdout: String::from("1.2.3-ver.1631311768+e696fb6c"), stdout: String::from("1.2.3-ver.1631311768+e696fb6c"),
stderr: String::default(), stderr: String::default(),