diff --git a/.clippy.toml b/.clippy.toml index fc3ef79a390..23bf481790c 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1 +1 @@ -msrv = "1.54.0" # MSRV +msrv = "1.60.0" # MSRV diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 51868bbdc77..61942039653 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,5 @@ name: Bug report -description: An issue with clap, clap_derive or clap_generate +description: An issue with clap, clap_complete, clap_derive, or clap_mangen labels: 'C-bug' body: - type: checkboxes @@ -8,7 +8,7 @@ body: options: - label: I have searched the [discussions](https://github.com/clap-rs/clap/discussions) required: true - - label: I have searched the existing issues + - label: I have searched the [open](https://github.com/clap-rs/clap/issues) and [rejected](https://github.com/clap-rs/clap/issues?q=is%3Aissue+label%3AS-wont-fix+is%3Aclosed) issues required: true - type: input attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index d72c9d78781..a28b8a62fe4 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -8,7 +8,7 @@ body: options: - label: I have searched the [discussions](https://github.com/clap-rs/clap/discussions) required: true - - label: I have searched the existing issues + - label: I have searched the [open](https://github.com/clap-rs/clap/issues) and [rejected](https://github.com/clap-rs/clap/issues?q=is%3Aissue+label%3AS-wont-fix+is%3Aclosed) issues required: true - type: input attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..0757758c8eb --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ + diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ec741d3171a..97ff7438db4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,3 +8,10 @@ updates: open-pull-requests-limit: 10 labels: - C-dependencies +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: monthly + open-pull-requests-limit: 10 + labels: + - "C-dependencies" diff --git a/.github/settings.yml b/.github/settings.yml index f80da786c71..658c28adc7e 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -108,3 +108,23 @@ branches: contexts: ["CI", "Lint Commits", "Spell Check with Typos"] enforce_admins: false restrictions: null + - name: v2-master + protection: + required_pull_request_reviews: null + required_conversation_resolution: true + required_status_checks: + # Required. Require branches to be up to date before merging. + strict: false + contexts: ["CI", "Lint Commits", "Spell Check with Typos"] + enforce_admins: false + restrictions: null + - name: v3-master + protection: + required_pull_request_reviews: null + required_conversation_resolution: true + required_status_checks: + # Required. Require branches to be up to date before merging. + strict: false + contexts: ["CI", "Lint Commits", "Spell Check with Typos"] + enforce_admins: false + restrictions: null diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index f369695ac72..d12326fe5ed 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -10,12 +10,17 @@ on: - '**/Cargo.lock' schedule: - cron: '3 3 3 * *' +permissions: + contents: read jobs: security_audit: + permissions: + issues: write # to create issues (actions-rs/audit-check) + checks: write # to create check (actions-rs/audit-check) runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - uses: actions-rs/audit-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fe3f3f4280..1a46dfcc9a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,23 +1,17 @@ name: CI on: pull_request: - paths: - - '**' - - '!/*.md' - - '!/docs/**' - - "!/LICENSE-*" push: - branches: - - master - paths: - - '**' - - '!/*.md' - - '!/docs/**' - - "!/LICENSE-*" + branches: ["*master"] schedule: - cron: '3 3 3 * *' +permissions: + contents: read + jobs: ci: + permissions: + contents: none name: CI needs: [test, check, docs, rustfmt, clippy] runs-on: ubuntu-latest @@ -28,7 +22,7 @@ jobs: name: Test strategy: matrix: - build: [linux, windows, mac, minimal, default] + build: [linux, windows, mac, minimal, default, next] include: - build: linux os: ubuntu-latest @@ -50,11 +44,15 @@ jobs: os: ubuntu-latest rust: "stable" features: "default" + - build: next + os: ubuntu-latest + rust: "stable" + features: "next" continue-on-error: ${{ matrix.rust != 'stable' }} runs-on: ${{ matrix.os }} steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust uses: actions-rs/toolchain@v1 with: @@ -80,7 +78,7 @@ jobs: build: [msrv, wasm, wasm-wasi, debug, release] include: - build: msrv - rust: 1.54.0 # MSRV + rust: 1.60.0 # MSRV target: x86_64-unknown-linux-gnu features: full - build: wasm @@ -101,7 +99,7 @@ jobs: features: release steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install rust uses: actions-rs/toolchain@v1 with: @@ -114,41 +112,45 @@ jobs: ui: name: UI Tests runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + features: [default, next] steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust uses: actions-rs/toolchain@v1 with: - toolchain: 1.54.0 # MSRV + toolchain: 1.60.0 # MSRV profile: minimal override: true - uses: Swatinem/rust-cache@v1 - name: UI Tests - run: cargo test --test derive_ui --features derive + run: make test-ui-${{ matrix.features }} docs: name: Docs runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: 1.60.0 # MSRV profile: minimal override: true - uses: Swatinem/rust-cache@v1 - name: Check documentation env: RUSTDOCFLAGS: -D warnings - run: cargo doc --workspace --all-features --no-deps --document-private-items + run: make doc rustfmt: name: rustfmt runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust uses: actions-rs/toolchain@v1 with: @@ -166,11 +168,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust uses: actions-rs/toolchain@v1 with: - toolchain: 1.54.0 # MSRV + toolchain: 1.60.0 # MSRV profile: minimal override: true components: clippy diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml index 8375557a65c..5d2f2970ddf 100644 --- a/.github/workflows/committed.yml +++ b/.github/workflows/committed.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions Repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 - name: Lint Commits diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml new file mode 100644 index 00000000000..f4c23f1a449 --- /dev/null +++ b/.github/workflows/post-release.yml @@ -0,0 +1,42 @@ +name: post-release +on: + push: + tags: + - "v*" +permissions: + contents: read + +jobs: + create-release: + permissions: + contents: write # for actions/create-release to create a release + name: create-release + runs-on: ubuntu-latest + outputs: + upload_url: ${{ steps.release.outputs.upload_url }} + release_version: ${{ env.RELEASE_VERSION }} + steps: + - name: Get the release version from the tag + shell: bash + if: env.RELEASE_VERSION == '' + run: | + # See: https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027 + echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + echo "version is: ${{ env.RELEASE_VERSION }}" + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 1 + - name: Generate Release Notes + run: | + ./.github/workflows/release-notes.py --tag ${{ env.RELEASE_VERSION }} --output notes-${{ env.RELEASE_VERSION }}.md + cat notes-${{ env.RELEASE_VERSION }}.md + - name: Create GitHub release + id: release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.RELEASE_VERSION }} + release_name: ${{ env.RELEASE_VERSION }} + body_path: notes-${{ env.RELEASE_VERSION }}.md diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 174325b0bc8..05c39619f62 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -2,11 +2,14 @@ name: pre-commit on: pull_request: push: - branches: [master] + branches: ["*master"] +permissions: {} # none jobs: pre-commit: + permissions: + contents: read runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - uses: pre-commit/action@v2.0.3 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 + - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/release-notes.py b/.github/workflows/release-notes.py new file mode 100755 index 00000000000..7a0d26d23d2 --- /dev/null +++ b/.github/workflows/release-notes.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +import argparse +import re +import pathlib +import sys + + +_STDIO = pathlib.Path("-") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("-i", "--input", type=pathlib.Path, default="CHANGELOG.md") + parser.add_argument("--tag", required=True) + parser.add_argument("-o", "--output", type=pathlib.Path, required=True) + args = parser.parse_args() + + if args.input == _STDIO: + lines = sys.stdin.readlines() + else: + with args.input.open() as fh: + lines = fh.readlines() + version = args.tag.lstrip("v") + + note_lines = [] + for line in lines: + if line.startswith("## ") and version in line: + note_lines.append(line) + elif note_lines and line.startswith("## "): + break + elif note_lines: + note_lines.append(line) + + notes = "".join(note_lines).strip() + if args.output == _STDIO: + print(notes) + else: + args.output.write_text(notes) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index f66a64970b3..dac1e4e27c6 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -2,12 +2,15 @@ name: rust-next on: schedule: - cron: '3 3 3 * *' +permissions: + contents: read + jobs: test: name: Test strategy: matrix: - build: [stable, linux, windows, mac, nightly, minimal, default] + build: [stable, linux, windows, mac, nightly, minimal, default, next] include: - build: stable os: ubuntu-latest @@ -37,11 +40,15 @@ jobs: os: ubuntu-latest rust: "stable" features: "default" + - build: next + os: ubuntu-latest + rust: "stable" + features: "next" continue-on-error: ${{ matrix.rust != 'stable' }} runs-on: ${{ matrix.os }} steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust uses: actions-rs/toolchain@v1 with: @@ -69,7 +76,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust uses: actions-rs/toolchain@v1 with: @@ -85,13 +92,13 @@ jobs: strategy: matrix: rust: - - 1.54.0 # MSRV + - 1.60.0 # MSRV - stable - continue-on-error: ${{ matrix.rust != '1.54.0' }} # MSRV + continue-on-error: ${{ matrix.rust != '1.60.0' }} # MSRV runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Rust uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 8d296517ff7..c00987ea985 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -1,12 +1,15 @@ name: Spelling on: [pull_request] +permissions: + contents: read + jobs: spelling: name: Spell Check with Typos runs-on: ubuntu-latest steps: - name: Checkout Actions Repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Spell Check Repo uses: crate-ci/typos@master diff --git a/.gitignore b/.gitignore index a9d37c560c6..eb5a316cbd1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ target -Cargo.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 10b8b7c6e76..79df3909b13 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - id: detect-private-key stages: [commit] - repo: https://github.com/crate-ci/committed - rev: v0.2.5 + rev: v1.0.1 hooks: - id: committed stages: [commit-msg] diff --git a/CHANGELOG.md b/CHANGELOG.md index f01a4d8821c..da0d571b2c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,852 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +## [4.0.2] - 2022-09-28 + +### Fixes + +- *(parser)* `SetFalse` should conflict with itself like `SetTrue` and `Set` +- *(parser)* Allow one-off overrides + +## [4.0.1] - 2022-09-28 + +### Fixes + +- *(derive)* Ensure `#[clap(...)]` attribute still works + +## [4.0.0] - 2022-09-28 + +### Highlights + +**`Arg::num_args(range)`** + +Clap has had several ways for controlling how many values will be captured without always being clear on how they interacted, including +- `Arg::multiple_values(true)` +- `Arg::number_of_values(4)` +- `Arg::min_values(2)` +- `Arg::max_values(20)` +- `Arg::takes_value(true)` + +These have now all been collapsed into `Arg::num_args` which accepts both +single values and ranges of values. `num_args` controls how many raw arguments +on the command line will be captured as values per occurrence and independent +of value delimiters. + +See [Issue 2688](https://github.com/clap-rs/clap/issues/2688) for more background. + +**Polishing Help** + +Clap strives to give a polished CLI experience out of the box with little +ceremony. With some feedback that has accumulated over time, we took this +release as an opportunity to re-evaluate our `--help` output to make sure it is +meeting that goal. + +In doing this evaluation, we wanted to keep in mind: +- Whether other CLIs had ideas that make sense to apply +- Providing an experience that fits within the rest of applications and works across all shells + +Before: +``` +git +A fictional versioning CLI + +USAGE: + git + +OPTIONS: + -h, --help Print help information + +SUBCOMMANDS: + add adds things + clone Clones repos + help Print this message or the help of the given subcommand(s) + push pushes things + stash +``` + +After: +``` +A fictional versioning CLI + +Usage: git + +Commands: + clone Clones repos + push pushes things + add adds things + stash + help Print this message or the help of the given subcommand(s) + +Options: + -h, --help Print help information +``` +- name/version header was removed because we couldn't justify the space it occupied when + - Usage already includes the name + - `--version` is available for showing the same thing (if the program has a version set) +- Usage was dropped to one line to save space +- Focus is put on the subcommands +- Headings are now Title case +- The more general term "command" is used rather than being explicit about being "subcommands" +- The output is more dense with the expectation that it won't affect legibility but will allow more content +- We've moved to a more neutral palette for highlighting elements (not highlighted above) + +In talking to users, we found some that liked clap's `man`-like experience. +When deviating from this, we are making the assumption that those are more +power users and that the majority of users wouldn't look as favorably on being +consistent with `man`. + +See [Issue 4132](https://github.com/clap-rs/clap/issues/4132) for more background. + +**More Dynamicism** + +Clap's API has focused on `&str` for performance but this can make +dealing with owned data difficult, like `#[arg(default_value_t)]` generating a +String from the default value. + +Additionally, to avoid `ArgMatches` from borrowing (and for some features we +decided to forgo), clap took the `&str` argument IDs and hashed them. This +prevented us from providing a usable API for iterating over existing arguments. + +Now clap has switched to a string newtype that gives us the flexibility to +decide whether to use `&'static str`, `Cow<'static, str>` for fast dynamic behavior, or +`Box` for dynamic behavior with small binary size. + +As an extension of that work, you can now call `ArgMatches::ids` to iterate +over the arguments and groups that were found when parsing. The newtype `Id` +was used to prevent some classes of bugs and to make it easier to understand +when opaque Ids are used vs user-visible strings. + +**Clearing Out Deprecations** + +Instead of doing all development on clap 4.0.0, we implemented a lot of new features during clap 3's development, deprecating the old API while introducing the new API, including: +- Replacing the implicit behavior for args when parsing them with `ArgAction` +- Replacing various one-off forms of value validation with the `ValueParser` API + - Allowing derives to automatically do the right thing for `PathBuf` (allowing invalid UTF-8) +- Replacing `AppSettings` and `ArgSettings` enums with getters/setters +- Clarifying terms and making them more consistent + +### Migrating + +Steps: + +0. [Upgrade to v3](https://github.com/clap-rs/clap/blob/v3-master/CHANGELOG.md#migrating) if you haven't already +1. Add CLI tests (including example below), `-h` and `--help` output at a minimum (recommendation: [trycmd](https://docs.rs/trycmd/) for snapshot testing) +2. *If using Builder API*: Explicitly set the `arg.action(ArgAction::...)` on each argument (`StoreValue` for options and `IncOccurrences` for flags) +3. Run `cargo check --features clap/deprecated` and resolve all deprecation warnings +4. Upgrade to v4 +5. Update feature flags + - *If `default-features = false`*, run `cargo add clap -F help,usage,error-context` + - Run `cargo add clap -F wrap_help` unless you want to hard code line wraps +6. Resolve compiler errors +7. Resolve behavior changes (see "subtle changes" under BREAKING CHANGES) +8. *At your leisure:* resolve new deprecation notices + +Example test (derive): +```rust +#[derive(clap::Parser)] +struct Cli { + ... +} + +#[test] +fn verify_cli() { + use clap::CommandFactory; + Cli::command().debug_assert() +} +``` + +Example test (builder): +```rust +fn cli() -> clap::Command { + ... +} + +#[test] +fn verify_cli() { + cli().debug_assert(); +} +``` + +Note: the idiomatic / recommended way of specifying different types of args in the Builder API has changed: + +Before +```rust +.arg(Arg::new("flag").long("flag")) # --flag +.arg(Arg::new("option").long("option").takes_value(true)) # --option