Skip to content

Commit fd4ef23

Browse files
authored
Generate completions at compile-time (#118)
1 parent 9e39d46 commit fd4ef23

File tree

7 files changed

+152
-53
lines changed

7 files changed

+152
-53
lines changed
Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
name: Build assets for a Release
22

33
on:
4-
release:
5-
types: [published]
4+
push:
5+
tags:
6+
- "v[0-9]+.[0-9]+.[0-9]+"
67

78
jobs:
89
build-artifact:
@@ -12,39 +13,52 @@ jobs:
1213
os: [ubuntu-latest, windows-latest]
1314
include:
1415
- os: ubuntu-latest
15-
exe_suffix: ""
1616
target: x86_64-unknown-linux-musl
1717
- os: windows-latest
18-
exe_suffix: ".exe"
1918
target: x86_64-pc-windows-msvc
2019
steps:
21-
- uses: actions/checkout@v2
20+
- uses: actions/checkout@v3
21+
2222
- uses: actions-rs/toolchain@v1
2323
with:
2424
profile: minimal
2525
toolchain: stable
2626
target: ${{ matrix.target }}
2727
override: true
28+
2829
- name: Cache dependencies
29-
uses: Swatinem/rust-cache@v1
30-
- uses: actions-rs/cargo@v1
30+
uses: Swatinem/rust-cache@v2
31+
32+
- name: Build
33+
run: cargo build --release --locked --verbose --target ${{ matrix.target }}
34+
35+
- name: Pack completions
36+
if: matrix.os == 'ubuntu-latest' # Runs only once
37+
run: zip -r completions.zip completions
38+
39+
- name: Upload asset on Linux
40+
uses: softprops/action-gh-release@v1
41+
env:
42+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43+
if: startsWith(github.ref, 'refs/tags/') && matrix.os == 'ubuntu-latest'
3144
with:
32-
command: build
33-
args: --release --locked --verbose --target ${{ matrix.target }}
34-
- name: Upload asset
35-
uses: actions/upload-release-asset@v1.0.2
45+
files: |
46+
target/${{ matrix.target }}/release/dotter
47+
completions.zip
48+
49+
- name: Upload asset on Windows
50+
uses: softprops/action-gh-release@v1
3651
env:
3752
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53+
if: startsWith(github.ref, 'refs/tags/') && matrix.os == 'windows-latest'
3854
with:
39-
asset_path: target/${{ matrix.target }}/release/dotter${{ matrix.exe_suffix }}
40-
asset_name: dotter${{ matrix.exe_suffix }}
41-
asset_content_type: application/octet-stream
42-
upload_url: ${{ github.event.release.upload_url }}
55+
files: target/${{ matrix.target }}/release/dotter.exe
56+
4357
cargo-publish:
44-
runs-on: ubuntu-latest
45-
steps:
46-
- uses: actions/checkout@v1
47-
- run: cargo login ${CRATES_IO_TOKEN}
48-
env:
49-
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
50-
- run: cargo publish
58+
runs-on: ubuntu-latest
59+
steps:
60+
- uses: actions/checkout@v3
61+
- run: cargo login ${CRATES_IO_TOKEN}
62+
env:
63+
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
64+
- run: cargo publish

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,13 @@
1+
# Generated by Cargo
2+
# will have compiled files and executables
3+
debug/
14
target/
5+
6+
# These are backup files generated by rustfmt
7+
**/*.rs.bk
8+
9+
# MSVC Windows builds of rustc generate these, which store debugging information
10+
*.pdb
11+
12+
# Generated by build.rs
13+
completions/

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ license = "Unlicense"
1414
anyhow = "1.*"
1515
clap = { version = "4.0.26", features = ["derive"] }
1616
clap_complete = "4.0.5"
17+
clap_complete_nushell = "0.1.10"
1718
crossterm = "0.25.0"
1819
diff = "0.1.*"
1920
handlebars = { version = "4.*", features = ["script_helper"] }
@@ -38,6 +39,11 @@ mockall = "0.11.3"
3839
# Enable this instead for better failure messages (on nightly only)
3940
# mockall = { version = "0.9.*", features = ["nightly"] }
4041

42+
[build-dependencies]
43+
clap = { version = "4.0.26", features = ["derive"] }
44+
clap_complete = "4.0.5"
45+
clap_complete_nushell = "0.1.10"
46+
4147
[target.'cfg(windows)'.dependencies]
4248
dunce = "1.*"
4349

build.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#[allow(dead_code)]
2+
#[path = "src/args.rs"]
3+
mod args;
4+
5+
use self::args::Options;
6+
use clap::CommandFactory;
7+
use clap_complete::generate_to;
8+
use clap_complete::Shell::*;
9+
use clap_complete_nushell::Nushell;
10+
use std::fs;
11+
use std::io;
12+
13+
fn main() -> io::Result<()> {
14+
let cmd = &mut Options::command();
15+
let name = "dotter";
16+
let dir = "completions";
17+
18+
fs::create_dir_all(dir)?;
19+
20+
generate_to(Bash, cmd, name, dir)?;
21+
generate_to(Zsh, cmd, name, dir)?;
22+
generate_to(Elvish, cmd, name, dir)?;
23+
generate_to(Fish, cmd, name, dir)?;
24+
generate_to(PowerShell, cmd, name, dir)?;
25+
generate_to(Nushell, cmd, name, dir)?;
26+
27+
Ok(())
28+
}

src/args.rs

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,78 @@
1+
use std::io;
12
use std::path::PathBuf;
23

3-
use clap::{Parser, Subcommand};
4-
use clap_complete::Shell;
4+
use clap::{Command, Parser, Subcommand, ValueEnum};
5+
use clap_complete::Generator;
56

67
/// A small dotfile manager.
78
#[derive(Debug, Parser, Default, Clone)]
89
#[clap(author, version, about, long_about = None)]
910
pub struct Options {
1011
/// Location of the global configuration
11-
#[clap(
12-
short,
13-
long,
14-
value_parser,
15-
default_value = ".dotter/global.toml",
16-
global = true
17-
)]
12+
#[arg(short, long, default_value = ".dotter/global.toml", global = true)]
1813
pub global_config: PathBuf,
1914

2015
/// Location of the local configuration
21-
#[clap(
22-
short,
23-
long,
24-
value_parser,
25-
default_value = ".dotter/local.toml",
26-
global = true
27-
)]
16+
#[arg(short, long, default_value = ".dotter/local.toml", global = true)]
2817
pub local_config: PathBuf,
2918

3019
/// Location of cache file
31-
#[clap(long, value_parser, default_value = ".dotter/cache.toml")]
20+
#[arg(long, default_value = ".dotter/cache.toml")]
3221
pub cache_file: PathBuf,
3322

3423
/// Directory to cache into.
35-
#[clap(long, value_parser, default_value = ".dotter/cache")]
24+
#[arg(long, default_value = ".dotter/cache")]
3625
pub cache_directory: PathBuf,
3726

3827
/// Location of optional pre-deploy hook
39-
#[clap(long, value_parser, default_value = ".dotter/pre_deploy.sh")]
28+
#[arg(long, default_value = ".dotter/pre_deploy.sh")]
4029
pub pre_deploy: PathBuf,
4130

4231
/// Location of optional post-deploy hook
43-
#[clap(long, value_parser, default_value = ".dotter/post_deploy.sh")]
32+
#[arg(long, default_value = ".dotter/post_deploy.sh")]
4433
pub post_deploy: PathBuf,
4534

4635
/// Location of optional pre-undeploy hook
47-
#[clap(long, value_parser, default_value = ".dotter/pre_undeploy.sh")]
36+
#[arg(long, default_value = ".dotter/pre_undeploy.sh")]
4837
pub pre_undeploy: PathBuf,
4938

5039
/// Location of optional post-undeploy hook
51-
#[clap(long, value_parser, default_value = ".dotter/post_undeploy.sh")]
40+
#[arg(long, default_value = ".dotter/post_undeploy.sh")]
5241
pub post_undeploy: PathBuf,
5342

5443
/// Dry run - don't do anything, only print information.
5544
/// Implies -v at least once
56-
#[clap(short = 'd', long = "dry-run", global = true)]
45+
#[arg(short = 'd', long = "dry-run", global = true)]
5746
pub dry_run: bool,
5847

5948
/// Verbosity level - specify up to 3 times to get more detailed output.
6049
/// Specifying at least once prints the differences between what was before and after Dotter's run
61-
#[clap(short = 'v', long = "verbose", action = clap::ArgAction::Count, global = true)]
50+
#[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count, global = true)]
6251
pub verbosity: u8,
6352

6453
/// Quiet - only print errors
65-
#[clap(short, long, value_parser, global = true)]
54+
#[arg(short, long, global = true)]
6655
pub quiet: bool,
6756

6857
/// Force - instead of skipping, overwrite target files if their content is unexpected.
6958
/// Overrides --dry-run.
70-
#[clap(short, long, value_parser, global = true)]
59+
#[arg(short, long, global = true)]
7160
pub force: bool,
7261

7362
/// Assume "yes" instead of prompting when removing empty directories
74-
#[clap(short = 'y', long = "noconfirm", global = true)]
63+
#[arg(short = 'y', long = "noconfirm", global = true)]
7564
pub noconfirm: bool,
7665

7766
/// Take standard input as an additional files/variables patch, added after evaluating
7867
/// `local.toml`. Assumes --noconfirm flag because all of stdin is taken as the patch.
79-
#[clap(short, long, value_parser, global = true)]
68+
#[arg(short, long, global = true)]
8069
pub patch: bool,
8170

8271
/// Amount of lines that are printed before and after a diff hunk.
83-
#[clap(long, value_parser, default_value = "3")]
72+
#[arg(long, default_value = "3")]
8473
pub diff_context_lines: usize,
8574

86-
#[clap(subcommand)]
75+
#[command(subcommand)]
8776
pub action: Option<Action>,
8877
}
8978

@@ -107,12 +96,50 @@ pub enum Action {
10796

10897
/// Generate shell completions
10998
GenCompletions {
110-
/// Set the shell for generating completions [values: bash, elvish, fish, powerShell, zsh]
99+
/// Set the shell for generating completions [values: bash, elvish, fish, powershell, zsh, nushell]
111100
#[clap(long, short)]
112101
shell: Shell,
113102
},
114103
}
115104

105+
#[derive(Debug, Clone, Copy, ValueEnum)]
106+
pub enum Shell {
107+
Bash,
108+
Elvish,
109+
Fish,
110+
Powershell,
111+
Zsh,
112+
Nushell,
113+
}
114+
115+
impl Generator for Shell {
116+
fn file_name(&self, name: &str) -> String {
117+
use clap_complete::Shell::*;
118+
use clap_complete_nushell::Nushell;
119+
match self {
120+
Self::Bash => Bash.file_name(name),
121+
Self::Elvish => Elvish.file_name(name),
122+
Self::Fish => Fish.file_name(name),
123+
Self::Powershell => PowerShell.file_name(name),
124+
Self::Zsh => Zsh.file_name(name),
125+
Self::Nushell => Nushell.file_name(name),
126+
}
127+
}
128+
129+
fn generate(&self, cmd: &Command, buf: &mut dyn io::Write) {
130+
use clap_complete::Shell::*;
131+
use clap_complete_nushell::Nushell;
132+
match self {
133+
Self::Bash => Bash.generate(cmd, buf),
134+
Self::Elvish => Elvish.generate(cmd, buf),
135+
Self::Fish => Fish.generate(cmd, buf),
136+
Self::Powershell => PowerShell.generate(cmd, buf),
137+
Self::Zsh => Zsh.generate(cmd, buf),
138+
Self::Nushell => Nushell.generate(cmd, buf),
139+
}
140+
}
141+
}
142+
116143
pub fn get_options() -> Options {
117144
let mut opt = Options::parse();
118145
if opt.dry_run {

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ Otherwise, run `dotter undeploy` as root, remove cache.toml and cache/ folders,
109109
.block_on(watch::watch(opt))
110110
.context("watch repository")?;
111111
}
112+
112113
args::Action::GenCompletions { shell } => {
113114
generate(
114115
shell,

0 commit comments

Comments
 (0)