mirror of
https://github.com/CLIUtils/CLI11.git
synced 2026-01-19 04:52:08 +00:00
add permission validators as an Extra Validator (#1203)
an update of #250 Fixes #249 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
22
README.md
22
README.md
@@ -38,8 +38,9 @@ set with a simple and intuitive interface.
|
||||
- [Option options](#option-options)
|
||||
- [Validators](#validators)
|
||||
- [Default Validators](#default-validators)
|
||||
- [Validatrs that may be disabled 🚧](#validatrs-that-may-be-disabled-)
|
||||
- [Validators that may be disabled 🚧](#validators-that-may-be-disabled-)
|
||||
- [Extra Validators 🚧](#extra-validators-)
|
||||
- [permission. Requires C++17.](#permission-requires-c17)
|
||||
- [Validator Usage](#validator-usage)
|
||||
- [Transforming Validators](#transforming-validators)
|
||||
- [Validator operations](#validator-operations)
|
||||
@@ -575,7 +576,9 @@ they can be disabled by using
|
||||
|
||||
#### Default Validators
|
||||
|
||||
These validators are always available regardless of definitions
|
||||
These validators are always available regardless of definitions. These are used
|
||||
internally or are very commonly used, so will always remain available regardless
|
||||
of flags.
|
||||
|
||||
- `CLI::ExistingFile`: Requires that the file exists if given.
|
||||
- `CLI::ExistingDirectory`: Requires that the directory exists.
|
||||
@@ -590,11 +593,14 @@ These validators are always available regardless of definitions
|
||||
- `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0
|
||||
- `CLI::Number`: Requires the input be a number.
|
||||
|
||||
#### Validatrs that may be disabled 🚧
|
||||
#### Validators that may be disabled 🚧
|
||||
|
||||
Validators that may be disabled by setting `CLI11_DISABLE_EXTRA_VALIDATORS` to 1
|
||||
or enabled by setting `CLI11_ENABLE_EXTRA_VALIDATORS` to 1. By default they are
|
||||
enabled.
|
||||
enabled. In version 3.0 these will likely move to be disabled by default and be
|
||||
controlled solely by the `CLI11_ENABLE_EXTRA_VALIDATORS` option. These
|
||||
validators are less commonly used or are template heavy and require additional
|
||||
computation time that may not be valuable for some use cases.
|
||||
|
||||
- `CLI::IsMember(...)`: Require an option be a member of a given set. See
|
||||
[Transforming Validators](#transforming-validators) for more details.
|
||||
@@ -627,6 +633,14 @@ enabled.
|
||||
New validators will go into code sections that must be explicitly enabled by
|
||||
setting `CLI11_ENABLE_EXTRA_VALIDATORS` to 1
|
||||
|
||||
- `CLI::ReadPermission`: Requires that the file or folder given exist and have
|
||||
read permission. Requires C++17.
|
||||
- `CLI::WritePermission`: Requires that the file or folder given exist and have
|
||||
write permission. Requires C++17.
|
||||
- `CLI::ExecPermission`: Requires that the file given exist and have execution
|
||||
permission. Requires C++17.
|
||||
-
|
||||
|
||||
#### Validator Usage
|
||||
|
||||
These Validators once enabled can be used by simply passing the name into the
|
||||
|
||||
@@ -34,7 +34,9 @@ jobs:
|
||||
vmImage: "windows-2025"
|
||||
cli11.std: 17
|
||||
cli11.build_type: Debug
|
||||
cli11.options: -G "Visual Studio 17 2022" -A ARM64
|
||||
cli11.options:
|
||||
-G "Visual Studio 17 2022" -A ARM64
|
||||
-DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
|
||||
@@ -52,12 +54,15 @@ jobs:
|
||||
macOS-15_23:
|
||||
vmImage: "macOS-15"
|
||||
cli11.std: 23
|
||||
cli11.options: -DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
macOS-14_20:
|
||||
vmImage: "macOS-14"
|
||||
cli11.std: 20
|
||||
cli11.options: -DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
macOS-13_17:
|
||||
vmImage: "macOS-13"
|
||||
cli11.std: 17
|
||||
cli11.options: -DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
macOS-14_11:
|
||||
vmImage: "macOS-14"
|
||||
cli11.std: 11
|
||||
@@ -68,10 +73,12 @@ jobs:
|
||||
Windows17:
|
||||
vmImage: "windows-2022"
|
||||
cli11.std: 17
|
||||
cli11.options: -DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
Windows17PC:
|
||||
vmImage: "windows-2022"
|
||||
cli11.std: 17
|
||||
cli11.precompile: ON
|
||||
cli11.options: -DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
Windows11:
|
||||
vmImage: "windows-2022"
|
||||
cli11.std: 11
|
||||
@@ -87,7 +94,8 @@ jobs:
|
||||
Linux17nortti:
|
||||
vmImage: "ubuntu-latest"
|
||||
cli11.std: 17
|
||||
cli11.options: -DCMAKE_CXX_FLAGS="-fno-rtti"
|
||||
cli11.options:
|
||||
-DCMAKE_CXX_FLAGS="-fno-rtti" -DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
@@ -126,7 +134,9 @@ jobs:
|
||||
gcc9:
|
||||
containerImage: gcc:9
|
||||
cli11.std: 17
|
||||
cli11.options: -DCMAKE_CXX_FLAGS="-Wstrict-overflow=5"
|
||||
cli11.options:
|
||||
-DCMAKE_CXX_FLAGS="-Wstrict-overflow=5"
|
||||
-DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
gcc11:
|
||||
containerImage: gcc:11
|
||||
cli11.std: 20
|
||||
@@ -143,19 +153,24 @@ jobs:
|
||||
clang3.4:
|
||||
containerImage: silkeh/clang:3.4
|
||||
cli11.std: 11
|
||||
cli11.options: -DCLI11_WARNINGS_AS_ERRORS=OFF
|
||||
cli11.options:
|
||||
-DCLI11_WARNINGS_AS_ERRORS=OFF -DCLI11_DISABLE_EXTRA_VALIDATORS=1
|
||||
clang8:
|
||||
containerImage: silkeh/clang:8
|
||||
cli11.std: 14
|
||||
cli11.options: -DCLI11_FORCE_LIBCXX=ON
|
||||
cli11.options:
|
||||
-DCLI11_FORCE_LIBCXX=ON -DCLI11_DISABLE_EXTRA_VALIDATORS=1
|
||||
clang8_17:
|
||||
containerImage: silkeh/clang:8
|
||||
cli11.std: 17
|
||||
cli11.options: -DCLI11_FORCE_LIBCXX=ON
|
||||
cli11.options:
|
||||
-DCLI11_FORCE_LIBCXX=ON -DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
clang10_20:
|
||||
containerImage: silkeh/clang:10
|
||||
cli11.std: 20
|
||||
cli11.options: -DCLI11_FORCE_LIBCXX=ON -DCMAKE_CXX_FLAGS=-std=c++20
|
||||
cli11.options:
|
||||
-DCLI11_FORCE_LIBCXX=ON -DCMAKE_CXX_FLAGS=-std=c++20
|
||||
-DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
container: $[ variables['containerImage'] ]
|
||||
steps:
|
||||
- template: .ci/azure-cmake.yml
|
||||
@@ -172,19 +187,25 @@ jobs:
|
||||
gcc13_17:
|
||||
containerImage: gcc:13
|
||||
cli11.std: 17
|
||||
cli11.options: -DCMAKE_CXX_FLAGS="-Wstrict-overflow=5"
|
||||
cli11.options:
|
||||
-DCMAKE_CXX_FLAGS="-Wstrict-overflow=5"
|
||||
-DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
gcc12_20:
|
||||
containerImage: gcc:12
|
||||
cli11.std: 20
|
||||
cli11.options: -DCMAKE_CXX_FLAGS="-Wredundant-decls -Wconversion"
|
||||
cli11.options:
|
||||
-DCMAKE_CXX_FLAGS="-Wredundant-decls -Wconversion"
|
||||
-DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
clang17_23:
|
||||
containerImage: silkeh/clang:17
|
||||
cli11.std: 23
|
||||
cli11.options: -DCMAKE_CXX_FLAGS=-std=c++23
|
||||
cli11.options:
|
||||
-DCMAKE_CXX_FLAGS=-std=c++23 -DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
clang20_26:
|
||||
containerImage: silkeh/clang:20
|
||||
cli11.std: 26
|
||||
cli11.options: -DCMAKE_CXX_FLAGS=-std=c++2c
|
||||
cli11.options:
|
||||
-DCMAKE_CXX_FLAGS=-std=c++2c -DCLI11_ENABLE_EXTRA_VALIDATORS=1
|
||||
container: $[ variables['containerImage'] ]
|
||||
steps:
|
||||
- template: .ci/azure-cmake-new.yml
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#define CLI11_ENABLE_EXTRA_VALIDATORS 1
|
||||
|
||||
#pragma once
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1) || \
|
||||
(!defined(CLI11_DISABLE_EXTRA_VALIDATORS) || CLI11_DISABLE_EXTRA_VALIDATORS == 0)
|
||||
@@ -591,6 +593,24 @@ class AsSizeValue : public AsNumberWithUnit {
|
||||
|
||||
#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0
|
||||
// new extra validators
|
||||
#if CLI11_HAS_FILESYSTEM
|
||||
namespace detail {
|
||||
enum class Permission : std::uint8_t { none = 0, read = 1, write = 2, exec = 4 };
|
||||
class PermissionValidator : public Validator {
|
||||
public:
|
||||
explicit PermissionValidator(Permission permission);
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/// Check that the file exist and available for read
|
||||
const detail::PermissionValidator ReadPermissions(detail::Permission::read);
|
||||
|
||||
/// Check that the file exist and available for write
|
||||
const detail::PermissionValidator WritePermissions(detail::Permission::write);
|
||||
|
||||
/// Check that the file exist and available for write
|
||||
const detail::PermissionValidator ExecPermissions(detail::Permission::exec);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
// [CLI11:extra_validators_hpp:end]
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
// [CLI11:public_includes:set]
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -94,6 +95,62 @@ CLI11_INLINE std::map<std::string, AsSizeValue::result_t> AsSizeValue::get_mappi
|
||||
namespace detail {} // namespace detail
|
||||
/// @}
|
||||
|
||||
#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0
|
||||
// new extra validators
|
||||
namespace detail {
|
||||
|
||||
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
|
||||
CLI11_INLINE PermissionValidator::PermissionValidator(Permission permission) {
|
||||
std::filesystem::perms permission_code = std::filesystem::perms::none;
|
||||
std::string permission_name;
|
||||
switch(permission) {
|
||||
case Permission::read:
|
||||
permission_code = std::filesystem::perms::owner_read | std::filesystem::perms::group_read |
|
||||
std::filesystem::perms::others_read;
|
||||
permission_name = "read";
|
||||
break;
|
||||
case Permission::write:
|
||||
permission_code = std::filesystem::perms::owner_write | std::filesystem::perms::group_write |
|
||||
std::filesystem::perms::others_write;
|
||||
permission_name = "write";
|
||||
break;
|
||||
case Permission::exec:
|
||||
permission_code = std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec |
|
||||
std::filesystem::perms::others_exec;
|
||||
permission_name = "exec";
|
||||
break;
|
||||
case Permission::none:
|
||||
default:
|
||||
permission_code = std::filesystem::perms::none;
|
||||
break;
|
||||
}
|
||||
func_ = [permission_code](std::string &path) {
|
||||
std::error_code ec;
|
||||
auto p = std::filesystem::path(path);
|
||||
if(!std::filesystem::exists(p, ec)) {
|
||||
return std::string("Path does not exist: ") + path;
|
||||
}
|
||||
if(ec) {
|
||||
return std::string("Error checking path: ") + ec.message(); // LCOV_EXCL_LINE
|
||||
}
|
||||
if(permission_code == std::filesystem::perms::none) {
|
||||
return std::string{};
|
||||
}
|
||||
auto perms = std::filesystem::status(p, ec).permissions();
|
||||
if(ec) {
|
||||
return std::string("Error checking path status: ") + ec.message(); // LCOV_EXCL_LINE
|
||||
}
|
||||
if((perms & permission_code) == std::filesystem::perms::none) {
|
||||
return std::string("Path does not have required permissions: ") + path;
|
||||
}
|
||||
return std::string{};
|
||||
};
|
||||
description("Path with " + permission_name + " permission");
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
#endif
|
||||
// [CLI11:extra_validators_inl_hpp:end]
|
||||
} // namespace CLI
|
||||
|
||||
|
||||
@@ -640,8 +640,9 @@ CLI11_INLINE void Option::_reduce_results(results_t &out, const results_t &origi
|
||||
}
|
||||
if(original.size() > num_max) {
|
||||
if(original.size() == 2 && num_max == 1 && original[1] == "%%" && original[0] == "{}") {
|
||||
// this condition is a trap for the following empty indicator check on config files
|
||||
out = original;
|
||||
// this condition is a trap for the following empty indicator check on config files, it may not be used
|
||||
// anymore
|
||||
out = original; // LCOV_EXCL_LINE
|
||||
} else {
|
||||
throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size());
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ endif()
|
||||
|
||||
if(CLI11_ENABLE_EXTRA_VALIDATORS)
|
||||
target_compile_definitions(CLI11 ${PUBLIC_OR_INTERFACE} -DCLI11_ENABLE_EXTRA_VALIDATORS=1)
|
||||
elseif(CLI11_DISABLE_EXTRA_VALIDATORS)
|
||||
target_compile_definitions(CLI11 ${PUBLIC_OR_INTERFACE} -DCLI11_DISABLE_EXTRA_VALIDATORS=1)
|
||||
elseif(DEFINED CLI11_ENABLE_EXTRA_VALIDATORS)
|
||||
target_compile_definitions(CLI11 ${PUBLIC_OR_INTERFACE} -DCLI11_ENABLE_EXTRA_VALIDATORS=0)
|
||||
endif()
|
||||
# Allow IDE's to group targets into folders
|
||||
add_library(CLI11::CLI11 ALIAS CLI11) # for add_subdirectory calls
|
||||
|
||||
@@ -543,4 +543,110 @@ TEST_CASE_METHOD(TApp, "AsSizeValue1024", "[transform]") {
|
||||
CHECK(ki_value == value);
|
||||
}
|
||||
|
||||
#if (defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS == 1)
|
||||
|
||||
#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
|
||||
#include <filesystem>
|
||||
|
||||
TEST_CASE_METHOD(TApp, "FileExistsForRead", "[validate]") {
|
||||
std::string myfile{"TestNonFileNotUsed.txt"};
|
||||
if(std::filesystem::exists(myfile)) {
|
||||
std::filesystem::remove(myfile);
|
||||
}
|
||||
CHECK(!CLI::ReadPermissions(myfile).empty());
|
||||
|
||||
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
|
||||
CHECK(ok);
|
||||
|
||||
std::string filename = "Failed";
|
||||
app.add_option("--file", filename)->check(CLI::ReadPermissions);
|
||||
args = {"--file", myfile};
|
||||
|
||||
run();
|
||||
|
||||
CHECK(myfile == filename);
|
||||
|
||||
std::filesystem::permissions(std::filesystem::path(myfile), std::filesystem::perms::owner_exec);
|
||||
|
||||
#if !defined(_WIN32)
|
||||
// not sure how to make a file unreadable on windows in this context
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
#endif
|
||||
std::filesystem::permissions(std::filesystem::path(myfile), std::filesystem::perms::owner_write);
|
||||
std::filesystem::remove(myfile);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "FileExistsForWrite", "[validate]") {
|
||||
std::string myfile{"TestNonFileNotUsed.txt"};
|
||||
if(std::filesystem::exists(myfile)) {
|
||||
std::filesystem::remove(myfile);
|
||||
}
|
||||
CHECK(!CLI::WritePermissions(myfile).empty());
|
||||
|
||||
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
|
||||
CHECK(ok);
|
||||
|
||||
std::string filename = "Failed";
|
||||
app.add_option("--file", filename)->check(CLI::WritePermissions);
|
||||
args = {"--file", myfile};
|
||||
|
||||
run();
|
||||
|
||||
CHECK(myfile == filename);
|
||||
|
||||
std::filesystem::permissions(std::filesystem::path(myfile), std::filesystem::perms::owner_read);
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
|
||||
std::remove(myfile.c_str());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "FileExistsForExec", "[validate]") {
|
||||
std::string myfile{"TestNonFileNotUsed.txt"};
|
||||
if(std::filesystem::exists(myfile)) {
|
||||
std::filesystem::remove(myfile);
|
||||
}
|
||||
CHECK(!CLI::ExecPermissions(myfile).empty());
|
||||
|
||||
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
|
||||
CHECK(ok);
|
||||
|
||||
std::string filename = "Failed";
|
||||
app.add_option("--file", filename)->check(CLI::ExecPermissions);
|
||||
args = {"--file", myfile};
|
||||
|
||||
std::filesystem::permissions(std::filesystem::path(myfile),
|
||||
std::filesystem::perms::owner_exec | std::filesystem::perms::owner_read);
|
||||
run();
|
||||
|
||||
CHECK(myfile == filename);
|
||||
#if !defined(_WIN32)
|
||||
std::filesystem::permissions(std::filesystem::path(myfile), std::filesystem::perms::owner_read);
|
||||
CHECK_THROWS_AS(run(), CLI::ValidationError);
|
||||
// exec permission not really a thing on windows
|
||||
#endif
|
||||
|
||||
std::remove(myfile.c_str());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "noPermissionCheck", "[validate]") {
|
||||
std::string myfile{"TestNonFileNotUsed.txt"};
|
||||
if(std::filesystem::exists(myfile)) {
|
||||
std::filesystem::remove(myfile);
|
||||
}
|
||||
CHECK(!CLI::detail::PermissionValidator(CLI::detail::Permission::none)(myfile).empty());
|
||||
|
||||
bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
|
||||
CHECK(ok);
|
||||
|
||||
std::string filename = "Failed";
|
||||
app.add_option("--file", filename)->check(CLI::detail::PermissionValidator(CLI::detail::Permission::none));
|
||||
args = {"--file", myfile};
|
||||
|
||||
run();
|
||||
|
||||
CHECK(myfile == filename);
|
||||
std::remove(myfile.c_str());
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user