Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
BasedOnStyle: Google
PointerAlignment: Right
DerivePointerAlignment: false
ColumnLimit: 100
BasedOnStyle: LLVM
IndentWidth: 4
ColumnLimit: 100
AccessModifierOffset: -4
AllowShortFunctionsOnASingleLine: InlineOnly
IncludeBlocks: Regroup
IncludeIsMainRegex: '([-_]test)?$'
IncludeCategories:
Expand All @@ -13,8 +12,3 @@ IncludeCategories:
Priority: 1
- Regex: '^"'
Priority: 3
ReflowComments: true
BreakBeforeBraces: Attach
Cpp11BracedListStyle: true
AllowShortFunctionsOnASingleLine: InlineOnly
SortIncludes: true
13 changes: 13 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,16 @@ Checks: >
readability-container-size-empty
WarningsAsErrors: "*"
FormatStyle: file
CheckOptions:
- { key: readability-identifier-naming.FunctionCase, value: CamelCase }
- { key: readability-identifier-naming.MethodCase, value: CamelCase }
- { key: readability-identifier-naming.ClassCase, value: CamelCase }
- { key: readability-identifier-naming.StructCase, value: CamelCase }
- { key: readability-identifier-naming.EnumCase, value: CamelCase }
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
- { key: readability-identifier-naming.VariableCase, value: lower_case }
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
- { key: readability-identifier-naming.MemberCase, value: lower_case }
- { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
- { key: readability-identifier-naming.PrivateMemberSuffix, value: _ }
- { key: readability-identifier-naming.ConstantCase, value: lower_case }
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ on:
- "Dockerfile.musl"

permissions:
contents: write
contents: read

jobs:
build:
Expand Down
25 changes: 14 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ permissions:
jobs:
release_binaries:
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Resolve release version
id: version
run: echo "version=$(make get_project_version)" >> "$GITHUB_OUTPUT"

- name: Download Linux GLIBC binary
uses: actions/download-artifact@v8
with:
Expand All @@ -39,13 +45,14 @@ jobs:
with:
name: mpqcli-windows-amd64.exe

- name: Extract latest changelog section
- name: Extract changelog for this tag
id: changelog
run: |
CHANGELOG_CONTENT=$(awk '/^## /{i++} i==1{print}' CHANGELOG.md | tail -n +2)
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
{
echo "content<<EOF"
make get_changelog TAG="${{ steps.version.outputs.version }}"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Release package
run: |
Expand Down Expand Up @@ -74,17 +81,13 @@ jobs:
run: |
docker load -i mpqcli-docker.tar

- name: Extract version without "v"
id: version
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV

- name: Tag Docker image with version
run: |
docker tag mpqcli ghcr.io/thegraydot/mpqcli:${{ env.VERSION }}
docker tag mpqcli ghcr.io/thegraydot/mpqcli:${{ needs.release_binaries.outputs.version }}
docker tag mpqcli ghcr.io/thegraydot/mpqcli:latest

- name: Push Docker image to GitHub Container Registry
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker push ghcr.io/thegraydot/mpqcli:${{ env.VERSION }}
docker push ghcr.io/thegraydot/mpqcli:${{ needs.release_binaries.outputs.version }}
docker push ghcr.io/thegraydot/mpqcli:latest
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_call

permissions:
contents: write
contents: read

jobs:
test_linux:
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 0.10.1 - 2026-07-03

### Added

- Command completion for common shells (bash, zsh, fish and powershell)

### Fixed

- Memory leak in read subcommand
- Inconsistencies in documentation

### Updated

- Code style to conform to Google CPP style with slight variations
- StormLib dependency for a variety of fixes

### Thanks

- Thanks to @sjoblomj for the contributions in this release

## 0.10.0 - 2026-06-07

### Added
Expand Down
10 changes: 8 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.10)

project(MPQCLI VERSION 0.10.0)
project(MPQCLI VERSION 0.10.1)

# Options
option(BUILD_MPQCLI "Build the mpqcli CLI app" ON)
Expand All @@ -25,11 +25,17 @@ if(BUILD_STATIC)
endif()

# Determine git commit hash
execute_process (
execute_process(
COMMAND git rev-parse HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_STRIP_TRAILING_WHITESPACE
OUTPUT_VARIABLE GIT_COMMIT_HASH
RESULT_VARIABLE GIT_COMMIT_RESULT
ERROR_QUIET
)
if(NOT GIT_COMMIT_RESULT EQUAL 0 OR NOT GIT_COMMIT_HASH)
set(GIT_COMMIT_HASH "unknown")
endif()

# Handle StormLib dependency
if (NOT EXISTS "${CMAKE_SOURCE_DIR}/extern/StormLib/CMakeLists.txt")
Expand Down
11 changes: 7 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Run `make help` to list all available targets. Common ones:
| Target | Description |
|----------------------------|--------------------------------------------------------------------|
| `make install_clang_tools` | Install clang-format and clang-tidy via apt |
| `make configure` | Configure cmake build with clang (required before `make lint`) |
| `make build_linux` | Build for Linux using cmake |
| `make build_windows` | Build for Windows using cmake |
| `make build_clean` | Remove the cmake build directory |
Expand Down Expand Up @@ -72,9 +73,10 @@ If your change adds or modifies user-facing functionality - such as a new subcom

### 4. Linting must pass

All C++ code is formatted with clang-format and analysed with clang-tidy. Run the full suite before submitting:
All C++ code is formatted with clang-format and analysed with clang-tidy. `clang-tidy` needs a compile database generated with clang, so run `make configure` first (`make build_linux`/`make build_windows` alone will not work, since they don't set up the compiler flags clang-tidy needs):

```
make configure
make lint
```

Expand All @@ -88,7 +90,7 @@ Then re-run `make lint` to confirm everything passes.

### 5. Match the existing code style

C++ formatting is enforced by `.clang-format` (Google style base). Static analysis is enforced by `.clang-tidy`. Both configs live in the repo root. Python tests should follow the style of the existing test files.
C++ formatting is enforced by `.clang-format` (LLVM style base). Static analysis is enforced by `.clang-tidy`. Both configs live in the repo root. Python tests should follow the style of the existing test files.

#### Suppression policy

Expand All @@ -106,7 +108,8 @@ Suppressions are occasionally necessary for third-party code or intentional patt
Use `// clang-format off` / `// clang-format on` only when the default formatting genuinely hurts readability (e.g. column-aligned tables). Add a brief comment explaining the intent:

```cpp
// clang-format off: preserve column-aligned flag-to-char mappings for readability
// Preserve column-aligned flag-to-char mappings for readability
// clang-format off
if (flags & MPQ_FILE_IMPLODE) result += 'i';
if (flags & MPQ_FILE_COMPRESS) result += 'c';
// clang-format on
Expand All @@ -131,6 +134,6 @@ If you add a new StormLib call that is locale-sensitive, follow the existing pat
2. Run `git submodule update --init --recursive` after cloning
3. Run `make install_clang_tools` to install lint dependencies
4. Make your changes and verify they build: `make build_linux`
5. Run `make lint` and fix any issues
5. Run `make configure` and then `make lint`, fixing any issues
6. Run `make test_mpqcli` and confirm all tests pass
7. Open a pull request with a clear description of what was changed and why
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

![Release Version](https://img.shields.io/github/v/release/thegraydot/mpqcli?style=flat)

![Release downloads](https://img.shields.io/github/downloads/thegraydot/mpqcli/total?label=release_downloads) ![Package downloads](https://img.shields.io/badge/package_downloads-894-green)
![Release downloads](https://img.shields.io/github/downloads/thegraydot/mpqcli/total?label=release_downloads) ![Package downloads](https://img.shields.io/badge/package_downloads-996-green)

A command-line tool to create, add, remove, list, extract, read, and verify MPQ archives using the [StormLib library](https://github.com/ladislav-zezula/StormLib).

Expand Down
18 changes: 9 additions & 9 deletions docs/commands/completion.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ $ source ~/.bash_completion.d/mpqcli
Alternatively, write the script to a system-wide completions directory (requires root):

```bash
$ mpqcli completion bash > /etc/bash_completion.d/mpqcli
$ mpqcli completion bash | sudo tee /etc/bash_completion.d/mpqcli > /dev/null
```

## Zsh
Expand All @@ -32,18 +32,18 @@ Write the completion script to a directory that is on your `$fpath`.
$ mpqcli completion zsh > "${fpath[1]}/_mpqcli"
```

## PowerShell

Append the completion script to your PowerShell profile so it loads automatically.

```powershell
PS> mpqcli completion powershell >> $PROFILE
```

## Fish

Write the completion script to the fish completions directory.

```fish
$ mpqcli completion fish > ~/.config/fish/completions/mpqcli.fish
```

## PowerShell

Append the completion script to your PowerShell profile so it loads automatically.

```powershell
PS> mpqcli completion powershell >> $PROFILE
```
54 changes: 35 additions & 19 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,27 @@ git submodule update --init --recursive
Install the clang lint tools:

```
make setup
make install_clang_tools
```

## Makefile Reference

Run `make help` to list all available targets. Common ones:

| Target | Description |
|---|---|
| `make setup` | Install clang-format and clang-tidy via apt |
| `make build_linux` | Build for Linux using cmake |
| `make build_windows` | Build for Windows using cmake |
| `make build_clean` | Remove the cmake build directory |
| `make test_create_venv` | Create Python venv and install test dependencies (first-time only) |
| `make test_mpqcli` | Run the pytest test suite |
| `make lint` | Run all C++ linters (clang-format + clang-tidy) |
| `make lint_format` | Check formatting only (dry run) |
| `make lint_format_fix` | Auto-fix formatting in-place |
| `make lint_cpp` | Run clang-tidy static analysis |
| `make clean` | Remove all build and test artifacts |
| Target | Description |
|----------------------------|--------------------------------------------------------------------|
| `make install_clang_tools` | Install clang-format and clang-tidy via apt |
| `make configure` | Configure cmake build with clang (required before `make lint`) |
| `make build_linux` | Build for Linux using cmake |
| `make build_windows` | Build for Windows using cmake |
| `make build_clean` | Remove the cmake build directory |
| `make test_create_venv` | Create Python venv and install test dependencies (first-time only) |
| `make test_mpqcli` | Run the pytest test suite |
| `make lint` | Run all C++ linters (clang-format + clang-tidy) |
| `make fmt_check` | Check formatting only (dry run) |
| `make fmt` | Auto-fix formatting in-place |
| `make lint_cpp` | Run clang-tidy static analysis |
| `make clean` | Remove all build and test artifacts |

## Requirements for a Pull Request

Expand Down Expand Up @@ -72,23 +73,24 @@ If your change adds or modifies user-facing functionality - such as a new subcom

### 4. Linting must pass

All C++ code is formatted with clang-format and analysed with clang-tidy. Run the full suite before submitting:
All C++ code is formatted with clang-format and analysed with clang-tidy. `clang-tidy` needs a compile database generated with clang, so run `make configure` first (`make build_linux`/`make build_windows` alone will not work, since they don't set up the compiler flags clang-tidy needs):

```
make configure
make lint
```

If there are formatting violations, auto-fix them with:

```
make lint_format_fix
make fmt
```

Then re-run `make lint` to confirm everything passes.

### 5. Match the existing code style

C++ formatting is enforced by `.clang-format` (Google style base). Static analysis is enforced by `.clang-tidy`. Both configs live in the repo root. Python tests should follow the style of the existing test files.
C++ formatting is enforced by `.clang-format` (LLVM style base). Static analysis is enforced by `.clang-tidy`. Both configs live in the repo root. Python tests should follow the style of the existing test files.

#### Suppression policy

Expand All @@ -106,18 +108,32 @@ Suppressions are occasionally necessary for third-party code or intentional patt
Use `// clang-format off` / `// clang-format on` only when the default formatting genuinely hurts readability (e.g. column-aligned tables). Add a brief comment explaining the intent:

```cpp
// clang-format off: preserve column-aligned flag-to-char mappings for readability
// Preserve column-aligned flag-to-char mappings for readability
// clang-format off
if (flags & MPQ_FILE_IMPLODE) result += 'i';
if (flags & MPQ_FILE_COMPRESS) result += 'c';
// clang-format on
```

## Known Design Constraints

### StormLib locale state is global and not thread-safe

`SFileSetLocale` sets a process-wide locale variable (`g_lcFileLocale`) inside StormLib. All locale-sensitive operations in `mpq.cpp` - file open, add, remove, read, extract, and list - call `SFileSetLocale` immediately before the relevant StormLib call. There is no locale-explicit alternative in StormLib's public API (`SFileOpenFileEx`, `SFileAddFileEx`, etc. all read `g_lcFileLocale` internally).

This means:

- The `SFileSetLocale` + StormLib-call sequence is **not atomic** and would be unsafe under concurrency
- mpqcli is intentionally **single-threaded**; do not introduce threads or async I/O without auditing every locale-sensitive call site in `mpq.cpp`

If you add a new StormLib call that is locale-sensitive, follow the existing pattern: call `SFileSetLocale` immediately before it, with no intervening calls between the two.

## Workflow Summary

1. Fork the repository and create a branch for your change
2. Run `git submodule update --init --recursive` after cloning
3. Run `make install_clang_tools` to install lint dependencies
4. Make your changes and verify they build: `make build_linux`
5. Run `make lint` and fix any issues
5. Run `make configure` and then `make lint`, fixing any issues
6. Run `make test_mpqcli` and confirm all tests pass
7. Open a pull request with a clear description of what was changed and why
2 changes: 1 addition & 1 deletion extern/CLI11
Submodule CLI11 updated 47 files
+27 −0 .all-contributorsrc
+9 −9 .github/CONTRIBUTING.md
+40 −0 .github/workflows/copilot-setup-steps.yml
+4 −0 .gitignore
+6 −5 .pre-commit-config.yaml
+111 −0 AGENTS.md
+14 −9 README.md
+4 −1 azure-pipelines.yml
+6 −0 book/chapters/installation.md
+4 −0 examples/CMakeLists.txt
+3 −2 examples/help_usage.cpp
+19 −0 examples/minimal.cpp
+0 −3 fuzz/fuzzApp.cpp
+17 −8 include/CLI/App.hpp
+6 −5 include/CLI/Error.hpp
+28 −18 include/CLI/ExtraValidators.hpp
+1 −1 include/CLI/FormatterFwd.hpp
+4 −0 include/CLI/Option.hpp
+5 −2 include/CLI/StringTools.hpp
+1 −9 include/CLI/Timer.hpp
+65 −5 include/CLI/TypeTools.hpp
+4 −4 include/CLI/Validators.hpp
+57 −76 include/CLI/impl/App_inl.hpp
+0 −1 include/CLI/impl/Argv_inl.hpp
+57 −27 include/CLI/impl/Config_inl.hpp
+20 −8 include/CLI/impl/Encoding_inl.hpp
+1 −1 include/CLI/impl/ExtraValidators_inl.hpp
+50 −16 include/CLI/impl/Formatter_inl.hpp
+16 −8 include/CLI/impl/Option_inl.hpp
+54 −27 include/CLI/impl/StringTools_inl.hpp
+6 −3 include/CLI/impl/Validators_inl.hpp
+0 −1 meson.build
+0 −36 scripts/clang-format-pre-commit
+4 −0 src/CMakeLists.txt
+71 −1 tests/AppTest.cpp
+50 −27 tests/CMakeLists.txt
+124 −0 tests/ConfigFileTest.cpp
+40 −0 tests/EncodingTest.cpp
+36 −0 tests/FormatterTest.cpp
+8 −0 tests/HelpTest.cpp
+126 −0 tests/HelpersTest.cpp
+33 −0 tests/OptionGroupTest.cpp
+31 −0 tests/OptionTypeTest.cpp
+36 −0 tests/OptionalTest.cpp
+34 −0 tests/SubcommandTest.cpp
+46 −0 tests/TransformTest.cpp
+1 −1 tests/catch.hpp
Loading