feat: Add a module for C projects (#3631)
Co-authored-by: David Knaack <davidkna@users.noreply.github.com>
This commit is contained in:
parent
b90d1164f5
commit
0863146f07
|
@ -1,6 +1,9 @@
|
|||
[aws]
|
||||
format = '\[[$symbol($profile)(\($region\))(\[$duration\])]($style)\]'
|
||||
|
||||
[c]
|
||||
format = '\[[$symbol($version(-$name))]($style)\]'
|
||||
|
||||
[cmake]
|
||||
format = '\[[$symbol($version)]($style)\]'
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ symbol = " "
|
|||
[buf]
|
||||
symbol = " "
|
||||
|
||||
[c]
|
||||
symbol = " "
|
||||
|
||||
[conda]
|
||||
symbol = " "
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@ deleted = "x"
|
|||
[aws]
|
||||
symbol = "aws "
|
||||
|
||||
[c]
|
||||
symbol = "C "
|
||||
|
||||
[cobol]
|
||||
symbol = "cobol "
|
||||
|
||||
|
|
|
@ -207,6 +207,7 @@ $hg_branch\
|
|||
$docker_context\
|
||||
$package\
|
||||
$buf\
|
||||
$c\
|
||||
$cmake\
|
||||
$cobol\
|
||||
$container\
|
||||
|
@ -496,6 +497,54 @@ The `buf` module shows the currently installed version of [Buf](https://buf.buil
|
|||
symbol = "🦬 "
|
||||
```
|
||||
|
||||
## C
|
||||
|
||||
The `c` module shows some information about your C compiler. By default
|
||||
the module will be shown if the current directory contains a `.c` or `.h`
|
||||
file.
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Default | Description |
|
||||
| ------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
||||
| `format` | `"via [$symbol($version(-$name) )]($style)"` | The format string for the module. |
|
||||
| `version_format` | `"v${raw}"` | The version format. Available vars are `raw`, `major`, `minor`, & `patch` |
|
||||
| `symbol` | `"C "` | The symbol used before displaying the compiler details |
|
||||
| `detect_extensions` | `["c", "h"]` | Which extensions should trigger this module. |
|
||||
| `detect_files` | `[]` | Which filenames should trigger this module. |
|
||||
| `detect_folders` | `[]` | Which folders should trigger this module. |
|
||||
| `commands` | [ [ "cc", "--version" ], [ "gcc", "--version" ], [ "clang", "--version" ] ] | How to detect what the compiler is |
|
||||
| `style` | `"bold 149"` | The style for the module. |
|
||||
| `disabled` | `false` | Disables the `c` module. |
|
||||
|
||||
### Variables
|
||||
|
||||
| Variable | Example | Description |
|
||||
| -------- | ------- | ------------------------------------ |
|
||||
| name | clang | The name of the compiler |
|
||||
| version | 13.0.0 | The version of the compiler |
|
||||
| symbol | | Mirrors the value of option `symbol` |
|
||||
| style | | Mirrors the value of option `style` |
|
||||
|
||||
NB that `version` is not in the default format.
|
||||
|
||||
### Commands
|
||||
|
||||
The `commands` option accepts a list of commands to determine the compiler version and name.
|
||||
|
||||
Each command is represented as a list of the executable name, followed by its arguments, usually something like `["mycc", "--version"]`. Starship will try executing each command until it gets a result on STDOUT.
|
||||
|
||||
If a C compiler is not supported by this module, you can request it by [raising an issue on GitHub](https://github.com/starship/starship/).
|
||||
|
||||
### Example
|
||||
|
||||
```toml
|
||||
# ~/.config/starship.toml
|
||||
|
||||
[c]
|
||||
format = "via [$name $version]($style)"
|
||||
```
|
||||
|
||||
## Character
|
||||
|
||||
The `character` module shows a character (usually an arrow) beside where the text
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
use crate::config::ModuleConfig;
|
||||
|
||||
use serde::Serialize;
|
||||
use starship_module_config_derive::ModuleConfig;
|
||||
|
||||
#[derive(Clone, ModuleConfig, Serialize)]
|
||||
pub struct CConfig<'a> {
|
||||
pub format: &'a str,
|
||||
pub version_format: &'a str,
|
||||
pub style: &'a str,
|
||||
pub symbol: &'a str,
|
||||
pub disabled: bool,
|
||||
pub detect_extensions: Vec<&'a str>,
|
||||
pub detect_files: Vec<&'a str>,
|
||||
pub detect_folders: Vec<&'a str>,
|
||||
pub commands: Vec<Vec<&'a str>>,
|
||||
}
|
||||
|
||||
impl<'a> Default for CConfig<'a> {
|
||||
fn default() -> Self {
|
||||
CConfig {
|
||||
format: "via [$symbol($version(-$name) )]($style)",
|
||||
version_format: "v${raw}",
|
||||
style: "149 bold",
|
||||
symbol: "C ",
|
||||
disabled: false,
|
||||
detect_extensions: vec!["c", "h"],
|
||||
detect_files: vec![],
|
||||
detect_folders: vec![],
|
||||
commands: vec![
|
||||
// the compiler is usually cc, and --version works on gcc and clang
|
||||
vec!["cc", "--version"],
|
||||
// but on some platforms gcc is installed as *gcc*, not cc
|
||||
vec!["gcc", "--version"],
|
||||
// for completeness, although I've never seen a clang that wasn't cc
|
||||
vec!["clang", "--version"],
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ pub mod aws;
|
|||
pub mod azure;
|
||||
pub mod battery;
|
||||
pub mod buf;
|
||||
pub mod c;
|
||||
pub mod character;
|
||||
pub mod cmake;
|
||||
pub mod cmd_duration;
|
||||
|
@ -93,6 +94,7 @@ pub struct FullConfig<'a> {
|
|||
azure: azure::AzureConfig<'a>,
|
||||
battery: battery::BatteryConfig<'a>,
|
||||
buf: buf::BufConfig<'a>,
|
||||
c: c::CConfig<'a>,
|
||||
character: character::CharacterConfig<'a>,
|
||||
cmake: cmake::CMakeConfig<'a>,
|
||||
cmd_duration: cmd_duration::CmdDurationConfig<'a>,
|
||||
|
@ -176,6 +178,7 @@ impl<'a> Default for FullConfig<'a> {
|
|||
azure: Default::default(),
|
||||
battery: Default::default(),
|
||||
buf: Default::default(),
|
||||
c: Default::default(),
|
||||
character: Default::default(),
|
||||
cmake: Default::default(),
|
||||
cmd_duration: Default::default(),
|
||||
|
|
|
@ -36,6 +36,7 @@ pub const PROMPT_ORDER: &[&str] = &[
|
|||
"package",
|
||||
// ↓ Toolchain version modules ↓
|
||||
// (Let's keep these sorted alphabetically)
|
||||
"c",
|
||||
"cmake",
|
||||
"cobol",
|
||||
"dart",
|
||||
|
|
|
@ -335,6 +335,13 @@ impl<'a> Context<'a> {
|
|||
Duration::from_millis(self.root_config.command_timeout),
|
||||
)
|
||||
}
|
||||
|
||||
/// Attempt to execute several commands with exec_cmd, return the results of the first that works
|
||||
pub fn exec_cmds_return_first(&self, commands: Vec<Vec<&str>>) -> Option<CommandOutput> {
|
||||
commands
|
||||
.iter()
|
||||
.find_map(|attempt| self.exec_cmd(attempt[0], &attempt[1..]))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -13,6 +13,7 @@ pub const ALL_MODULES: &[&str] = &[
|
|||
#[cfg(feature = "battery")]
|
||||
"battery",
|
||||
"buf",
|
||||
"c",
|
||||
"character",
|
||||
"cmake",
|
||||
"cmd_duration",
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
use super::{Context, Module, RootModuleConfig};
|
||||
|
||||
use crate::configs::c::CConfig;
|
||||
use crate::formatter::StringFormatter;
|
||||
use crate::formatter::VersionFormatter;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use semver::Version;
|
||||
use std::borrow::Cow;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Creates a module with the current C compiler and version
|
||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||
let mut module = context.new_module("c");
|
||||
let config: CConfig = CConfig::try_load(module.config);
|
||||
let is_c_project = context
|
||||
.try_begin_scan()?
|
||||
.set_extensions(&config.detect_extensions)
|
||||
.set_files(&config.detect_files)
|
||||
.set_folders(&config.detect_folders)
|
||||
.is_match();
|
||||
|
||||
if !is_c_project {
|
||||
return None;
|
||||
}
|
||||
|
||||
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
|
||||
let c_compiler_info = Lazy::new(|| context.exec_cmds_return_first(config.commands));
|
||||
|
||||
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 {
|
||||
"name" => {
|
||||
let c_compiler_info = &c_compiler_info.deref().as_ref()?.stdout;
|
||||
|
||||
let c_compiler = if c_compiler_info.contains("clang") {
|
||||
"clang"
|
||||
} else if c_compiler_info.contains("Free Software Foundation") {
|
||||
"gcc"
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some(c_compiler).map(Cow::Borrowed).map(Ok)
|
||||
}
|
||||
"version" => {
|
||||
let c_compiler_info = &c_compiler_info.deref().as_ref()?.stdout;
|
||||
|
||||
// Clang says ...
|
||||
// Apple clang version 13.0.0 ...\n
|
||||
// OpenBSD clang version 11.1.0\n...
|
||||
// FreeBSD clang version 11.0.1 ...\n
|
||||
// so we always want the first semver-ish whitespace-
|
||||
// separated "word".
|
||||
// gcc says ...
|
||||
// gcc (OmniOS 151036/9.3.0-il-1) 9.3.0\n...
|
||||
// gcc (Debian 10.2.1-6) 10.2.1 ...\n
|
||||
// cc (GCC) 3.3.5 (Debian 1:3.3.5-13)\n...
|
||||
// so again we always want the first semver-ish word.
|
||||
VersionFormatter::format_module_version(
|
||||
module.get_name(),
|
||||
c_compiler_info.split_whitespace().find_map(
|
||||
|word| match Version::parse(word) {
|
||||
Ok(_v) => Some(word),
|
||||
Err(_e) => None,
|
||||
},
|
||||
)?,
|
||||
config.version_format,
|
||||
)
|
||||
.map(Cow::Owned)
|
||||
.map(Ok)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.parse(None, Some(context))
|
||||
});
|
||||
|
||||
module.set_segments(match parsed {
|
||||
Ok(segments) => segments,
|
||||
Err(error) => {
|
||||
log::warn!("Error in module `c`:\n{}", error);
|
||||
return None;
|
||||
}
|
||||
});
|
||||
|
||||
Some(module)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{test::ModuleRenderer, utils::CommandOutput};
|
||||
use ansi_term::Color;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
|
||||
#[test]
|
||||
fn folder_without_c_files() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
|
||||
let actual = ModuleRenderer::new("c").path(dir.path()).collect();
|
||||
|
||||
let expected = None;
|
||||
assert_eq!(expected, actual);
|
||||
dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_c_file() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
File::create(dir.path().join("any.c"))?.sync_all()?;
|
||||
|
||||
// What happens when `cc --version` says it's modern gcc?
|
||||
// The case when it claims to be clang is covered in folder_with_h_file,
|
||||
// and uses the mock in src/test/mod.rs.
|
||||
let actual = ModuleRenderer::new("c")
|
||||
.cmd(
|
||||
"cc --version",
|
||||
Some(CommandOutput {
|
||||
stdout: String::from(
|
||||
"\
|
||||
cc (Debian 10.2.1-6) 10.2.1 20210110
|
||||
Copyright (C) 2020 Free Software Foundation, Inc.
|
||||
This is free software; see the source for copying conditions. There is NO
|
||||
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.",
|
||||
),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
)
|
||||
.path(dir.path())
|
||||
.collect();
|
||||
let expected = Some(format!(
|
||||
"via {}",
|
||||
Color::Fixed(149).bold().paint("C v10.2.1-gcc ")
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
// What happens when `cc --version` says it's ancient gcc?
|
||||
let actual = ModuleRenderer::new("c")
|
||||
.cmd(
|
||||
"cc --version",
|
||||
Some(CommandOutput {
|
||||
stdout: String::from(
|
||||
"\
|
||||
cc (GCC) 3.3.5 (Debian 1:3.3.5-13)
|
||||
Copyright (C) 2003 Free Software Foundation, Inc.
|
||||
This is free software; see the source for copying conditions. There is NO
|
||||
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.",
|
||||
),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
)
|
||||
.path(dir.path())
|
||||
.collect();
|
||||
let expected = Some(format!(
|
||||
"via {}",
|
||||
Color::Fixed(149).bold().paint("C v3.3.5-gcc ")
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
// What happens with an unknown C compiler? Needless to say, we're
|
||||
// not running on a Z80 so we're never going to see this one in reality!
|
||||
let actual = ModuleRenderer::new("c")
|
||||
.cmd(
|
||||
"cc --version",
|
||||
Some(CommandOutput {
|
||||
stdout: String::from("HISOFT-C Compiler V1.2\nCopyright © 1984 HISOFT"),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
)
|
||||
.path(dir.path())
|
||||
.collect();
|
||||
let expected = Some(format!("via {}", Color::Fixed(149).bold().paint("C ")));
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
// What happens when 'cc --version' doesn't work, but 'gcc --version' does?
|
||||
// This stubs out `cc` but we'll fall back to `gcc --version` as defined in
|
||||
// src/test/mod.rs.
|
||||
// Also we don't bother to redefine the config for this one, as we've already
|
||||
// proved we can parse its version.
|
||||
let actual = ModuleRenderer::new("c")
|
||||
.cmd("cc --version", None)
|
||||
.path(dir.path())
|
||||
.collect();
|
||||
let expected = Some(format!(
|
||||
"via {}",
|
||||
Color::Fixed(149).bold().paint("C v10.2.1-gcc ")
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
// Now with both 'cc' and 'gcc' not working, this should fall back to 'clang --version'
|
||||
let actual = ModuleRenderer::new("c")
|
||||
.cmd("cc --version", None)
|
||||
.cmd("gcc --version", None)
|
||||
.path(dir.path())
|
||||
.collect();
|
||||
let expected = Some(format!(
|
||||
"via {}",
|
||||
Color::Fixed(149).bold().paint("C v11.1.0-clang ")
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
// What happens when we can't find any of cc, gcc or clang?
|
||||
let actual = ModuleRenderer::new("c")
|
||||
.cmd("cc --version", None)
|
||||
.cmd("gcc --version", None)
|
||||
.cmd("clang --version", None)
|
||||
.path(dir.path())
|
||||
.collect();
|
||||
let expected = Some(format!("via {}", Color::Fixed(149).bold().paint("C ")));
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
dir.close()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_with_h_file() -> io::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
File::create(dir.path().join("any.h"))?.sync_all()?;
|
||||
|
||||
let actual = ModuleRenderer::new("c").path(dir.path()).collect();
|
||||
let expected = Some(format!(
|
||||
"via {}",
|
||||
Color::Fixed(149).bold().paint("C v11.0.1-clang ")
|
||||
));
|
||||
assert_eq!(expected, actual);
|
||||
dir.close()
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
mod aws;
|
||||
mod azure;
|
||||
mod buf;
|
||||
mod c;
|
||||
mod character;
|
||||
mod cmake;
|
||||
mod cmd_duration;
|
||||
|
@ -93,6 +94,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> {
|
|||
#[cfg(feature = "battery")]
|
||||
"battery" => battery::module(context),
|
||||
"buf" => buf::module(context),
|
||||
"c" => c::module(context),
|
||||
"character" => character::module(context),
|
||||
"cmake" => cmake::module(context),
|
||||
"cmd_duration" => cmd_duration::module(context),
|
||||
|
@ -184,6 +186,7 @@ pub fn description(module: &str) -> &'static str {
|
|||
"azure" => "The current Azure subscription",
|
||||
"battery" => "The current charge of the device's battery and its current charging status",
|
||||
"buf" => "The currently installed version of the Buf CLI",
|
||||
"c" => "Your C compiler type",
|
||||
"character" => {
|
||||
"A character (usually an arrow) beside where the text is entered in your terminal"
|
||||
}
|
||||
|
|
24
src/utils.rs
24
src/utils.rs
|
@ -133,6 +133,30 @@ pub fn mock_cmd<T: AsRef<OsStr> + Debug, U: AsRef<OsStr> + Debug>(
|
|||
stdout: String::from("1.0.0"),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
"cc --version" => Some(CommandOutput {
|
||||
stdout: String::from("\
|
||||
FreeBSD clang version 11.0.1 (git@github.com:llvm/llvm-project.git llvmorg-11.0.1-0-g43ff75f2c3fe)
|
||||
Target: x86_64-unknown-freebsd13.0
|
||||
Thread model: posix
|
||||
InstalledDir: /usr/bin"),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
"gcc --version" => Some(CommandOutput {
|
||||
stdout: String::from("\
|
||||
cc (Debian 10.2.1-6) 10.2.1 20210110
|
||||
Copyright (C) 2020 Free Software Foundation, Inc.
|
||||
This is free software; see the source for copying conditions. There is NO
|
||||
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
"clang --version" => Some(CommandOutput {
|
||||
stdout: String::from("\
|
||||
OpenBSD clang version 11.1.0
|
||||
Target: amd64-unknown-openbsd7.0
|
||||
Thread model: posix
|
||||
InstalledDir: /usr/bin"),
|
||||
stderr: String::default(),
|
||||
}),
|
||||
"cobc -version" => Some(CommandOutput {
|
||||
stdout: String::from("\
|
||||
cobc (GnuCOBOL) 3.1.2.0
|
||||
|
|
Loading…
Reference in New Issue