215 lines
9.1 KiB
Markdown
215 lines
9.1 KiB
Markdown
# Contributing
|
|
|
|
🚀 Thank you for contributing to starship! 🚀
|
|
|
|
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By contributing to this project you agree to abide by its terms.
|
|
|
|
If you have any questions that aren't addressed in this document, please don't hesitate to open an issue or drop into our [Discord server](https://discord.gg/8Jzqu3T)! 💬
|
|
|
|
## Glossary
|
|
|
|
- **Module**: A component in the prompt giving information based on contextual information from your OS. For example, the `nodejs` module shows the version of NodeJS that is currently installed on your computer, if your current directory is a NodeJS project.
|
|
|
|
- **Segment**: Smaller sub-components that compose a module. For example, the `symbol` segment in the `nodejs` module contains the character that is shown before the version number (`⬢` by default).
|
|
|
|
## Philosophy
|
|
|
|
We aim to make starship as fast, robust and reliable as possible, while also allowing for extensive customization. We do so by leveraging Rust's inherent safety and with thorough cross-platform testing. We also do our best to eliminate unnecessary work when displaying the prompt by reducing repeated work and by using caching to our favor.
|
|
|
|
If you spot anywhere that we could trim some time or reduce the prompt's workload, we will gladly accept new issues or PRs! 😄
|
|
|
|
## Architecture
|
|
|
|
The project begins in [`main.rs`](src/main.rs), where the appropriate `print::` method is called based on which arguments are given to [clap](https://crates.io/crates/clap). When printing the full prompt, we use [rayon](https://crates.io/crates/rayon) to parallelize the computation of modules.
|
|
|
|
Any styling that is applied to a module is inherited by its segments. Module prefixes and suffixes by default don't have any styling applied to them.
|
|
|
|
## Environment Variables and external commands
|
|
|
|
We have custom functions to be able to test our modules better. Here we show you how.
|
|
|
|
### Environment Variables
|
|
|
|
To get an environment variable we have special function to allow for mocking of vars. Here's a quick example:
|
|
|
|
```rust
|
|
use super::{Context, Module, RootModuleConfig};
|
|
|
|
use crate::configs::php::PhpConfig;
|
|
use crate::formatter::StringFormatter;
|
|
use crate::utils;
|
|
|
|
|
|
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
|
// Here `my_env_var` will be either the contents of the var or the function
|
|
// will exit if the variable is not set.
|
|
let my_env_var = context.get_env("MY_VAR")?;
|
|
|
|
// Then you can happily use the value
|
|
}
|
|
```
|
|
|
|
## External commands
|
|
|
|
To run a external command (e.g. to get the version of a tool) and to allow for mocking use the `utils::exec_cmd` function. Here's a quick example:
|
|
|
|
```rust
|
|
use super::{Context, Module, RootModuleConfig};
|
|
|
|
use crate::configs::php::PhpConfig;
|
|
use crate::formatter::StringFormatter;
|
|
use crate::utils;
|
|
|
|
|
|
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
|
// Here `my_env_var` will be either the stdout of the called command or the function
|
|
// will exit if the called program was not installed or could not be run.
|
|
let output = utils::exec_cmd("my_command", &["first_arg", "second_arg"])?.stdout;
|
|
|
|
// Then you can happily use the output
|
|
}
|
|
```
|
|
|
|
## Logging
|
|
|
|
Debug logging in starship is done with [pretty_env_logger](https://crates.io/crates/pretty_env_logger).
|
|
To run starship with debug logs, set the `STARSHIP_LOG` environment variable to the log level needed.
|
|
For example, to enable the trace logs, run the following:
|
|
|
|
```sh
|
|
# Run installed starship
|
|
STARSHIP_LOG=trace starship
|
|
|
|
# Run with cargo
|
|
STARSHIP_LOG=trace cargo run
|
|
```
|
|
|
|
## Linting
|
|
|
|
Starship source files are linted with [clippy](https://crates.io/crates/clippy). Clippy will be ran as part of CI. Linting errors will fail a build, so it is suggested that you run Clippy locally:
|
|
|
|
```sh
|
|
rustup component add clippy
|
|
cargo clippy
|
|
```
|
|
|
|
## Formatting
|
|
|
|
Starship source files are formatted with [rustfmt](https://crates.io/crates/rustfmt-nightly). Rustfmt will be ran as part of CI. Unformatted code will fail a build, so it is suggested that you run rustfmt locally:
|
|
|
|
```sh
|
|
rustup component add rustfmt
|
|
cargo fmt
|
|
```
|
|
|
|
## Testing
|
|
|
|
Testing is critical to making sure starship works as intended on systems big and small. Starship interfaces with many applications and system APIs when generating the prompt, so there's a lot of room for bugs to slip in.
|
|
|
|
Unit tests are written using the built-in Rust testing library in the same file as the implementation, as is traditionally done in Rust codebases. These tests can be run with `cargo test` and are run on GitHub as part of our GitHub Actions continuous integration to ensure consistend behavior.
|
|
|
|
All tests that test the rendered output of a module should use `ModuleRenderer`. For Example:
|
|
|
|
```rust
|
|
use super::{Context, Module, RootModuleConfig};
|
|
|
|
use crate::configs::php::PhpConfig;
|
|
use crate::formatter::StringFormatter;
|
|
use crate::utils;
|
|
|
|
|
|
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
|
/* This is where your module code goes */
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::test::ModuleRenderer;
|
|
use ansi_term::Color;
|
|
use std::fs::File;
|
|
use std::io;
|
|
|
|
|
|
#[test]
|
|
fn should_render() -> io::Result<()> {
|
|
// Here you setup the testing environment
|
|
let tempdir = tempfile::tempdir()?;
|
|
// Create some file needed to render the module
|
|
File::create(dir.path().join("YOUR_FILE"))?.sync_all()?;
|
|
|
|
// The output of the module
|
|
let actual = ModuleRenderer::new("YOUR_MODULE_NAME")
|
|
// For a custom path
|
|
.path(&tempdir.path())
|
|
// For a custom config
|
|
.config(toml::toml!{
|
|
[YOUR_MODULE_NAME]
|
|
val = 1
|
|
})
|
|
// For env mocking
|
|
.env("KEY","VALUE")
|
|
// Run the module and collect the output
|
|
.collect();
|
|
|
|
// The value that should be rendered by the module.
|
|
let expected = Some(format!("{} ",Color::Black.paint("THIS SHOULD BE RENDERED")));
|
|
|
|
// Assert that the actual and expected values are the same
|
|
assert_eq!(actual, expected);
|
|
|
|
// Close the tempdir
|
|
tempdir.close()
|
|
}
|
|
}
|
|
```
|
|
|
|
If a module depends on output of another program, then that output should be added to the match statement in [`utils.rs`](src/utils.rs). The match has to be exactly the same as the call to `utils::exec_cmd()`, including positional arguments and flags. The array of arguments are joined by a `" "`, so `utils::exec_cmd("program", &["arg", "more_args"])` would match with the `program arg more_args` match statement.
|
|
|
|
If the program cannot be mocked (e.g. It performs some filesystem operations, either writing or reading files) then it has to added to the project's GitHub Actions workflow file([`.github/workflows/workflow.yml`](.github/workflows/workflow.yml)) and the test has to be marked with an `#[ignored]`. This ensures that anyone can run the test suite locally without needing to pre-configure their environment. The `#[ignored]` attribute is bypassed during CI runs in GitHub Actions.
|
|
|
|
Unit tests should be fully isolated, only testing a given function's expected output given a specific input, and should be reproducible on any machine. Unit tests should not expect the computer running them to be in any particular state. This includes having any applications pre-installed, having any environment variables set, etc.
|
|
|
|
The previous point should be emphasized: even seemingly innocuous ideas like "if we can see the directory, we can read it" or "nobody will have their home directory be a git repo" have bitten us in the past. Having even a single test fail can completely break installation on some platforms, so be careful with tests!
|
|
|
|
### Test Programming Guidelines
|
|
|
|
Any tests that depend on File I/O should use [`sync_all()`](https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_all) when creating files or after writing to files.
|
|
|
|
Any tests that use `tempfile::tempdir` should take care to call `dir.close()` after usage to ensure the lifecycle of the directory can be reasoned about. This includes `fixture_repo()` as it returns a TempDir that should be closed.
|
|
|
|
## Running the Documentation Website Locally
|
|
|
|
If you are contributing to the design of Starship's website, the following section will help you get started.
|
|
|
|
### Setup
|
|
|
|
After cloning the project, you can do the following to run the VuePress website on your local machine:
|
|
|
|
1. `cd` into the `/docs` directory.
|
|
2. Install the project dependencies:
|
|
|
|
```sh
|
|
npm install
|
|
```
|
|
|
|
3. Start the project in development mode:
|
|
|
|
```sh
|
|
npm run dev
|
|
```
|
|
|
|
Once setup is complete, you can refer to VuePress documentation on the actual implementation here: <https://vuepress.vuejs.org/guide/>.
|
|
|
|
## Git/GitHub workflow
|
|
|
|
This is our preferred process for opening a PR on GitHub:
|
|
|
|
1. Fork this repository
|
|
2. Create a branch off of `master` for your work: `git checkout -b my-feature-branch`
|
|
3. Make some changes, committing them along the way
|
|
4. When your changes are ready for review, push your branch: `git push origin my-feature-branch`
|
|
5. Create a pull request from your branch to `starship/master`
|
|
6. No need to assign the pull request to anyone, we'll review it when we can
|
|
7. When the changes have been reviewed and approved, someone will squash and merge for you
|