other: Add autocomplete file generation (#213)

Adds shell completion generation as part of the build, as well as tweaking install scripts/templates/CI to use them.
This commit is contained in:
Clement Tsang 2020-08-31 17:50:21 -04:00 committed by GitHub
parent c6a20a1420
commit 3431411215
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 404 additions and 55 deletions

4
.cargo/config.toml Normal file
View file

@ -0,0 +1,4 @@
[target.x86_64-pc-windows-msvc]
rustflags = ["-Ctarget-feature=+crt-static"]
[target.i686-pc-windows-msvc]
rustflags = ["-Ctarget-feature=+crt-static"]

View file

@ -27,6 +27,8 @@ before_install:
powershell Install-WindowsFeature Net-Framework-Core;
choco install -y wixtoolset;
export PATH=$PATH:"/c/Program Files (x86)/WiX Toolset v3.11/bin";
choco install zip;
rustup target add i686-pc-windows-msvc;
fi
elif [[ $TRAVIS_OS_NAME == "osx" ]]; then
export TARGET=x86_64-apple-darwin;
@ -45,49 +47,86 @@ notifications:
on_success: never
before_deploy:
- cargo install --path . --target $TARGET --locked --force;
- |
echo "Test whether installing works. This is mostly just a sanity check.";
cargo install --path . --target $TARGET --locked --force;
- |
echo "Building release..."
if [[ $TRAVIS_OS_NAME == "windows" ]]; then
choco install zip;
rustup target add x86_64-pc-windows-msvc;
echo "Building Windows 64-bit...";
cargo build --release --target x86_64-pc-windows-msvc;
local target_dir=$(ls target/release/build/bottom-*/out/rg.bash | head -n1 | xargs dirname)
cp -r $target_dir completions
mv "./target/x86_64-pc-windows-msvc/release/btm" "btm.exe";
strip "btm.exe"
zip bottom_x86_64-pc-windows-msvc.zip "btm.exe";
zip -r bottom_x86_64-pc-windows-msvc.zip "btm.exe" "completions";
rm "btm.exe"
rustup target add i686-pc-windows-msvc;
rm -r "completions"
echo "Building Windows 32-bit...";
cargo clean;
cargo build --release --target i686-pc-windows-msvc;
local target_dir=$(ls target/release/build/bottom-*/out/rg.bash | head -n1 | xargs dirname)
cp -r $target_dir completions
mv "./target/i686-pc-windows-msvc/release/btm" "btm.exe";
strip "btm.exe"
zip bottom_i686-pc-windows-msvc.zip "btm.exe";
zip -r bottom_i686-pc-windows-msvc.zip "btm.exe" "completions";
rm "btm.exe"
rm -r "completions"
echo "Building choco template...";
python "./deployment/windows/choco/choco_packager.py" "bottom_i686-pc-windows-msvc.zip" "bottom_x86_64-pc-windows-msvc.zip" $TRAVIS_TAG "./deployment/windows/choco/bottom.nuspec.template" "./deployment/windows/choco/chocolateyinstall.ps1.template" "./deployment/windows/choco/bottom.nuspec" "./deployment/windows/choco/tools/chocolateyinstall.ps1" "./deployment/windows/choco/tools/";
cd "./deployment/windows/choco/"
zip -r choco.zip "bottom.nuspec" "tools/";
cd "../../../";
mv "./deployment/windows/choco/choco.zip" "./choco.zip"
echo "Building msi file...";
cargo install cargo-wix;
cargo wix init;
cargo wix;
python "./deployment/packager.py" $TRAVIS_TAG "./deployment/windows/winget/winget.yaml.template" "$TRAVIS_TAG.yaml" "SHA256" "./bottom_x86_64_installer.msi" ;
echo "Building winget template...";
python "./deployment/packager.py" $TRAVIS_TAG "./deployment/windows/winget/winget.yaml.template" "$TRAVIS_TAG.yaml" "SHA256" "./bottom_x86_64_installer.msi";
echo "Done Windows pre-deploy!";
else
echo "Building release for macOS/Linux...";
cargo build --release;
cp ./target/release/btm btm;
strip btm;
local target_dir=$(ls target/release/build/bottom-*/out/rg.bash | head -n1 | xargs dirname)
cp -r $target_dir completions
if [[ $TRAVIS_OS_NAME == "linux" ]]; then
tar -czvf bottom_x86_64-unknown-linux-gnu.tar.gz btm;
echo "Tar-ing Linux binary and completions..."
tar -czvf bottom_x86_64-unknown-linux-gnu.tar.gz btm completions;
echo "Generating AUR template...";
python "./deployment/packager.py" $TRAVIS_TAG "./deployment/linux/arch/PKGBUILD_BIN.template" "./PKGBUILD_BIN" "SHA512" "./bottom_x86_64-unknown-linux-gnu.tar.gz";
curl -LO "https://github.com/ClementTsang/bottom/archive/$TRAVIS_TAG.tar.gz";
echo "Generating AUR binary template...";
python "./deployment/packager.py" $TRAVIS_TAG "./deployment/linux/arch/PKGBUILD.template" "./PKGBUILD" "SHA512" "./$TRAVIS_TAG.tar.gz";
rm "$TRAVIS_TAG.tar.gz";
echo "Tar-ing AUR PKGBUILDs...";
tar -czvf arch.tar.gz PKGBUILD_BIN PKGBUILD;
# Note this requires the completions directory in the current directory.
echo "Generating Debian install file...";
cargo install cargo-deb;
cargo deb;
cp ./target/debian/bottom_*.deb .;
elif [[ $TRAVIS_OS_NAME == "osx" ]]; then
tar -czvf bottom_x86_64-apple-darwin.tar.gz btm;
echo "Tar-ing macOS binary and completions..."
tar -czvf bottom_x86_64-apple-darwin.tar.gz btm completions;
# The bottom.rb file must be generated AFTER, since it relies on the Linux binary file.
fi
echo "Done macOS/Linux pre-deploy!";
fi
deploy:

View file

@ -46,6 +46,7 @@
"markdownlint",
"memb",
"minwindef",
"n'th",
"noheader",
"ntdef",
"nuget",

View file

@ -10,6 +10,7 @@ categories = ["command-line-utilities", "visualization"]
description = "A cross-platform graphical process/system monitor with a customizable interface and a multitude of features. Supports Linux, macOS, and Windows."
readme = "README.md"
default-run = "btm"
build = "build.rs"
[[bin]]
name = "btm"
@ -55,14 +56,22 @@ winapi = "0.3.9"
assert_cmd = "1.0"
predicates = "1"
[build-dependencies]
clap = "2.33"
[package.metadata.deb]
section = "utility"
assets = [
["target/release/btm", "usr/bin/", "755"],
["LICENSE", "usr/share/doc/btm/", "644"],
["README.md", "usr/share/doc/btm/README", "644"],
["completions/btm.bash", "usr/share/bash-completion/completions/btm", "644"],
["completions/btm.fish", "usr/share/fish/vendor_completions.d/btm.fish", "644"],
["completions/_btm", "usr/share/zsh/vendor-completions/", "644"],
]
extended-description = """\
A cross-platform graphical process/system monitor with a customizable interface and a multitude of
features. Supports Linux, macOS, and Windows.
By default, bottom will look for a config file in ~/.config/bottom/bottom.toml.
If one is not specified it will fall back to defaults. If a config file does not
exist at the specified or default location, a blank one will be created for the user.

View file

@ -20,6 +20,7 @@ A cross-platform graphical process/system monitor with a customizable interface
- [Homebrew](#homebrew)
- [Scoop](#scoop)
- [Chocolatey](#chocolatey)
- [Auto-completion](#auto-completion)
- [Usage](#usage)
- [Flags](#flags)
- [Options](#options)
@ -86,6 +87,9 @@ tar -xzvf 0.4.7.tar.gz
cargo install --path .
```
Or, you can just download the binary from the [latest release](https://github.com/ClementTsang/bottom/releases/latest) and install/use it
in whatever way you want.
### Cargo
```bash
@ -152,6 +156,19 @@ choco install bottom
choco install bottom --version=0.4.7
```
### Auto-completion
Shell completions are included in binary releases, and are generated in the same directory as the
binary if bottom is manually built.
- For bash, move `btm.bash` to `$XDG_CONFIG_HOME/bash_completion or /etc/bash_completion.d/`.
- For fish, move `btm.fish` to `$HOME/.config/fish/completions/`.
- For zsh, move `_btm` to one of your `$fpath` directories.
- For PowerShell, add `. _btm.ps1` to your PowerShell
[profile](<https://docs.microsoft.com/en-us/previous-versions//bb613488(v=vs.85)>).
Some install scripts (i.e. AUR) will automatically do this for you.
## Usage
Run using `btm`.

31
build.rs Normal file
View file

@ -0,0 +1,31 @@
use clap::Shell;
use std::{env, fs, process};
include!("src/clap.rs");
fn main() {
// OUT_DIR is where extra build files are written to for Cargo.
let out_dir = match env::var_os("OUT_DIR") {
Some(out_dir) => out_dir,
None => {
eprintln!("The OUT_DIR environment variable was not set! Aborting...");
process::exit(1)
}
};
match fs::create_dir_all(&out_dir) {
Ok(()) => {}
Err(err) => {
eprintln!(
"Failed to create a directory at OUT_DIR location {:?}, encountered error {:?}. Aborting...",
out_dir, err
);
process::exit(1)
}
}
// Generate completions
let mut app = build_app();
app.gen_completions("btm", Shell::Bash, &out_dir);
app.gen_completions("btm", Shell::Zsh, &out_dir);
app.gen_completions("btm", Shell::Fish, &out_dir);
app.gen_completions("btm", Shell::PowerShell, &out_dir);
}

View file

@ -26,4 +26,9 @@ package() {
cd $pkgname-$pkgver
install -Dm755 target/release/btm "$pkgdir/usr/bin/btm"
install -Dm644 "LICENSE" "$pkgdir/usr/share/licenses/${pkgname}/LICENSE"
local target_dir=$(ls target/release/build/bottom-*/out/rg.bash | head -n1 | xargs dirname)
install -Dm644 "$target_dir"/_btm "$pkgdir/usr/share/zsh/site-functions/_btm"
install -Dm644 "$target_dir"/btm.bash "$pkgdir/usr/share/bash-completion/completions/btm"
install -Dm644 "$target_dir"/btm.fish "$pkgdir/usr/share/fish/vendor_completions.d/btm.fish"
}

View file

@ -10,15 +10,19 @@ arch=('x86_64')
url="https://github.com/ClementTsang/bottom"
license=(MIT)
source=(
archive-${pkgver}.tar.gz::${url}/releases/download/${pkgver}/bottom_x86_64-unknown-linux-gnu.tar.gz
LICENSE::${url}/raw/${pkgver}/LICENSE
archive-${pkgver}.tar.gz::${url}/releases/download/${pkgver}/bottom_x86_64-unknown-linux-gnu.tar.gz
LICENSE::${url}/raw/${pkgver}/LICENSE
)
sha512sums=(
'$hash1'
SKIP
'$hash1'
SKIP
)
package() {
install -Dm755 btm "$pkgdir"/usr/bin/btm
install -Dm644 LICENSE "$pkgdir"/usr/share/licenses/$pkgname/LICENSE
install -Dm755 btm "$pkgdir"/usr/bin/btm
install -Dm644 LICENSE "$pkgdir"/usr/share/licenses/$pkgname/LICENSE
install -Dm644 completion/_btm "$pkgdir/usr/share/zsh/site-functions/_btm"
install -Dm644 completion/btm.bash "$pkgdir/usr/share/bash-completion/completions/btm"
install -Dm644 completion/btm.fish "$pkgdir/usr/share/fish/vendor_completions.d/btm.fish"
}

View file

@ -11,6 +11,8 @@ class Bottom < Formula
end
def install
bash_completion.install "completion/rg.bash"
zsh_completion.install "completion/_rg"
bin.install "btm"
ohai "You're done! Run with \"btm\""
ohai "For runtime flags, see \"btm --help\""

View file

@ -29,7 +29,7 @@ fn main() -> error::Result<()> {
{
utils::logging::init_logger()?;
}
let matches = get_matches();
let matches = clap::get_matches();
let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;

273
src/clap.rs Normal file
View file

@ -0,0 +1,273 @@
use clap::*;
const TEMPLATE: &str = "\
{bin} {version}
{author}
{about}
USAGE:
{usage}
FLAGS:
{flags}
OPTIONS:
{options}
";
pub fn get_matches() -> clap::ArgMatches<'static> {
build_app().get_matches()
}
pub fn build_app() -> App<'static, 'static> {
// Temps
let kelvin = Arg::with_name("KELVIN")
.short("k")
.long("kelvin")
.help("Sets the temperature type to Kelvin.")
.long_help(
"\
Sets the temperature type to Kelvin.\n\n",
);
let fahrenheit = Arg::with_name("FAHRENHEIT")
.short("f")
.long("fahrenheit")
.help("Sets the temperature type to Fahrenheit.")
.long_help(
"\
Sets the temperature type to Fahrenheit.\n\n",
);
let celsius = Arg::with_name("CELSIUS")
.short("c")
.long("celsius")
.help("Sets the temperature type to Celsius.")
.long_help(
"\
Sets the temperature type to Celsius. This is the default
option.\n\n",
);
// All flags. These are in alphabetical order
let autohide_time = Arg::with_name("AUTOHIDE_TIME")
.long("autohide_time")
.help("Temporarily shows the time scale in graphs.")
.long_help(
"\
Automatically hides the time scaling in graphs after being
shown for a brief moment when zoomed in/out. If time is
disabled via --hide_time then this will have no effect.\n\n\n",
);
let basic = Arg::with_name("BASIC_MODE")
.short("b")
.long("basic")
.help("Hides graphs and uses a more basic look.")
.long_help(
"\
Hides graphs and uses a more basic look. Design is largely
inspired by htop's.\n\n",
);
let battery = Arg::with_name("BATTERY")
.long("battery")
.help("Shows the battery widget.")
.long_help(
"\
Shows the battery widget in default or basic mode. No effect on
custom layouts.\n\n",
);
let case_sensitive = Arg::with_name("CASE_SENSITIVE")
.short("S")
.long("case_sensitive")
.help("Enables case sensitivity by default.")
.long_help(
"\
When searching for a process, enables case sensitivity by default.\n\n",
);
let disable_click = Arg::with_name("DISABLE_CLICK")
.long("disable_click")
.help("Disables mouse clicks.")
.long_help(
"\
Disables mouse clicks from interacting with the program.\n\n",
);
let dot_marker = Arg::with_name("DOT_MARKER")
.short("m")
.long("dot_marker")
.help("Uses a dot marker for graphs.")
.long_help(
"\
Uses a dot marker for graphs as opposed to the default braille
marker.\n\n",
);
let group = Arg::with_name("GROUP_PROCESSES")
.short("g")
.long("group")
.help("Groups processes with the same name by default.")
.long_help(
"\
Groups processes with the same name by default.\n\n",
);
let hide_avg_cpu = Arg::with_name("HIDE_AVG_CPU")
.short("a")
.long("hide_avg_cpu")
.help("Hides the average CPU usage.")
.long_help(
"\
Hides the average CPU usage from being shown.\n\n",
);
let hide_table_gap = Arg::with_name("HIDE_TABLE_GAP")
.long("hide_table_gap")
.help("Hides the spacing between table headers and entries.")
.long_help(
"\
Hides the spacing between table headers and entries.\n\n",
);
let hide_time = Arg::with_name("HIDE_TIME")
.long("hide_time")
.help("Completely hides the time scaling.")
.long_help(
"\
Completely hides the time scaling from being shown.\n\n",
);
let left_legend = Arg::with_name("LEFT_LEGEND")
.short("l")
.long("left_legend")
.help("Puts the CPU chart legend to the left side.")
.long_help(
"\
Puts the CPU chart legend to the left side rather than the right side.\n\n",
);
let regex = Arg::with_name("REGEX_DEFAULT")
.short("R")
.long("regex")
.help("Enables regex by default.")
.long_help(
"\
When searching for a process, enables regex by default.\n\n",
);
let current_usage = Arg::with_name("USE_CURR_USAGE")
.short("u")
.long("current_usage")
.help("Sets process CPU% to be based on current CPU%.")
.long_help(
"\
Sets process CPU% usage to be based on the current system CPU% usage
rather than total CPU usage.\n\n",
);
let use_old_network_legend = Arg::with_name("USE_OLD_NETWORK_LEGEND")
.long("use_old_network_legend")
.help("DEPRECATED - uses the older network legend.")
.long_help(
"\
DEPRECATED - uses the older (pre-0.4) network widget legend.
This display is not tested anymore and could be broken.\n\n\n",
);
let whole_word = Arg::with_name("WHOLE_WORD")
.short("W")
.long("whole_word")
.help("Enables whole-word matching by default.")
.long_help(
"\
When searching for a process, return results that match the
entire query by default.\n\n",
);
// All options. Again, alphabetical order.
let config = Arg::with_name("CONFIG_LOCATION")
.short("C")
.long("config")
.takes_value(true)
.value_name("CONFIG PATH")
.help("Sets the location of the config file.")
.long_help(
"\
Sets the location of the config file. Expects a config
file in the TOML format. If it doesn't exist, one is created.\n\n\n",
);
let default_time_value = Arg::with_name("DEFAULT_TIME_VALUE")
.short("t")
.long("default_time_value")
.takes_value(true)
.value_name("MS")
.help("Default time value for graphs in ms.")
.long_help(
"\
Default time value for graphs in milliseconds. The minimum
time is 30s (30000), and the default is 60s (60000).\n\n\n",
);
let default_widget_count = Arg::with_name("DEFAULT_WIDGET_COUNT")
.long("default_widget_count")
.takes_value(true)
.value_name("INT")
.help("Sets the n'th selected widget type as the default.")
.long_help(
"\
Sets the n'th selected widget type to use as the default widget.
Goes from left to right, top to bottom.\n\n",
); //FIXME: Explain this
let default_widget_type = Arg::with_name("DEFAULT_WIDGET_TYPE")
.long("default_widget_type")
.takes_value(true)
.value_name("WIDGET TYPE")
.help("Sets which widget type to use as the default widget.")
.long_help(
"\
Sets which widget type to use as the default widget.
Acceptable widget types are...\n\n",
); //FIXME: Expand
let rate = Arg::with_name("RATE_MILLIS")
.short("r")
.long("rate")
.takes_value(true)
.value_name("MS")
.help("Sets a refresh rate in ms.")
.long_help(
"\
Sets a refresh rate in milliseconds. The minimum is 250ms,
and defaults to 1000ms. Smaller values may take more resources.\n\n\n",
);
let time_delta = Arg::with_name("TIME_DELTA")
.short("d")
.long("time_delta")
.takes_value(true)
.value_name("MS")
.help("The amount in ms changed upon zooming.")
.long_help(
"\
The amount of time in milliseconds changed when zooming in/out.
The minimum is 1s (1000), and defaults to 15s (15000).\n\n\n",
);
App::new(crate_name!())
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!())
.template(TEMPLATE)
.help_message("Prints help information. Use --help for more info.")
.version_message("Prints version information.")
.arg(kelvin)
.arg(fahrenheit)
.arg(celsius)
.group(ArgGroup::with_name("TEMPERATURE_TYPE").args(&["KELVIN", "FAHRENHEIT", "CELSIUS"]))
.arg(autohide_time)
.arg(basic)
.arg(battery)
.arg(case_sensitive)
.arg(disable_click)
.arg(dot_marker)
.arg(group)
.arg(hide_avg_cpu)
.arg(hide_table_gap)
.arg(hide_time)
.arg(left_legend)
.arg(regex)
.arg(current_usage)
.arg(use_old_network_legend)
.arg(whole_word)
.arg(config)
.arg(default_time_value)
.arg(default_widget_count)
.arg(default_widget_type)
.arg(rate)
.arg(time_delta)
}

View file

@ -11,8 +11,6 @@ use std::{
time::{Duration, Instant},
};
use clap::*;
use crossterm::{
event::{poll, read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent},
execute,
@ -43,6 +41,8 @@ pub mod constants;
pub mod data_conversion;
pub mod options;
pub mod clap;
pub enum BottomEvent<I, J> {
KeyInput(I),
MouseInput(J),
@ -54,42 +54,6 @@ pub enum ResetEvent {
Reset,
}
pub fn get_matches() -> clap::ArgMatches<'static> {
clap_app!(app =>
(name: crate_name!())
(version: crate_version!())
(author: crate_authors!())
(about: crate_description!())
(@arg HIDE_AVG_CPU: -a --hide_avg_cpu "Hides the average CPU usage.")
(@arg DOT_MARKER: -m --dot_marker "Use a dot marker instead of the default braille marker.")
(@group TEMPERATURE_TYPE =>
(@arg KELVIN : -k --kelvin "Sets the temperature type to Kelvin.")
(@arg FAHRENHEIT : -f --fahrenheit "Sets the temperature type to Fahrenheit.")
(@arg CELSIUS : -c --celsius "Sets the temperature type to Celsius. This is the default option.")
)
(@arg RATE_MILLIS: -r --rate +takes_value "Sets a refresh rate in milliseconds; the minimum is 250ms, defaults to 1000ms. Smaller values may take more resources.")
(@arg LEFT_LEGEND: -l --left_legend "Puts external chart legends on the left side rather than the default right side.")
(@arg USE_CURR_USAGE: -u --current_usage "Within Linux, sets a process' CPU usage to be based on the total current CPU usage, rather than assuming 100% usage.")
(@arg CONFIG_LOCATION: -C --config +takes_value "Sets the location of the config file. Expects a config file in the TOML format. If it doesn't exist, one is created.")
(@arg BASIC_MODE: -b --basic "Hides graphs and uses a more basic look")
(@arg GROUP_PROCESSES: -g --group "Groups processes with the same name together on launch.")
(@arg CASE_SENSITIVE: -S --case_sensitive "Match case when searching by default.")
(@arg WHOLE_WORD: -W --whole_word "Match whole word when searching by default.")
(@arg REGEX_DEFAULT: -R --regex "Use regex in searching by default.")
(@arg DEFAULT_TIME_VALUE: -t --default_time_value +takes_value "Default time value for graphs in milliseconds; minimum is 30s, defaults to 60s.")
(@arg TIME_DELTA: -d --time_delta +takes_value "The amount changed upon zooming in/out in milliseconds; minimum is 1s, defaults to 15s.")
(@arg HIDE_TIME: --hide_time "Completely hide the time scaling")
(@arg AUTOHIDE_TIME: --autohide_time "Automatically hide the time scaling in graphs after being shown for a brief moment when zoomed in/out. If time is disabled via --hide_time then this will have no effect.")
(@arg DEFAULT_WIDGET_TYPE: --default_widget_type +takes_value "The default widget type to select by default.")
(@arg DEFAULT_WIDGET_COUNT: --default_widget_count +takes_value "Which number of the selected widget type to select, from left to right, top to bottom. Defaults to 1.")
(@arg USE_OLD_NETWORK_LEGEND: --use_old_network_legend "Use the older (pre-0.4) network widget legend.")
(@arg HIDE_TABLE_GAP: --hide_table_gap "Hides the spacing between the table headers and entries.")
(@arg BATTERY: --battery "Shows the battery widget in default or basic mode. No effect on custom layouts.")
(@arg DISABLE_CLICK: --disable_click "Disables mouse clicks from interacting with the program.")
)
.get_matches()
}
pub fn handle_mouse_event(event: MouseEvent, app: &mut App) {
match event {
MouseEvent::ScrollUp(_x, _y, _modifiers) => app.handle_scroll_up(),