Thank you for your interest in contributing to topgrade
!
We welcome and encourage contributions of all kinds, such as:
- Issue reports or feature requests
- Documentation improvements
- Code (PR or PR Review)
Please follow the Karma Runner guidelines for commit messages.
In topgrade
's term, package manager is called step
.
To add a new step
to topgrade
:
-
Add a new variant to
enum Step
pub enum Step { // Existed steps // ... // Your new step here! // You may want it to be sorted alphabetically because that looks great:) Xxx, }
-
Implement the update function
You need to find the appropriate location where this update function goes, it should be a file under
src/steps
, the file names are self-explanatory, for example,step
s related tozsh
are placed insteps/zsh.rs
.Then you implement the update function, and put it in the file where it belongs.
pub fn run_xxx(ctx: &ExecutionContext) -> Result<()> { // Check if this step is installed, if not, then this update will be skipped. let xxx = require("xxx")?; // Print the separator print_separator("xxx"); // Invoke the new step to get things updated! ctx.run_type() .execute(xxx) .arg(/* args required by this step */) .status_checked() }
Such a update function would be conventionally named
run_xxx()
, wherexxx
is the name of the new step, and it should take a argument of type&ExecutionContext
, this is adequate for most cases unless some extra stuff is needed (You can find some examples where extra arguments are needed here).Update function would usually do 3 things:
- Check if the step is installed
- Output the Separator
- Invoke the step
Still, this is sufficient for most tools, but you may need some extra stuff with complicated
step
. -
Finally, invoke that update function in
main.rs
runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?;
We use conditional compilation to separate the steps, for example, for steps that are Linux-only, it goes like this:
#[cfg(target_os = "linux")] { // Xxx is Linux-only runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?; }
Congrats, you just added a new
step
:)
If your PR has the configuration options
(in src/config.rs
)
modified:
- Adding new options
- Changing the existing options
Be sure to apply your changes to
config.example.toml
,
and have some basic documentations guiding user how to use these options.
If your PR introduces a breaking change, document it in BREAKINGCHANGES_dev.md
,
it should be written in Markdown and wrapped at 80, for example:
1. The configuration location has been updated to x.
2. The step x has been removed.
3. ...
Make sure your patch passes the following tests on your host:
$ cargo build
$ cargo fmt
$ cargo clippy
$ cargo test
Don't worry about other platforms, we have most of them covered in our CI.
If your PR introduces user-facing messages, we need to ensure they are translated.
Please add the translations to locales/app.yml
. For simple messages
without arguments (e.g., "hello world"), we can simply translate them according
(Tip: ChatGPT or similar LLMs is good at translation). If a message contains
arguments, e.g., "hello ", please follow this convention:
"hello {name}": # key
en: "hello %{name}" # translation
Arguments in the key should be in format {argument_name}
, and they will have
a preceeding %
when used in translations.
-
Locale
Some
step
respects locale, which means their output can be in language other than English, we should not do check on it.For example, one may want to check if a tool works by doing this:
let output = Command::new("xxx").arg("--help").output().unwrap(); let stdout = from_utf8(output.stdout).expect("Assume it is UTF-8 encoded"); if stdout.contains("help") { // xxx works }
If
xxx
respects locale, then the above code should work on English system, on a system that does not use English, e.g., it uses Chinese, that"help"
may be translated to"帮助"
, and the above code won't work.