From fb9e39261378ec1d6fc2c4390727fcf02af36e3f Mon Sep 17 00:00:00 2001 From: thomaslaurenson Date: Sat, 6 Jun 2026 18:30:03 +1200 Subject: [PATCH 1/8] Added completion solution for bash, zsh, fish and ps1 --- src/CMakeLists.txt | 16 +++ src/completion.cpp | 21 +++ src/completion.h | 9 ++ src/completion/mpqcli.bash | 202 ++++++++++++++++++++++++++++ src/completion/mpqcli.fish | 208 ++++++++++++++++++++++++++++ src/completion/mpqcli.ps1 | 269 +++++++++++++++++++++++++++++++++++++ src/completion/mpqcli.zsh | 159 ++++++++++++++++++++++ src/completion_data.h.in | 16 +++ src/main.cpp | 32 +++++ src/mpq.cpp | 2 +- 10 files changed, 933 insertions(+), 1 deletion(-) create mode 100644 src/completion.cpp create mode 100644 src/completion.h create mode 100644 src/completion/mpqcli.bash create mode 100644 src/completion/mpqcli.fish create mode 100644 src/completion/mpqcli.ps1 create mode 100644 src/completion/mpqcli.zsh create mode 100644 src/completion_data.h.in diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 21bca10..a8bdb17 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,21 @@ # Configure version header configure_file(mpqcli.h.in "${CMAKE_BINARY_DIR}/mpqcli.h") +# Embed completion scripts into generated headers. +# Declare the script files as configure-time dependencies so CMake re-generates +# completion_data.h whenever the bash or zsh source scripts are edited. +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/completion/mpqcli.bash" + "${CMAKE_CURRENT_SOURCE_DIR}/completion/mpqcli.zsh" + "${CMAKE_CURRENT_SOURCE_DIR}/completion/mpqcli.ps1" + "${CMAKE_CURRENT_SOURCE_DIR}/completion/mpqcli.fish" +) +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/completion/mpqcli.bash" BASH_COMPLETION_SCRIPT) +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/completion/mpqcli.zsh" ZSH_COMPLETION_SCRIPT) +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/completion/mpqcli.ps1" PS_COMPLETION_SCRIPT) +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/completion/mpqcli.fish" FISH_COMPLETION_SCRIPT) +configure_file(completion_data.h.in "${CMAKE_BINARY_DIR}/completion_data.h") + # Set output directory for the executable set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") @@ -14,6 +29,7 @@ endif() add_executable(mpqcli main.cpp commands.cpp + completion.cpp mpq.cpp helpers.cpp locales.cpp diff --git a/src/completion.cpp b/src/completion.cpp new file mode 100644 index 0000000..ce787a9 --- /dev/null +++ b/src/completion.cpp @@ -0,0 +1,21 @@ +#include "completion.h" + +#include + +#include "completion_data.h" + +void HandleCompletionBash() { + std::cout << BashCompletionScript; +} + +void HandleCompletionZsh() { + std::cout << ZshCompletionScript; +} + +void HandleCompletionPs() { + std::cout << PsCompletionScript; +} + +void HandleCompletionFish() { + std::cout << FishCompletionScript; +} diff --git a/src/completion.h b/src/completion.h new file mode 100644 index 0000000..990aa79 --- /dev/null +++ b/src/completion.h @@ -0,0 +1,9 @@ +#ifndef COMPLETION_H +#define COMPLETION_H + +void HandleCompletionBash(); +void HandleCompletionZsh(); +void HandleCompletionPs(); +void HandleCompletionFish(); + +#endif // COMPLETION_H diff --git a/src/completion/mpqcli.bash b/src/completion/mpqcli.bash new file mode 100644 index 0000000..d45f213 --- /dev/null +++ b/src/completion/mpqcli.bash @@ -0,0 +1,202 @@ +# Use _filedir when available, otherwise fall back to compgen. +# Pass an extension (e.g. "mpq") to restrict completions to that type plus directories. +_mpqcli_filedir() { + if declare -f _filedir > /dev/null 2>&1; then + _filedir "${1-}" + else + if [[ -n "${1-}" ]]; then + local -a _f _d + mapfile -t _f < <(compgen -f -X "!*.$1" -- "$cur") + mapfile -t _d < <(compgen -d -- "$cur") + COMPREPLY=("${_f[@]}" "${_d[@]}") + else + mapfile -t COMPREPLY < <(compgen -f -- "$cur") + fi + fi +} + +_mpqcli() { + local cur prev words cword + + # Use bash-completion helpers when available, fall back to COMP_* variables. + if declare -f _init_completion > /dev/null 2>&1; then + _init_completion || return + else + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + words=("${COMP_WORDS[@]}") + cword=$COMP_CWORD + fi + + local subcommands="version about info create add remove list extract read verify completion" + local -a locales=( + default enUS zhTW csCZ deDE esES frFR itIT + jaJP koKR nlNL plPL ptBR ruRU zhCN enGB esMX ptPT + ) + local -a games=( + generic diablo1 lordsofmagic starcraft1 warcraft2 diablo2 + warcraft3 warcraft3-map wow-vanilla wow-tbc wow-wotlk + wow-cataclysm wow-mop starcraft2 diablo3 + ) + local -a info_properties=( + format-version header-offset header-size archive-size + file-count max-files signature-type + ) + local -a list_properties=( + hash-index name-hash1 name-hash2 name-hash3 locale + file-index byte-offset file-time file-size compressed-size + flags encryption-key encryption-key-raw + ) + + if [[ $cword -eq 1 ]]; then + mapfile -t COMPREPLY < <(compgen -W "$subcommands" -- "$cur") + return + fi + + local subcmd="${words[1]}" + + case "$subcmd" in + info) + case "$prev" in + -p|--property) + mapfile -t COMPREPLY < <(compgen -W "${info_properties[*]}" -- "$cur") + return ;; + esac + if [[ "$cur" == -* ]]; then + mapfile -t COMPREPLY < <(compgen -W "-p --property" -- "$cur") + else + _mpqcli_filedir mpq + fi + ;; + + create) + case "$prev" in + --locale) + mapfile -t COMPREPLY < <(compgen -W "${locales[*]}" -- "$cur") + return ;; + -g|--game) + mapfile -t COMPREPLY < <(compgen -W "${games[*]}" -- "$cur") + return ;; + --version) + mapfile -t COMPREPLY < <(compgen -W "1 2 3 4" -- "$cur") + return ;; + -p|--path|-o|--output|--stream-flags|--sector-size|\ + --raw-chunk-size|--file-flags1|--file-flags2|--file-flags3|--attr-flags|\ + --flags|--compression|--compression-next) + return ;; + esac + if [[ "$cur" == -* ]]; then + mapfile -t COMPREPLY < <(compgen -W "-p --path -o --output -s --sign \ +--locale -g --game --version --stream-flags --sector-size --raw-chunk-size \ +--file-flags1 --file-flags2 --file-flags3 --attr-flags \ +--flags --compression --compression-next" -- "$cur") + else + _mpqcli_filedir + fi + ;; + + add) + case "$prev" in + --locale) + mapfile -t COMPREPLY < <(compgen -W "${locales[*]}" -- "$cur") + return ;; + -g|--game) + mapfile -t COMPREPLY < <(compgen -W "${games[*]}" -- "$cur") + return ;; + -p|--path|--flags|--compression|--compression-next) + return ;; + esac + if [[ "$cur" == -* ]]; then + mapfile -t COMPREPLY < <(compgen -W "-p --path -w --overwrite -u --update \ +--locale -g --game \ +--flags --compression --compression-next" -- "$cur") + else + _mpqcli_filedir + fi + ;; + + remove) + case "$prev" in + --locale) + mapfile -t COMPREPLY < <(compgen -W "${locales[*]}" -- "$cur") + return ;; + esac + if [[ "$cur" == -* ]]; then + mapfile -t COMPREPLY < <(compgen -W "--locale" -- "$cur") + else + _mpqcli_filedir + fi + ;; + + list) + case "$prev" in + -l|--listfile) + _mpqcli_filedir + return ;; + -p|--property) + mapfile -t COMPREPLY < <(compgen -W "${list_properties[*]}" -- "$cur") + return ;; + esac + if [[ "$cur" == -* ]]; then + mapfile -t COMPREPLY < <(compgen -W "-l --listfile -d --detailed -a --all -p --property" -- "$cur") + else + _mpqcli_filedir mpq + fi + ;; + + extract) + case "$prev" in + -o|--output) + _mpqcli_filedir + return ;; + -l|--listfile) + _mpqcli_filedir + return ;; + --locale) + mapfile -t COMPREPLY < <(compgen -W "${locales[*]}" -- "$cur") + return ;; + -f|--file) + return ;; + esac + if [[ "$cur" == -* ]]; then + mapfile -t COMPREPLY < <(compgen -W "-o --output -f --file -k --keep -l --listfile --locale" -- "$cur") + else + _mpqcli_filedir mpq + fi + ;; + + read) + case "$prev" in + --locale) + mapfile -t COMPREPLY < <(compgen -W "${locales[*]}" -- "$cur") + return ;; + esac + if [[ "$cur" == -* ]]; then + mapfile -t COMPREPLY < <(compgen -W "--locale" -- "$cur") + elif [[ $cword -ge 3 ]]; then + # positional 1 is an in-archive path (no filesystem completion); + # positional 2+ is the archive file + _mpqcli_filedir mpq + fi + ;; + + verify) + if [[ "$cur" == -* ]]; then + mapfile -t COMPREPLY < <(compgen -W "-p --print" -- "$cur") + else + _mpqcli_filedir mpq + fi + ;; + + completion) + if [[ "$cur" != -* ]]; then + mapfile -t COMPREPLY < <(compgen -W "bash zsh powershell fish" -- "$cur") + fi + ;; + + version|about) ;; + esac +} + +complete -F _mpqcli mpqcli diff --git a/src/completion/mpqcli.fish b/src/completion/mpqcli.fish new file mode 100644 index 0000000..0341335 --- /dev/null +++ b/src/completion/mpqcli.fish @@ -0,0 +1,208 @@ +# Fish shell completions for mpqcli +# Source: generated from mpqcli/src/main.cpp +# +# Install: +# cp mpqcli.fish ~/.config/fish/completions/mpqcli.fish + +# Disable file completion globally; re-enable per argument where needed. +complete -c mpqcli -f + +# Shared value sets (sourced directly from main.cpp) +set -l __mpqcli_locales \ + default \ + enUS enGB zhTW zhCN csCZ deDE esES esMX \ + frFR itIT jaJP koKR nlNL plPL ptBR ptPT ruRU + +set -l __mpqcli_game_profiles \ + generic \ + diablo1 diablo d1 \ + lordsofmagic lomse \ + starcraft starcraft1 sc1 \ + warcraft2 wc2 war2 \ + diablo2 d2 \ + warcraft3 wc3 war3 \ + warcraft3-map wc3-map war3-map \ + wow1 wow-vanilla \ + wow2 wow-tbc \ + wow3 wow-wotlk \ + wow4 wow-cataclysm \ + wow5 wow-mop \ + starcraft2 sc2 \ + diablo3 d3 + +set -l __mpqcli_info_properties \ + format-version header-offset header-size archive-size \ + file-count max-files signature-type + +set -l __mpqcli_list_properties \ + hash-index name-hash1 name-hash2 name-hash3 locale \ + file-index byte-offset file-time file-size compressed-size \ + flags encryption-key encryption-key-raw + +# Top-level subcommands (no subcommand active yet) +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ + -a version -d 'Print program version' +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ + -a about -d 'Print program information' +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ + -a info -d 'Print info about an MPQ archive' +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ + -a create -d 'Create an MPQ archive from a file or directory' +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ + -a add -d 'Add files to an existing MPQ archive' +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ + -a remove -d 'Remove files from an existing MPQ archive' +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ + -a list -d 'List files in an MPQ archive' +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ + -a extract -d 'Extract files from an MPQ archive' +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ + -a read -d 'Read a file from an MPQ archive' +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ + -a verify -d 'Verify an MPQ archive' +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ + -a completion -d 'Generate shell completion script' + +# info +# info [-p/--property ] +# positional: existing file +complete -c mpqcli -n '__fish_seen_subcommand_from info' -F + +complete -c mpqcli -n '__fish_seen_subcommand_from info' \ + -s p -l property -d 'Print only a specific property value' \ + -r -a "$__mpqcli_info_properties" + +# create +# create [-p/--path] [-o/--output] [-s/--sign] +# [--locale] [-g/--game] +# [--version] [--stream-flags] [--sector-size] [--raw-chunk-size] +# [--file-flags1/2/3] [--attr-flags] +# [--flags] [--compression] [--compression-next] +complete -c mpqcli -n '__fish_seen_subcommand_from create' -F + +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -s p -l path -d 'Archive path for a single file, or prefix for a directory' -r +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -s o -l output -d 'Output MPQ archive path' -r -F +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -s s -l sign -d 'Sign the MPQ archive' +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l locale -d 'Locale to use for added files' \ + -r -a "$__mpqcli_locales" +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -s g -l game -d 'Game profile for MPQ creation' \ + -r -a "$__mpqcli_game_profiles" +# Game setting overrides +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l version -d 'Override MPQ archive version (1-4)' -r +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l stream-flags -d 'Override stream flags' -r +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l sector-size -d 'Override sector size' -r +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l raw-chunk-size -d 'Override raw chunk size (MPQ v4)' -r +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l file-flags1 -d 'Override file flags for (listfile)' -r +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l file-flags2 -d 'Override file flags for (attributes)' -r +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l file-flags3 -d 'Override file flags for (signature)' -r +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l attr-flags -d 'Override attribute flags (CRC32, FILETIME, MD5)' -r +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l flags -d 'Override MPQ file flags for added files' -r +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l compression -d 'Override compression for first sector of added files' -r +complete -c mpqcli -n '__fish_seen_subcommand_from create' \ + -l compression-next -d 'Override compression for subsequent sectors of added files' -r + +# add +# add [-p/--path] [-w/--overwrite] [-u/--update] +# [--locale] [-g/--game] +# [--flags] [--compression] [--compression-next] +complete -c mpqcli -n '__fish_seen_subcommand_from add' -F + +complete -c mpqcli -n '__fish_seen_subcommand_from add' \ + -s p -l path -d 'Archive path for a single file, or prefix for a directory' -r +complete -c mpqcli -n '__fish_seen_subcommand_from add' \ + -s w -l overwrite -d 'Overwrite file if it already exists in the archive' +complete -c mpqcli -n '__fish_seen_subcommand_from add' \ + -s u -l update -d 'Skip files whose archived size matches on-disk size' +complete -c mpqcli -n '__fish_seen_subcommand_from add' \ + -l locale -d 'Locale to use for added file' \ + -r -a "$__mpqcli_locales" +complete -c mpqcli -n '__fish_seen_subcommand_from add' \ + -s g -l game -d 'Game profile for compression rules' \ + -r -a "$__mpqcli_game_profiles" +complete -c mpqcli -n '__fish_seen_subcommand_from add' \ + -l flags -d 'Override MPQ file flags' -r +complete -c mpqcli -n '__fish_seen_subcommand_from add' \ + -l compression -d 'Override compression for first sector' -r +complete -c mpqcli -n '__fish_seen_subcommand_from add' \ + -l compression-next -d 'Override compression for subsequent sectors' -r + +# remove +# remove [--locale] +complete -c mpqcli -n '__fish_seen_subcommand_from remove' -F + +complete -c mpqcli -n '__fish_seen_subcommand_from remove' \ + -l locale -d 'Locale of the file to remove' \ + -r -a "$__mpqcli_locales" + +# list +# list [-l/--listfile] [-d/--detailed] [-a/--all] +# [-p/--property ] +complete -c mpqcli -n '__fish_seen_subcommand_from list' -F + +complete -c mpqcli -n '__fish_seen_subcommand_from list' \ + -s l -l listfile -d 'External file listing content of the archive' -r -F +complete -c mpqcli -n '__fish_seen_subcommand_from list' \ + -s d -l detailed -d 'Show additional columns' +complete -c mpqcli -n '__fish_seen_subcommand_from list' \ + -s a -l all -d 'Include hidden files' +complete -c mpqcli -n '__fish_seen_subcommand_from list' \ + -s p -l property -d 'Print only specific property values' \ + -r -a "$__mpqcli_list_properties" + +# extract +# extract [-o/--output] [-f/--file] [-k/--keep] +# [-l/--listfile] [--locale] +complete -c mpqcli -n '__fish_seen_subcommand_from extract' -F + +complete -c mpqcli -n '__fish_seen_subcommand_from extract' \ + -s o -l output -d 'Output directory' -r -F +complete -c mpqcli -n '__fish_seen_subcommand_from extract' \ + -s f -l file -d 'Target file to extract' -r +complete -c mpqcli -n '__fish_seen_subcommand_from extract' \ + -s k -l keep -d 'Keep folder structure' +complete -c mpqcli -n '__fish_seen_subcommand_from extract' \ + -s l -l listfile -d 'External file listing content of the archive' -r -F +complete -c mpqcli -n '__fish_seen_subcommand_from extract' \ + -l locale -d 'Preferred locale for extracted file' \ + -r -a "$__mpqcli_locales" + +# read +# read [--locale] +complete -c mpqcli -n '__fish_seen_subcommand_from read' -F + +complete -c mpqcli -n '__fish_seen_subcommand_from read' \ + -l locale -d 'Preferred locale for read file' \ + -r -a "$__mpqcli_locales" + +# verify +# verify [-p/--print] +complete -c mpqcli -n '__fish_seen_subcommand_from verify' -F + +complete -c mpqcli -n '__fish_seen_subcommand_from verify' \ + -s p -l print -d 'Print the digital signature (in hex)' + +# completion +# completion +complete -c mpqcli -n '__fish_seen_subcommand_from completion' \ + -a bash -d 'Generate bash completion script' +complete -c mpqcli -n '__fish_seen_subcommand_from completion' \ + -a zsh -d 'Generate zsh completion script' +complete -c mpqcli -n '__fish_seen_subcommand_from completion' \ + -a powershell -d 'Generate PowerShell completion script' +complete -c mpqcli -n '__fish_seen_subcommand_from completion' \ + -a fish -d 'Generate fish completion script' diff --git a/src/completion/mpqcli.ps1 b/src/completion/mpqcli.ps1 new file mode 100644 index 0000000..d030471 --- /dev/null +++ b/src/completion/mpqcli.ps1 @@ -0,0 +1,269 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + PowerShell argument completer for the mpqcli tool. + +.DESCRIPTION + Provides tab/menu completion for mpqcli subcommands, options, and known + value sets (game profiles, locales, info/list properties). File-path + arguments fall back to PowerShell's native filesystem completion. + +.NOTES + Source it from your $PROFILE: + . /path/to/mpqcli-completion.ps1 + + Or copy it into a module / profile script that loads at startup. + Works for any executable named 'mpqcli' or 'mpqcli.exe'. +#> + +Register-ArgumentCompleter -Native -CommandName 'mpqcli', 'mpqcli.exe' -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + + # Static value sets (derived from mpqcli source) + + $subcommands = [ordered]@{ + 'version' = 'Prints program version' + 'about' = 'Prints program information' + 'info' = 'Prints info about an MPQ archive' + 'create' = 'Create an MPQ archive from target file or directory' + 'add' = 'Add files to an existing MPQ archive' + 'remove' = 'Remove files from an existing MPQ archive' + 'list' = 'List files from the MPQ archive' + 'extract' = 'Extract files from the MPQ archive' + 'read' = 'Read a file from an MPQ archive' + 'verify' = 'Verify the MPQ archive' + 'completion' = 'Generate shell completion script' + } + + $gameProfiles = @( + 'generic', + 'diablo1', 'diablo', 'd1', + 'lordsofmagic', 'lomse', + 'starcraft', 'starcraft1', 'sc1', + 'warcraft2', 'wc2', 'war2', + 'diablo2', 'd2', + 'warcraft3', 'wc3', 'war3', + 'warcraft3-map', 'wc3-map', 'war3-map', + 'wow1', 'wow-vanilla', + 'wow2', 'wow-tbc', + 'wow3', 'wow-wotlk', + 'wow4', 'wow-cataclysm', + 'wow5', 'wow-mop', + 'starcraft2', 'sc2', + 'diablo3', 'd3' + ) + + $locales = @( + 'default', + 'enUS', 'enGB', 'zhTW', 'zhCN', 'csCZ', 'deDE', 'esES', 'esMX', + 'frFR', 'itIT', 'jaJP', 'koKR', 'nlNL', 'plPL', 'ptBR', 'ptPT', 'ruRU' + ) + + $infoProperties = @( + 'format-version', 'header-offset', 'header-size', 'archive-size', + 'file-count', 'max-files', 'signature-type' + ) + + $listProperties = @( + 'hash-index', 'name-hash1', 'name-hash2', 'name-hash3', 'locale', + 'file-index', 'byte-offset', 'file-time', 'file-size', 'compressed-size', + 'flags', 'encryption-key', 'encryption-key-raw' + ) + + # Options available per subcommand. Each value is a hashtable mapping the + # option token to a short help string. + $optionSpec = @{ + 'info' = @{ + '-p' = 'Print only a specific property value' + '--property' = 'Print only a specific property value' + } + 'create' = @{ + '-p' = 'Archive path for a single file, or prefix for a directory' + '--path' = 'Archive path for a single file, or prefix for a directory' + '-o' = 'Output MPQ archive' + '--output' = 'Output MPQ archive' + '-s' = 'Sign the MPQ archive' + '--sign' = 'Sign the MPQ archive' + '--locale' = 'Locale to use for added files' + '-g' = 'Game profile for MPQ creation' + '--game' = 'Game profile for MPQ creation' + '--version' = 'Override the MPQ archive version (1-4)' + '--stream-flags' = 'Override stream flags' + '--sector-size' = 'Override sector size' + '--raw-chunk-size'= 'Override raw chunk size for MPQ v4' + '--file-flags1' = 'Override file flags for (listfile)' + '--file-flags2' = 'Override file flags for (attributes)' + '--file-flags3' = 'Override file flags for (signature)' + '--attr-flags' = 'Override attribute flags (CRC32, FILETIME, MD5)' + '--flags' = 'Override MPQ file flags for added files' + '--compression' = 'Override compression for first sector' + '--compression-next' = 'Override compression for subsequent sectors' + } + 'add' = @{ + '-p' = 'Archive path for a single file, or prefix for a directory' + '--path' = 'Archive path for a single file, or prefix for a directory' + '-w' = 'Overwrite file if it already is in MPQ archive' + '--overwrite' = 'Overwrite file if it already is in MPQ archive' + '-u' = 'Skip files whose archived size matches on-disk size' + '--update' = 'Skip files whose archived size matches on-disk size' + '--locale' = 'Locale to use for added file' + '-g' = 'Game profile for compression rules' + '--game' = 'Game profile for compression rules' + '--flags' = 'Override MPQ file flags' + '--compression' = 'Override compression for first sector' + '--compression-next' = 'Override compression for subsequent sectors' + } + 'remove' = @{ + '--locale' = 'Locale of file to remove' + } + 'list' = @{ + '-l' = 'File listing content of an MPQ archive' + '--listfile' = 'File listing content of an MPQ archive' + '-d' = 'File listing with additional columns' + '--detailed' = 'File listing with additional columns' + '-a' = 'File listing including hidden files' + '--all' = 'File listing including hidden files' + '-p' = 'Print only specific property values' + '--property' = 'Print only specific property values' + } + 'extract' = @{ + '-o' = 'Output directory' + '--output' = 'Output directory' + '-f' = 'Target file to extract' + '--file' = 'Target file to extract' + '-k' = 'Keep folder structure' + '--keep' = 'Keep folder structure' + '-l' = 'File listing content of an MPQ archive' + '--listfile' = 'File listing content of an MPQ archive' + '--locale' = 'Preferred locale for extracted file' + } + 'read' = @{ + '--locale' = 'Preferred locale for read file' + } + 'verify' = @{ + '-p' = 'Print the digital signature (in hex)' + '--print' = 'Print the digital signature (in hex)' + } + 'version' = @{} + 'about' = @{} + 'completion' = @{} + } + + # Options whose *argument* should complete from a static value set. + $valueOptions = @{ + '--locale' = $locales + '-g' = $gameProfiles + '--game' = $gameProfiles + } + # Property options depend on the subcommand (info vs list), handled below. + + # Parse the current command line + + # Tokenized elements of the command line, excluding the executable itself. + $elements = @($commandAst.CommandElements | Select-Object -Skip 1 | + ForEach-Object { $_.ToString() }) + + # Identify the active subcommand (first element that is a known subcommand). + $subcommand = $null + foreach ($el in $elements) { + if ($subcommands.Contains($el)) { $subcommand = $el; break } + } + + # The token immediately preceding the cursor (the one we may be an arg to). + $prevToken = $null + if ($elements.Count -ge 1) { + if ([string]::IsNullOrEmpty($wordToComplete)) { + $prevToken = $elements[-1] + } + elseif ($elements.Count -ge 2) { + $prevToken = $elements[-2] + } + } + + $emit = { + param($items, $type) + foreach ($i in $items) { + if ($i.Value -like "$wordToComplete*") { + [System.Management.Automation.CompletionResult]::new( + $i.Value, $i.Value, + [System.Management.Automation.CompletionResultType]::$type, + $i.Tip) + } + } + } + + # 1) No subcommand yet -> complete subcommands + if (-not $subcommand) { + $items = foreach ($k in $subcommands.Keys) { + [pscustomobject]@{ Value = $k; Tip = $subcommands[$k] } + } + return & $emit $items 'Command' + } + + # 2) completion subcommand -> complete shell names + if ($subcommand -eq 'completion') { + if (-not ($wordToComplete -like '-*')) { + $shells = [ordered]@{ + 'bash' = 'Generate bash completion script' + 'zsh' = 'Generate zsh completion script' + 'powershell' = 'Generate PowerShell completion script' + 'fish' = 'Generate fish completion script' + } + $items = foreach ($k in $shells.Keys) { + [pscustomobject]@{ Value = $k; Tip = $shells[$k] } + } + return & $emit $items 'ParameterValue' + } + return + } + + # 4) Completing the argument to a value-bearing option + if ($prevToken) { + # Locale / game profile options. + if ($valueOptions.ContainsKey($prevToken)) { + $items = foreach ($v in $valueOptions[$prevToken]) { + [pscustomobject]@{ Value = $v; Tip = $prevToken } + } + return & $emit $items 'ParameterValue' + } + # Property options: meaning depends on the subcommand. + if ($prevToken -in @('-p', '--property')) { + if ($subcommand -eq 'info') { + $items = foreach ($v in $infoProperties) { + [pscustomobject]@{ Value = $v; Tip = 'info property' } } + return & $emit $items 'ParameterValue' + } + elseif ($subcommand -eq 'list') { + $items = foreach ($v in $listProperties) { + [pscustomobject]@{ Value = $v; Tip = 'list property' } } + return & $emit $items 'ParameterValue' + } + # For 'verify', -p/--print is a flag (no value) -> fall through. + } + # Options that take a file/dir path -> let PowerShell complete paths. + $pathOptions = @('-o', '--output', '-l', '--listfile', '-f', '--file', + '-p', '--path') + if ($prevToken -in $pathOptions -and + -not ($subcommand -in @('info', 'list') -and $prevToken -in @('-p', '--property'))) { + return # empty -> native path completion takes over + } + } + + # 5) Completing an option for the current subcommand + if ($wordToComplete -like '-*' -or [string]::IsNullOrEmpty($wordToComplete)) { + $opts = $optionSpec[$subcommand] + if ($opts) { + $items = foreach ($k in $opts.Keys) { + [pscustomobject]@{ Value = $k; Tip = $opts[$k] } + } + $results = & $emit $items 'ParameterName' + if ($wordToComplete -like '-*') { return $results } + # When word is empty we still also allow path completion, so only + # return option results if the user has started a dash. + if ($results) { return $results } + } + } + + # 6) Fall back to native filesystem completion (archives/files) + return +} diff --git a/src/completion/mpqcli.zsh b/src/completion/mpqcli.zsh new file mode 100644 index 0000000..4877c51 --- /dev/null +++ b/src/completion/mpqcli.zsh @@ -0,0 +1,159 @@ +#compdef mpqcli + +_mpqcli_locales=( + default enUS zhTW csCZ deDE esES frFR itIT + jaJP koKR nlNL plPL ptBR ruRU zhCN enGB esMX ptPT +) + +_mpqcli_games=( + generic diablo1 lordsofmagic starcraft1 warcraft2 diablo2 + warcraft3 warcraft3-map wow-vanilla wow-tbc wow-wotlk + wow-cataclysm wow-mop starcraft2 diablo3 +) + +_mpqcli_info_properties=( + format-version header-offset header-size archive-size + file-count max-files signature-type +) + +_mpqcli_list_properties=( + hash-index name-hash1 name-hash2 name-hash3 locale + file-index byte-offset file-time file-size compressed-size + flags encryption-key encryption-key-raw +) + +_mpqcli() { + local state + typeset -A opt_args + + _arguments -C \ + '1: :_mpqcli_cmds' \ + '*:: :->subcmd' + + case $state in + subcmd) + case $words[1] in + info) _mpqcli_info ;; + create) _mpqcli_create ;; + add) _mpqcli_add ;; + remove) _mpqcli_remove ;; + list) _mpqcli_list ;; + extract) _mpqcli_extract ;; + read) _mpqcli_read ;; + verify) _mpqcli_verify ;; + completion) _mpqcli_completion ;; + esac + ;; + esac +} + +_mpqcli_cmds() { + local cmds=( + 'version:Print program version' + 'about:Print program information' + 'info:Print info about an MPQ archive' + 'create:Create an MPQ archive from target file or directory' + 'add:Add files to an existing MPQ archive' + 'remove:Remove files from an existing MPQ archive' + 'list:List files from the MPQ archive' + 'extract:Extract files from the MPQ archive' + 'read:Read a file from an MPQ archive' + 'verify:Verify the MPQ archive' + 'completion:Generate shell completion script' + ) + _describe 'subcommand' cmds +} + +_mpqcli_info() { + local props="${_mpqcli_info_properties[*]}" + _arguments \ + '1:archive:_files' \ + '(-p --property)'{-p,--property}'[print only a specific property]:property:('"$props"')' +} + +_mpqcli_create() { + _arguments \ + '1:target:_files' \ + '(-p --path)'{-p,--path}'[archive path for a single file, or prefix for a directory]:path' \ + '(-o --output)'{-o,--output}'[output archive]:file:_files' \ + '(-s --sign)'{-s,--sign}'[sign the archive]' \ + '--locale[locale for added files]:locale:('"${_mpqcli_locales[@]}"')' \ + '(-g --game)'{-g,--game}'[game profile]:profile:('"${_mpqcli_games[@]}"')' \ + '--version[MPQ archive version (1-4)]:version:(1 2 3 4)' \ + '--stream-flags[override stream flags]:flags' \ + '--sector-size[override sector size]:size' \ + '--raw-chunk-size[override raw chunk size for MPQ v4]:size' \ + '--file-flags1[override file flags for listfile]:flags' \ + '--file-flags2[override file flags for attributes]:flags' \ + '--file-flags3[override file flags for signature]:flags' \ + '--attr-flags[override attribute flags]:flags' \ + '--flags[override MPQ file flags for added files]:flags' \ + '--compression[override compression for first sector]:compression' \ + '--compression-next[override compression for subsequent sectors]:compression' +} + +_mpqcli_add() { + _arguments \ + '1:archive:_files' \ + '*:files:_files' \ + '(-p --path)'{-p,--path}'[archive path or prefix for directory add]:path' \ + '(-w --overwrite)'{-w,--overwrite}'[overwrite existing file]' \ + '(-u --update)'{-u,--update}'[skip unchanged files in directory add]' \ + '--locale[locale for added file]:locale:('"${_mpqcli_locales[@]}"')' \ + '(-g --game)'{-g,--game}'[game profile]:profile:('"${_mpqcli_games[@]}"')' \ + '--flags[override MPQ file flags]:flags' \ + '--compression[override compression for first sector]:compression' \ + '--compression-next[override compression for subsequent sectors]:compression' +} + +_mpqcli_remove() { + _arguments \ + '1:archive:_files' \ + '*:archive paths' \ + '--locale[locale of file to remove]:locale:('"${_mpqcli_locales[@]}"')' +} + +_mpqcli_list() { + local props="${_mpqcli_list_properties[*]}" + _arguments \ + '1:archive:_files' \ + '(-l --listfile)'{-l,--listfile}'[listfile path]:file:_files' \ + '(-d --detailed)'{-d,--detailed}'[detailed listing with extra columns]' \ + '(-a --all)'{-a,--all}'[include hidden files]' \ + '(-p --property)'{-p,--property}'[print specific property values]:property:('"$props"')' +} + +_mpqcli_extract() { + _arguments \ + '1:archive:_files' \ + '(-o --output)'{-o,--output}'[output directory]:dir:_files -/' \ + '(-f --file)'{-f,--file}'[target file to extract]:file' \ + '(-k --keep)'{-k,--keep}'[keep folder structure]' \ + '(-l --listfile)'{-l,--listfile}'[listfile path]:file:_files' \ + '--locale[preferred locale for extracted file]:locale:('"${_mpqcli_locales[@]}"')' +} + +_mpqcli_read() { + _arguments \ + '1:file-in-archive' \ + '2:archive:_files' \ + '--locale[preferred locale for read file]:locale:('"${_mpqcli_locales[@]}"')' +} + +_mpqcli_verify() { + _arguments \ + '1:archive:_files' \ + '(-p --print)'{-p,--print}'[print the digital signature in hex]' +} + +_mpqcli_completion() { + local shells=( + 'bash:Generate bash completion script' + 'zsh:Generate zsh completion script' + 'powershell:Generate PowerShell completion script' + 'fish:Generate fish completion script' + ) + _describe 'shell' shells +} + +_mpqcli "$@" diff --git a/src/completion_data.h.in b/src/completion_data.h.in new file mode 100644 index 0000000..081fc13 --- /dev/null +++ b/src/completion_data.h.in @@ -0,0 +1,16 @@ +#ifndef COMPLETION_DATA_H +#define COMPLETION_DATA_H + +// Auto-generated from src/completion/mpqcli.bash, do not edit directly +static constexpr char BashCompletionScript[] = R"BASH_MPQCLI(@BASH_COMPLETION_SCRIPT@)BASH_MPQCLI"; + +// Auto-generated from src/completion/mpqcli.zsh, do not edit directly +static constexpr char ZshCompletionScript[] = R"ZSH_MPQCLI(@ZSH_COMPLETION_SCRIPT@)ZSH_MPQCLI"; + +// Auto-generated from src/completion/mpqcli.ps1, do not edit directly +static constexpr char PsCompletionScript[] = R"PS_MPQCLI(@PS_COMPLETION_SCRIPT@)PS_MPQCLI"; + +// Auto-generated from src/completion/mpqcli.fish, do not edit directly +static constexpr char FishCompletionScript[] = R"FISH_MPQCLI(@FISH_COMPLETION_SCRIPT@)FISH_MPQCLI"; + +#endif // COMPLETION_DATA_H diff --git a/src/main.cpp b/src/main.cpp index e0d1b99..57cf2e2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include #include "commands.h" +#include "completion.h" #include "gamerules.h" #include "validators.h" @@ -213,6 +214,16 @@ int main(int argc, char **argv) { ->check(CLI::ExistingFile); verify->add_flag("-p,--print", verifyPrintSignature, "Print the digital signature (in hex)"); + // Subcommand: Completion + CLI::App *completion = app.add_subcommand("completion", "Generate shell completion script"); + CLI::App *completionBash = + completion->add_subcommand("bash", "Generate bash completion script"); + CLI::App *completionZsh = completion->add_subcommand("zsh", "Generate zsh completion script"); + CLI::App *completionPs = + completion->add_subcommand("powershell", "Generate PowerShell completion script"); + CLI::App *completionFish = + completion->add_subcommand("fish", "Generate fish completion script"); + try { app.parse(argc, argv); } catch (const CLI::ParseError &e) { @@ -295,5 +306,26 @@ int main(int argc, char **argv) { return HandleVerify(baseTarget, verifyPrintSignature); } + if (app.got_subcommand(completion)) { + if (completion->got_subcommand(completionBash)) { + HandleCompletionBash(); + return 0; + } + if (completion->got_subcommand(completionZsh)) { + HandleCompletionZsh(); + return 0; + } + if (completion->got_subcommand(completionPs)) { + HandleCompletionPs(); + return 0; + } + if (completion->got_subcommand(completionFish)) { + HandleCompletionFish(); + return 0; + } + std::cerr << completion->help(); + return 1; + } + return 0; } diff --git a/src/mpq.cpp b/src/mpq.cpp index db50424..630e928 100644 --- a/src/mpq.cpp +++ b/src/mpq.cpp @@ -404,7 +404,7 @@ int ListFiles(HANDLE hArchive, const std::optional &listfileName, b true; // If the user specified properties, we need to print the detailed output } - // Map of property name to SFileInfoClass — defined once, outside the loop + // Map of property name to SFileInfoClass, defined once, outside the loop static const std::map kPropertyInfoClass = { {"hash-index", SFileInfoHashIndex}, {"name-hash1", SFileInfoNameHash1}, From 80da4895d572c8a921e3a3730cfb689e46feaafc Mon Sep 17 00:00:00 2001 From: thomaslaurenson Date: Sat, 6 Jun 2026 18:30:22 +1200 Subject: [PATCH 2/8] Added docs to match completion subcommand --- docs/SUMMARY.md | 1 + docs/commands/completion.md | 49 +++++++++++++++++++++++++++++++++++++ docs/commands_list.md | 1 + 3 files changed, 51 insertions(+) create mode 100644 docs/commands/completion.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 9e02607..3ff861d 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -5,6 +5,7 @@ - [Commands](./commands_list.md) - [version](./commands/version.md) - [about](./commands/about.md) + - [completion](./commands/completion.md) - [info](./commands/info.md) - [create](./commands/create.md) - [add](./commands/add.md) diff --git a/docs/commands/completion.md b/docs/commands/completion.md new file mode 100644 index 0000000..d5b682f --- /dev/null +++ b/docs/commands/completion.md @@ -0,0 +1,49 @@ +# completion + +Print a shell completion script to stdout. + +## Supported Shells + +- bash +- zsh +- fish +- PowerShell + +## Bash + +Write the completion script to a file and source it from your shell profile. + +```bash +$ mpqcli completion bash > ~/.bash_completion.d/mpqcli +$ 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 +``` + +## Zsh + +Write the completion script to a directory that is on your `$fpath`. + +```zsh +$ mpqcli completion zsh > "${fpath[1]}/_mpqcli" +``` + +## PowerShell + +Append the completion script to your PowerShell profile so it loads automatically. + +```powershell +$ mpqcli completion powershell >> $PROFILE +``` + +## Fish + +Write the completion script to the fish completions directory. + +```fish +$ mpqcli completion fish > ~/.config/fish/completions/mpqcli.fish +``` diff --git a/docs/commands_list.md b/docs/commands_list.md index 859881d..ee0b40a 100644 --- a/docs/commands_list.md +++ b/docs/commands_list.md @@ -9,6 +9,7 @@ The `mpqcli` program has the following subcommands: |---|---| | [`version`](./commands/version.md) | Print the tool version number | | [`about`](./commands/about.md) | Print information about the tool | +| [`completion`](./commands/completion.md) | Print shell completion script to stdout | | [`info`](./commands/info.md) | Print information about MPQ archive properties | | [`create`](./commands/create.md) | Create an MPQ archive from a target directory or a single file | | [`add`](./commands/add.md) | Add a file to an existing MPQ archive | From 507544c8f8620bc597967c53ab4b9224cccbdea8 Mon Sep 17 00:00:00 2001 From: thomaslaurenson Date: Sun, 7 Jun 2026 16:47:46 +1200 Subject: [PATCH 3/8] Updated completion scripts for new compact subcommand --- src/completion/mpqcli.bash | 15 ++++++++++++++- src/completion/mpqcli.fish | 31 ++++++++++++++++++++----------- src/completion/mpqcli.ps1 | 5 +++++ src/completion/mpqcli.zsh | 8 ++++++++ 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/completion/mpqcli.bash b/src/completion/mpqcli.bash index d45f213..f57fba7 100644 --- a/src/completion/mpqcli.bash +++ b/src/completion/mpqcli.bash @@ -29,7 +29,7 @@ _mpqcli() { cword=$COMP_CWORD fi - local subcommands="version about info create add remove list extract read verify completion" + local subcommands="version about info create add remove list extract read verify compact completion" local -a locales=( default enUS zhTW csCZ deDE esES frFR itIT jaJP koKR nlNL plPL ptBR ruRU zhCN enGB esMX ptPT @@ -189,6 +189,19 @@ _mpqcli() { fi ;; + compact) + case "$prev" in + -l|--listfile) + _mpqcli_filedir + return ;; + esac + if [[ "$cur" == -* ]]; then + mapfile -t COMPREPLY < <(compgen -W "-l --listfile" -- "$cur") + else + _mpqcli_filedir mpq + fi + ;; + completion) if [[ "$cur" != -* ]]; then mapfile -t COMPREPLY < <(compgen -W "bash zsh powershell fish" -- "$cur") diff --git a/src/completion/mpqcli.fish b/src/completion/mpqcli.fish index 0341335..cec6666 100644 --- a/src/completion/mpqcli.fish +++ b/src/completion/mpqcli.fish @@ -40,27 +40,29 @@ set -l __mpqcli_list_properties \ flags encryption-key encryption-key-raw # Top-level subcommands (no subcommand active yet) -complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ -a version -d 'Print program version' -complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ -a about -d 'Print program information' -complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ -a info -d 'Print info about an MPQ archive' -complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ -a create -d 'Create an MPQ archive from a file or directory' -complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ -a add -d 'Add files to an existing MPQ archive' -complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ -a remove -d 'Remove files from an existing MPQ archive' -complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ -a list -d 'List files in an MPQ archive' -complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ -a extract -d 'Extract files from an MPQ archive' -complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ -a read -d 'Read a file from an MPQ archive' -complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ -a verify -d 'Verify an MPQ archive' -complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify completion' \ +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ + -a compact -d 'Compact the MPQ archive' +complete -c mpqcli -n 'not __fish_seen_subcommand_from version about info create add remove list extract read verify compact completion' \ -a completion -d 'Generate shell completion script' # info @@ -196,6 +198,13 @@ complete -c mpqcli -n '__fish_seen_subcommand_from verify' -F complete -c mpqcli -n '__fish_seen_subcommand_from verify' \ -s p -l print -d 'Print the digital signature (in hex)' +# compact +# compact [-l/--listfile] +complete -c mpqcli -n '__fish_seen_subcommand_from compact' -F + +complete -c mpqcli -n '__fish_seen_subcommand_from compact' \ + -s l -l listfile -d 'External file listing content of the archive' -r -F + # completion # completion complete -c mpqcli -n '__fish_seen_subcommand_from completion' \ diff --git a/src/completion/mpqcli.ps1 b/src/completion/mpqcli.ps1 index d030471..cd651ac 100644 --- a/src/completion/mpqcli.ps1 +++ b/src/completion/mpqcli.ps1 @@ -32,6 +32,7 @@ Register-ArgumentCompleter -Native -CommandName 'mpqcli', 'mpqcli.exe' -ScriptBl 'extract' = 'Extract files from the MPQ archive' 'read' = 'Read a file from an MPQ archive' 'verify' = 'Verify the MPQ archive' + 'compact' = 'Compact the MPQ archive' 'completion' = 'Generate shell completion script' } @@ -144,6 +145,10 @@ Register-ArgumentCompleter -Native -CommandName 'mpqcli', 'mpqcli.exe' -ScriptBl '-p' = 'Print the digital signature (in hex)' '--print' = 'Print the digital signature (in hex)' } + 'compact' = @{ + '-l' = 'File listing content of an MPQ archive' + '--listfile' = 'File listing content of an MPQ archive' + } 'version' = @{} 'about' = @{} 'completion' = @{} diff --git a/src/completion/mpqcli.zsh b/src/completion/mpqcli.zsh index 4877c51..f36d409 100644 --- a/src/completion/mpqcli.zsh +++ b/src/completion/mpqcli.zsh @@ -41,6 +41,7 @@ _mpqcli() { extract) _mpqcli_extract ;; read) _mpqcli_read ;; verify) _mpqcli_verify ;; + compact) _mpqcli_compact ;; completion) _mpqcli_completion ;; esac ;; @@ -59,6 +60,7 @@ _mpqcli_cmds() { 'extract:Extract files from the MPQ archive' 'read:Read a file from an MPQ archive' 'verify:Verify the MPQ archive' + 'compact:Compact the MPQ archive' 'completion:Generate shell completion script' ) _describe 'subcommand' cmds @@ -146,6 +148,12 @@ _mpqcli_verify() { '(-p --print)'{-p,--print}'[print the digital signature in hex]' } +_mpqcli_compact() { + _arguments \ + '1:archive:_files' \ + '(-l --listfile)'{-l,--listfile}'[listfile path]:file:_files' +} + _mpqcli_completion() { local shells=( 'bash:Generate bash completion script' From 30601b2cdf0c1a1c918709bca2e7416ab88bfa18 Mon Sep 17 00:00:00 2001 From: thomaslaurenson Date: Sun, 7 Jun 2026 16:48:19 +1200 Subject: [PATCH 4/8] Added tests for completion subcommand --- test/test_add.py | 8 --- test/test_completion.py | 128 ++++++++++++++++++++++++++++++++++++++++ test/test_extract.py | 10 ++-- 3 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 test/test_completion.py diff --git a/test/test_add.py b/test/test_add.py index 299fcfb..efdaa91 100644 --- a/test/test_add.py +++ b/test/test_add.py @@ -575,8 +575,6 @@ def test_add_file_after_all_locale_variants_removed(binary_path, generate_locale ) -# ---- Directory add tests ---- - def test_add_directory_to_mpq_archive(binary_path, generate_test_files): _ = generate_test_files script_dir = Path(__file__).parent @@ -710,8 +708,6 @@ def test_add_directory_without_overwrite_skips_existing(binary_path, generate_te shutil.rmtree(add_dir, ignore_errors=True) -# ---- --update flag tests ---- - def test_add_update_skips_unchanged_files(binary_path, generate_test_files): _ = generate_test_files script_dir = Path(__file__).parent @@ -826,8 +822,6 @@ def test_add_update_single_file_emits_warning(binary_path, generate_test_files): assert "--update is only meaningful when adding a directory" in result.stderr -# ---- stdin tests ---- - def test_add_files_via_stdin(binary_path, generate_test_files): _ = generate_test_files script_dir = Path(__file__).parent @@ -862,8 +856,6 @@ def test_add_files_via_stdin(binary_path, generate_test_files): assert "stdin_b.txt" in list_result.stdout -# ---- Helpers ---- - def create_mpq_archive_for_test(binary_path, script_dir): target_dir = script_dir / "data" / "files" target_file = target_dir.with_suffix(".mpq") diff --git a/test/test_completion.py b/test/test_completion.py new file mode 100644 index 0000000..a8712a3 --- /dev/null +++ b/test/test_completion.py @@ -0,0 +1,128 @@ +import subprocess +from pathlib import Path + + +def test_completion_bash(binary_path): + result = subprocess.run( + [str(binary_path), "completion", "bash"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" + assert "_mpqcli()" in result.stdout, "Bash completion function not found in output" + assert "complete -F _mpqcli mpqcli" in result.stdout, "Bash complete registration not found in output" + + +def test_completion_zsh(binary_path): + result = subprocess.run( + [str(binary_path), "completion", "zsh"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" + assert "#compdef mpqcli" in result.stdout, "Zsh compdef directive not found in output" + assert "_mpqcli()" in result.stdout, "Zsh completion function not found in output" + + +def test_completion_bash_output_size(binary_path): + result = subprocess.run( + [str(binary_path), "completion", "bash"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" + script_path = Path(__file__).parent.parent / "src" / "completion" / "mpqcli.bash" + assert len(result.stdout) == script_path.stat().st_size + + +def test_completion_zsh_output_size(binary_path): + result = subprocess.run( + [str(binary_path), "completion", "zsh"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" + script_path = Path(__file__).parent.parent / "src" / "completion" / "mpqcli.zsh" + assert len(result.stdout) == script_path.stat().st_size + + +def test_completion_powershell(binary_path): + result = subprocess.run( + [str(binary_path), "completion", "powershell"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" + assert "Register-ArgumentCompleter" in result.stdout, "PS completer registration not found in output" + assert "mpqcli" in result.stdout, "mpqcli reference not found in output" + + +def test_completion_powershell_output_size(binary_path): + result = subprocess.run( + [str(binary_path), "completion", "powershell"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" + script_path = Path(__file__).parent.parent / "src" / "completion" / "mpqcli.ps1" + assert len(result.stdout) == script_path.stat().st_size + + +def test_completion_fish(binary_path): + result = subprocess.run( + [str(binary_path), "completion", "fish"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" + assert "complete -c mpqcli" in result.stdout, "Fish complete directive not found in output" + + +def test_completion_fish_output_size(binary_path): + result = subprocess.run( + [str(binary_path), "completion", "fish"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" + script_path = Path(__file__).parent.parent / "src" / "completion" / "mpqcli.fish" + assert len(result.stdout) == script_path.stat().st_size + + +def test_completion_no_shell(binary_path): + result = subprocess.run( + [str(binary_path), "completion"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + assert result.returncode != 0, "Expected non-zero exit code when no shell is specified" + + +def test_completion_invalid_shell(binary_path): + result = subprocess.run( + [str(binary_path), "completion", "nushell"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + assert result.returncode != 0, "Expected non-zero exit code for unsupported shell" + assert result.stderr, "Expected error output for unsupported shell" diff --git a/test/test_extract.py b/test/test_extract.py index 9b24471..45aa4e9 100644 --- a/test/test_extract.py +++ b/test/test_extract.py @@ -80,9 +80,9 @@ def test_extract_mpq_default_options(binary_path, generate_test_files): expected_size = 28 if platform.system() == "Windows" else 27 assert cats_file.read_text(encoding="utf-8") == expected_cats_content, \ - f"Unexpected content in cats.txt" + "Unexpected content in cats.txt" assert dogs_file.read_text(encoding="utf-8") == expected_dogs_content, \ - f"Unexpected content in dogs.txt" + "Unexpected content in dogs.txt" assert cats_file.stat().st_size == expected_size, \ f"Unexpected size for cats.txt: {cats_file.stat().st_size}" assert dogs_file.stat().st_size == expected_size, \ @@ -142,9 +142,9 @@ def test_extract_mpq_output_directory_specified(binary_path, generate_test_files expected_size = 28 if platform.system() == "Windows" else 27 assert cats_file.read_text(encoding="utf-8") == expected_cats_content, \ - f"Unexpected content in cats.txt" + "Unexpected content in cats.txt" assert dogs_file.read_text(encoding="utf-8") == expected_dogs_content, \ - f"Unexpected content in dogs.txt" + "Unexpected content in dogs.txt" assert cats_file.stat().st_size == expected_size, \ f"Unexpected size for cats.txt: {cats_file.stat().st_size}" assert dogs_file.stat().st_size == expected_size, \ @@ -199,7 +199,7 @@ def test_extract_file_from_mpq_output_directory_specified(binary_path, generate_ expected_size = 28 if platform.system() == "Windows" else 27 assert cats_file.read_text(encoding="utf-8") == expected_cats_content, \ - f"Unexpected content in cats.txt" + "Unexpected content in cats.txt" assert cats_file.stat().st_size == expected_size, \ f"Unexpected size for cats.txt: {cats_file.stat().st_size}" From cc2191b4a7c19034b4a5e3eaa488443a0a1ea06f Mon Sep 17 00:00:00 2001 From: thomaslaurenson Date: Sun, 7 Jun 2026 16:54:26 +1200 Subject: [PATCH 5/8] Fixed game profile aliases in bash and zsh completion scripts --- src/completion/mpqcli.bash | 18 +++++++++++++++--- src/completion/mpqcli.zsh | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/completion/mpqcli.bash b/src/completion/mpqcli.bash index f57fba7..6624dd4 100644 --- a/src/completion/mpqcli.bash +++ b/src/completion/mpqcli.bash @@ -35,9 +35,21 @@ _mpqcli() { jaJP koKR nlNL plPL ptBR ruRU zhCN enGB esMX ptPT ) local -a games=( - generic diablo1 lordsofmagic starcraft1 warcraft2 diablo2 - warcraft3 warcraft3-map wow-vanilla wow-tbc wow-wotlk - wow-cataclysm wow-mop starcraft2 diablo3 + generic + diablo1 diablo d1 + lordsofmagic lomse + starcraft starcraft1 sc1 + warcraft2 wc2 war2 + diablo2 d2 + warcraft3 wc3 war3 + warcraft3-map wc3-map war3-map + wow1 wow-vanilla + wow2 wow-tbc + wow3 wow-wotlk + wow4 wow-cataclysm + wow5 wow-mop + starcraft2 sc2 + diablo3 d3 ) local -a info_properties=( format-version header-offset header-size archive-size diff --git a/src/completion/mpqcli.zsh b/src/completion/mpqcli.zsh index f36d409..183aa7c 100644 --- a/src/completion/mpqcli.zsh +++ b/src/completion/mpqcli.zsh @@ -6,9 +6,21 @@ _mpqcli_locales=( ) _mpqcli_games=( - generic diablo1 lordsofmagic starcraft1 warcraft2 diablo2 - warcraft3 warcraft3-map wow-vanilla wow-tbc wow-wotlk - wow-cataclysm wow-mop starcraft2 diablo3 + generic + diablo1 diablo d1 + lordsofmagic lomse + starcraft starcraft1 sc1 + warcraft2 wc2 war2 + diablo2 d2 + warcraft3 wc3 war3 + warcraft3-map wc3-map war3-map + wow1 wow-vanilla + wow2 wow-tbc + wow3 wow-wotlk + wow4 wow-cataclysm + wow5 wow-mop + starcraft2 sc2 + diablo3 d3 ) _mpqcli_info_properties=( From 9d5c31e73545c18e552c72dedc1e7557d1a936c2 Mon Sep 17 00:00:00 2001 From: thomaslaurenson Date: Sun, 7 Jun 2026 16:54:48 +1200 Subject: [PATCH 6/8] Added compact subcommand to docs navigation and introduction --- docs/SUMMARY.md | 1 + docs/changelog.md | 31 +++++++++++++++++++++++++++++++ docs/commands.md | 3 --- docs/commands/about.md | 2 +- docs/commands/completion.md | 2 +- docs/commands/extract.md | 10 ++++++++++ docs/commands/version.md | 2 +- docs/commands_list.md | 1 + docs/introduction.md | 2 +- 9 files changed, 47 insertions(+), 7 deletions(-) delete mode 100644 docs/commands.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 3ff861d..dd8d3d9 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -14,6 +14,7 @@ - [extract](./commands/extract.md) - [read](./commands/read.md) - [verify](./commands/verify.md) + - [compact](./commands/compact.md) - [Advanced Examples](./advanced.md) - [Building](./building.md) - [Contributing](./contributing.md) diff --git a/docs/changelog.md b/docs/changelog.md index 474e4d2..627c9e1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,36 @@ # Changelog +## 0.10.0 - 2026-06-07 + +### Added + +- Compact subcommand to compress MPQ archive after creation/editing +- Support passing multiple files to the `add` and `remove` subcommands +- Support reading file paths from stdin in the `add` and `remove` subcommands +- Support adding entire directories with the `add` subcommand +- Add an update flag to the `add` subcommand to skip files whose archived size matches the on-disk size + +### Fixed + +- `extract` command now reports an error when the output directory cannot be created +- Path traversal guard in `extract` uses fully resolved paths, closing a potential bypass +- Crash when reading strong signatures from malformed or truncated archives +- Docker glibc image updated to ubuntu:24.04 +- Adding files is now ordered and operating system agnostic +- `add` subcommand now returns non-zero exit codes on failure +- Error messages now include StormLib error codes +- Duplicate error messages removed +- Makefile build targets corrected in `CONTRIBUTING.md` + +### Changed + +- `--filename-in-archive` and `--directory-in-archive` replaced by a unified `--path` flag +- The path flag on add now acts as an archive path prefix when a directory is given + +### Thanks + +- Thanks to @sjoblomj for the contributions in this release + ## 0.9.9 - 2026-04-05 ### Fixed diff --git a/docs/commands.md b/docs/commands.md deleted file mode 100644 index 9bf37da..0000000 --- a/docs/commands.md +++ /dev/null @@ -1,3 +0,0 @@ -# Commands - -- `add` \ No newline at end of file diff --git a/docs/commands/about.md b/docs/commands/about.md index 7e36b2b..bae7a6e 100644 --- a/docs/commands/about.md +++ b/docs/commands/about.md @@ -5,7 +5,7 @@ Print information about the tool. ```bash $ mpqcli about Name: mpqcli -Version: 0.9.8-041480a92e698514d7938426587e93582b336b7d +Version: 0.10.0-30601b2cdf0c1a1c918709bca2e7416ab88bfa18 Author: Thomas Laurenson License: MIT GitHub: https://github.com/thegraydot/mpqcli diff --git a/docs/commands/completion.md b/docs/commands/completion.md index d5b682f..afe5a63 100644 --- a/docs/commands/completion.md +++ b/docs/commands/completion.md @@ -37,7 +37,7 @@ $ mpqcli completion zsh > "${fpath[1]}/_mpqcli" Append the completion script to your PowerShell profile so it loads automatically. ```powershell -$ mpqcli completion powershell >> $PROFILE +PS> mpqcli completion powershell >> $PROFILE ``` ## Fish diff --git a/docs/commands/extract.md b/docs/commands/extract.md index 9efc885..d8c0ee7 100644 --- a/docs/commands/extract.md +++ b/docs/commands/extract.md @@ -25,6 +25,16 @@ Extract files to a specific target directory, which will be created if it doesn' $ mpqcli extract -o patch-1.10 wow-patch.mpq ``` +## Extract all files keeping the folder structure + +Use the `-k` or `--keep` flag to preserve the directory structure from the archive when extracting. Without this flag, all files are placed flat in the output directory, with only the filename retained. + +```bash +$ mpqcli extract -k wow-patch.mpq +[*] Extracted: Interface/FrameXML/BlizzardFrameXML.lua +[*] Extracted: Interface/FrameXML/BlizzardFrameXML.xml +``` + ## Extract all files with an external listfile Older MPQ archives do not contain (complete) file paths of their content. By providing an external listfile that lists the content of the MPQ archive, the extracted files will have the correct names and paths. Listfiles can be downloaded on [Ladislav Zezula's site](http://www.zezula.net/en/mpq/download.html). diff --git a/docs/commands/version.md b/docs/commands/version.md index b16466f..20b849c 100644 --- a/docs/commands/version.md +++ b/docs/commands/version.md @@ -4,5 +4,5 @@ Print the tool version number. ```bash $ mpqcli version -0.9.8-041480a92e698514d7938426587e93582b336b7d +0.10.0-30601b2cdf0c1a1c918709bca2e7416ab88bfa18 ``` diff --git a/docs/commands_list.md b/docs/commands_list.md index ee0b40a..93c1de5 100644 --- a/docs/commands_list.md +++ b/docs/commands_list.md @@ -18,3 +18,4 @@ The `mpqcli` program has the following subcommands: | [`extract`](./commands/extract.md) | Extract one or all files from a target MPQ archive | | [`read`](./commands/read.md) | Read a specific file to stdout | | [`verify`](./commands/verify.md) | Verify a target MPQ archive signature | +| [`compact`](./commands/compact.md) | Compact (defragment) an MPQ archive | diff --git a/docs/introduction.md b/docs/introduction.md index 9bc1145..c72c356 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -1,6 +1,6 @@ # Introduction -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). +A command-line tool to create, add, remove, list, extract, read, verify, and compact MPQ archives using the [StormLib library](https://github.com/ladislav-zezula/StormLib). > ⚠️ **Warning:** This project is under active development and will change functionality between released versions until version 1.0.0. From a195423f81f0e95d0f8134e2899c52ec8766c6da Mon Sep 17 00:00:00 2001 From: thomaslaurenson Date: Sun, 7 Jun 2026 16:57:13 +1200 Subject: [PATCH 7/8] Tidied Makefile --- Makefile | 32 ++------------------------------ README.md | 4 ++-- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index 25ccde7..0594eb0 100644 --- a/Makefile +++ b/Makefile @@ -28,14 +28,6 @@ configure: ## Configure cmake build (debug, with compile_commands.json) -DCMAKE_CXX_COMPILER=clang++-$(CLANG_VERSION) \ -DCMAKE_CXX_FLAGS="--gcc-install-dir=$(GCC_INSTALL_DIR)" -build/compile_commands.json: CMakeLists.txt src/CMakeLists.txt - cmake -B build \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DBUILD_MPQCLI=$(BUILD_MPQCLI) \ - -DCMAKE_CXX_COMPILER=clang++-$(CLANG_VERSION) \ - -DCMAKE_CXX_FLAGS="--gcc-install-dir=$(GCC_INSTALL_DIR)" - .PHONY: build build: ## Build via cmake cmake --build build @@ -111,7 +103,7 @@ fmt: ## Auto-fix C++ formatting with clang-format | xargs clang-format-$(CLANG_VERSION) -i .PHONY: lint_cpp -lint_cpp: build/compile_commands.json ## Run clang-tidy static analysis +lint_cpp: ## Run clang-tidy static analysis (requires: make configure) clang-tidy-$(CLANG_VERSION) \ --quiet -p build --header-filter="$(CURDIR)/src/.*" src/*.cpp @@ -119,32 +111,12 @@ lint_cpp: build/compile_commands.json ## Run clang-tidy static analysis lint: fmt_check lint_cpp ## Run all C++ linters .PHONY: ci -ci: fmt_check lint_cpp test ## Run all CI checks locally +ci: configure build fmt_check lint_cpp test ## Run all CI checks locally # CLEAN .PHONY: clean clean: build_clean test_clean ## Remove all build and test artifacts -# SUBMODULES -.PHONY: bump_stormlib -bump_stormlib: ## Bump StormLib submodule to latest remote - @read -rp "[*] Bump StormLib? (y/N) " yn; \ - case $$yn in \ - [yY] ) git submodule update --init --remote extern/StormLib;; \ - * ) echo "[*] Skipping...";; \ - esac - -.PHONY: bump_cli11 -bump_cli11: ## Bump CLI11 submodule to latest remote - @read -rp "[*] Bump CLI11? (y/N) " yn; \ - case $$yn in \ - [yY] ) git submodule update --init --remote extern/CLI11;; \ - * ) echo "[*] Skipping...";; \ - esac - -.PHONY: bump_submodules -bump_submodules: bump_stormlib bump_cli11 ## Bump all submodules to latest remote - # GET .PHONY: get_project_version get_project_version: ## Print the project version from CMakeLists.txt diff --git a/README.md b/README.md index 20d2b8f..2b73322 100644 --- a/README.md +++ b/README.md @@ -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-845-green) +![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) 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). @@ -19,7 +19,7 @@ A command-line tool to create, add, remove, list, extract, read, and verify MPQ - Pipe the output to `grep` or other tools to search, filter, or process files - Redirect output to files or other commands for further automation -If you require an MPQ tool with a graphical interface (GUI) and explicit support for more MPQ archive versions — I would recommend using [Ladik's MPQ Editor](http://www.zezula.net/en/mpq/download.html). +If you require an MPQ tool with a graphical interface (GUI) and explicit support for more MPQ archive versions, I would recommend using [Ladik's MPQ Editor](http://www.zezula.net/en/mpq/download.html). ## Releases From 0b089b69c0c980f0ae9e013a287e098b77c2553f Mon Sep 17 00:00:00 2001 From: thomaslaurenson Date: Mon, 8 Jun 2026 08:48:49 +1200 Subject: [PATCH 8/8] Fixed clang issues, and cross platform completion test errors --- .github/workflows/lint.yml | 5 +++-- Makefile | 6 ++++-- src/CMakeLists.txt | 4 +++- test/test_completion.py | 8 ++++---- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 53acb34..68db4c7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,8 +12,9 @@ jobs: with: submodules: true - - name: Install clang tools - run: make install_clang_tools + - run: make install_clang_tools + + - run: make configure - run: make fmt_check diff --git a/Makefile b/Makefile index 0594eb0..e4dfcf0 100644 --- a/Makefile +++ b/Makefile @@ -104,8 +104,10 @@ fmt: ## Auto-fix C++ formatting with clang-format .PHONY: lint_cpp lint_cpp: ## Run clang-tidy static analysis (requires: make configure) - clang-tidy-$(CLANG_VERSION) \ - --quiet -p build --header-filter="$(CURDIR)/src/.*" src/*.cpp + clang-tidy-$(CLANG_VERSION) --quiet -p build \ + --header-filter="$(CURDIR)/src/.*" src/*.cpp 2>&1 \ + | grep -v " warnings generated"; \ + exit $${PIPESTATUS[0]} .PHONY: lint lint: fmt_check lint_cpp ## Run all C++ linters diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a8bdb17..0b7a52c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,8 +40,10 @@ add_executable(mpqcli add_dependencies(mpqcli storm) # Include directories -target_include_directories(mpqcli PRIVATE +target_include_directories(mpqcli SYSTEM PRIVATE "${CMAKE_SOURCE_DIR}/extern/StormLib/src" +) +target_include_directories(mpqcli PRIVATE "${CMAKE_BINARY_DIR}" ) diff --git a/test/test_completion.py b/test/test_completion.py index a8712a3..9914232 100644 --- a/test/test_completion.py +++ b/test/test_completion.py @@ -38,7 +38,7 @@ def test_completion_bash_output_size(binary_path): assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" script_path = Path(__file__).parent.parent / "src" / "completion" / "mpqcli.bash" - assert len(result.stdout) == script_path.stat().st_size + assert len(result.stdout) == len(script_path.read_text()) def test_completion_zsh_output_size(binary_path): @@ -51,7 +51,7 @@ def test_completion_zsh_output_size(binary_path): assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" script_path = Path(__file__).parent.parent / "src" / "completion" / "mpqcli.zsh" - assert len(result.stdout) == script_path.stat().st_size + assert len(result.stdout) == len(script_path.read_text()) def test_completion_powershell(binary_path): @@ -77,7 +77,7 @@ def test_completion_powershell_output_size(binary_path): assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" script_path = Path(__file__).parent.parent / "src" / "completion" / "mpqcli.ps1" - assert len(result.stdout) == script_path.stat().st_size + assert len(result.stdout) == len(script_path.read_text()) def test_completion_fish(binary_path): @@ -102,7 +102,7 @@ def test_completion_fish_output_size(binary_path): assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" script_path = Path(__file__).parent.parent / "src" / "completion" / "mpqcli.fish" - assert len(result.stdout) == script_path.stat().st_size + assert len(result.stdout) == len(script_path.read_text()) def test_completion_no_shell(binary_path):