Commit a227cd10fc6dd2905965bafc2d3aa9cb9d910ce5
Committed by
GitHub
1 parent
48624ead
feat: unicode support (#804)
* Add unicode support tests * Add unicode parse tests * Implement #14 * Slim down Windows.h * Fix documentation comments * Fix clang-tidy and cpplint * Update README * Fix clang-tidy * Fix to_path not being available on linux * Add roundtrip encoding tests * style: pre-commit.ci fixes * Fix pre-commit.ci * Fix codacy * Exclude parse_unicode which should not contain a newline from pre-commit * Remove a test which breaks CI * Fix build in CI * Replace broken execute_with tests * Fix wide string conversions on all systems * Fix system args on apple * style: pre-commit.ci fixes * Fix some includes * Fix wrong size calculation and comments * Add guards around codecvt * Fix _Pragma not recognized on MSVC * Fix bad macro check * Fix include * Fix narrow and widen when codecvt is missing * Fix some weird bug in old MSVC * Add dependent applications to meson-build * Fix precompilation * Fix lint * Fix coverage * Update README * style: pre-commit.ci fixes * Fix lint * Fix coverage * Fix optional braces offending clang * Remove copied comments from Windows.h * Suppress flawfinder detects * Fix cmake config tests failing because of a missing lib * chore: update copyright on new files to 2023 Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * style: pre-commit.ci fixes Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com>
Showing
26 changed files
with
1093 additions
and
57 deletions
.github/workflows/tests.yml
| ... | ... | @@ -128,9 +128,9 @@ jobs: |
| 128 | 128 | - name: Build |
| 129 | 129 | run: meson compile -C build-meson |
| 130 | 130 | |
| 131 | - cmake-config: | |
| 132 | - name: CMake config check | |
| 133 | - runs-on: ubuntu-20.04 | |
| 131 | + cmake-config-ubuntu-1804: | |
| 132 | + name: CMake config check (Ubuntu 18.04) | |
| 133 | + runs-on: ubuntu-18.04 | |
| 134 | 134 | steps: |
| 135 | 135 | - uses: actions/checkout@v3 |
| 136 | 136 | |
| ... | ... | @@ -175,6 +175,12 @@ jobs: |
| 175 | 175 | cmake-version: "3.10" |
| 176 | 176 | if: success() || failure() |
| 177 | 177 | |
| 178 | + cmake-config-ubuntu-2004: | |
| 179 | + name: CMake config check (Ubuntu 20.04) | |
| 180 | + runs-on: ubuntu-20.04 | |
| 181 | + steps: | |
| 182 | + - uses: actions/checkout@v3 | |
| 183 | + | |
| 178 | 184 | - name: Check CMake 3.11 (full) |
| 179 | 185 | uses: ./.github/actions/quick_cmake |
| 180 | 186 | with: |
| ... | ... | @@ -212,6 +218,12 @@ jobs: |
| 212 | 218 | cmake-version: "3.16" |
| 213 | 219 | if: success() || failure() |
| 214 | 220 | |
| 221 | + cmake-config-ubuntu-2204: | |
| 222 | + name: CMake config check (Ubuntu 22.04) | |
| 223 | + runs-on: ubuntu-22.04 | |
| 224 | + steps: | |
| 225 | + - uses: actions/checkout@v3 | |
| 226 | + | |
| 215 | 227 | - name: Check CMake 3.17 |
| 216 | 228 | uses: ./.github/actions/quick_cmake |
| 217 | 229 | with: | ... | ... |
.gitignore
CLI11.hpp.in
| ... | ... | @@ -40,10 +40,24 @@ |
| 40 | 40 | |
| 41 | 41 | {macros_hpp} |
| 42 | 42 | |
| 43 | +{slim_windows_h_hpp} | |
| 44 | + | |
| 43 | 45 | {validators_hpp_filesystem} |
| 44 | 46 | |
| 47 | +{encoding_includes} | |
| 48 | + | |
| 49 | +{argv_inl_includes} | |
| 50 | + | |
| 45 | 51 | namespace {namespace} {{ |
| 46 | 52 | |
| 53 | +{encoding_hpp} | |
| 54 | + | |
| 55 | +{encoding_inl_hpp} | |
| 56 | + | |
| 57 | +{argv_hpp} | |
| 58 | + | |
| 59 | +{argv_inl_hpp} | |
| 60 | + | |
| 47 | 61 | {string_tools_hpp} |
| 48 | 62 | |
| 49 | 63 | {string_tools_inl_hpp} | ... | ... |
CPPLINT.cfg
| ... | ... | @@ -4,6 +4,7 @@ linelength=120 # As in .clang-format |
| 4 | 4 | # Unused filters |
| 5 | 5 | filter=-build/c++11 # Reports e.g. chrono and thread, which overlap with Chromium's API. Not applicable to general C++ projects. |
| 6 | 6 | filter=-build/include_order # Requires unusual include order that encourages creating not self-contained headers |
| 7 | +filter=-build/include_subdir # Prevents including files in current directory for whatever reason | |
| 7 | 8 | filter=-readability/nolint # Conflicts with clang-tidy |
| 8 | 9 | filter=-readability/check # Catch uses CHECK(a == b) (Tests only) |
| 9 | 10 | filter=-build/namespaces # Currently using it for one test (Tests only) | ... | ... |
README.md
| ... | ... | @@ -50,6 +50,7 @@ set with a simple and intuitive interface. |
| 50 | 50 | - [Formatting](#formatting) |
| 51 | 51 | - [Subclassing](#subclassing) |
| 52 | 52 | - [How it works](#how-it-works) |
| 53 | + - [Unicode support](#unicode-support) | |
| 53 | 54 | - [Utilities](#utilities) |
| 54 | 55 | - [Other libraries](#other-libraries) |
| 55 | 56 | - [API](#api) |
| ... | ... | @@ -164,9 +165,6 @@ this library: |
| 164 | 165 | option to disable it). |
| 165 | 166 | - Autocomplete: This might eventually be added to both Plumbum and CLI11, but it |
| 166 | 167 | is not supported yet. |
| 167 | -- Wide strings / unicode: Since this uses the standard library only, it might be | |
| 168 | - hard to properly implement, but I would be open to suggestions in how to do | |
| 169 | - this. | |
| 170 | 168 | |
| 171 | 169 | ## Install |
| 172 | 170 | |
| ... | ... | @@ -278,13 +276,13 @@ To set up, add options, and run, your main function will look something like |
| 278 | 276 | this: |
| 279 | 277 | |
| 280 | 278 | ```cpp |
| 281 | -int main(int argc, char** argv) { | |
| 279 | +int main() { | |
| 282 | 280 | CLI::App app{"App description"}; |
| 283 | 281 | |
| 284 | 282 | std::string filename = "default"; |
| 285 | 283 | app.add_option("-f,--file", filename, "A help string"); |
| 286 | 284 | |
| 287 | - CLI11_PARSE(app, argc, argv); | |
| 285 | + CLI11_PARSE(app); | |
| 288 | 286 | return 0; |
| 289 | 287 | } |
| 290 | 288 | ``` |
| ... | ... | @@ -293,7 +291,7 @@ int main(int argc, char** argv) { |
| 293 | 291 | |
| 294 | 292 | ```cpp |
| 295 | 293 | try { |
| 296 | - app.parse(argc, argv); | |
| 294 | + app.parse(); | |
| 297 | 295 | } catch (const CLI::ParseError &e) { |
| 298 | 296 | return app.exit(e); |
| 299 | 297 | } |
| ... | ... | @@ -307,6 +305,25 @@ other processing for speed and to ensure required options and the like do not |
| 307 | 305 | interfere. |
| 308 | 306 | |
| 309 | 307 | </p></details> |
| 308 | + | |
| 309 | +<details><summary>Note: Why are argc and argv not used? (click to expand)</summary><p> | |
| 310 | + | |
| 311 | +`argc` and `argv` may contain incorrect information on Windows when unicode text | |
| 312 | +is passed in. Check out a section on [unicode support](#unicode-support) below. | |
| 313 | + | |
| 314 | +If this is not a concern, you can explicitly pass `argc` and `argv` from main or | |
| 315 | +from an external preprocessor of CLI arguments to `parse`: | |
| 316 | + | |
| 317 | +```cpp | |
| 318 | +int main(int argc, char** argv) { | |
| 319 | + // ... | |
| 320 | + | |
| 321 | + CLI11_PARSE(app, argc, argv); | |
| 322 | + return 0; | |
| 323 | +} | |
| 324 | +``` | |
| 325 | + | |
| 326 | +</p></details> | |
| 310 | 327 | </br> |
| 311 | 328 | |
| 312 | 329 | The initialization is just one line, adding options is just two each. The parse |
| ... | ... | @@ -1468,6 +1485,96 @@ app.add_option("--fancy-count", [](std::vector<std::string> val){ |
| 1468 | 1485 | }); |
| 1469 | 1486 | ``` |
| 1470 | 1487 | |
| 1488 | +### Unicode support | |
| 1489 | + | |
| 1490 | +CLI11 supports Unicode and wide strings as defined in the | |
| 1491 | +[UTF-8 Everywhere](http://utf8everywhere.org/) manifesto. In particular: | |
| 1492 | + | |
| 1493 | +- The library can parse a wide version of command-line arguments on Windows, | |
| 1494 | + which are converted internally to UTF-8 (more on this below); | |
| 1495 | +- You can store option values in `std::wstring`, in which case they will be | |
| 1496 | + converted to a correct wide string encoding on your system (UTF-16 on Windows | |
| 1497 | + and UTF-32 on most other systems); | |
| 1498 | +- Instead of storing wide strings, it is recommended to use provided `widen` and | |
| 1499 | + `narrow` functions to convert to and from wide strings when actually necessary | |
| 1500 | + (such as when calling into Windows APIs). | |
| 1501 | + | |
| 1502 | +When using the command line on Windows with unicode arguments, your `main` | |
| 1503 | +function may already receive broken Unicode. Parsing `argv` at that point will | |
| 1504 | +not give you a correct string. To fix this, you have three options: | |
| 1505 | + | |
| 1506 | +1. If you pass unmodified command-line arguments to CLI11, call `app.parse()` | |
| 1507 | + instead of `app.parse(argc, argv)` (or `CLI11_PARSE(app)` instead of | |
| 1508 | + `CLI11_PARSE(app, argc, argv)`). The library will find correct arguments | |
| 1509 | + itself. | |
| 1510 | + | |
| 1511 | + ```cpp | |
| 1512 | + int main() { | |
| 1513 | + CLI::App app; | |
| 1514 | + // ... | |
| 1515 | + CLI11_PARSE(app); | |
| 1516 | + } | |
| 1517 | + ``` | |
| 1518 | + | |
| 1519 | +2. Get correct arguments with which the program was originally executed using | |
| 1520 | + provided functions: `CLI::argc()` and `CLI::argv()`. These two methods are | |
| 1521 | + the only cross-platform ways of handling unicode correctly. | |
| 1522 | + | |
| 1523 | + ```cpp | |
| 1524 | + int main() { | |
| 1525 | + CLI::App app; | |
| 1526 | + // ... | |
| 1527 | + CLI11_PARSE(app, CLI::argc(), CLI::argv()); | |
| 1528 | + } | |
| 1529 | + ``` | |
| 1530 | + | |
| 1531 | +3. Use the Windows-only non-standard `wmain` function, which accepts | |
| 1532 | + `wchar_t *argv[]` instead of `char* argv[]`. Parsing this will allow CLI to | |
| 1533 | + convert wide strings to UTF-8 without losing information. | |
| 1534 | + | |
| 1535 | + ```cpp | |
| 1536 | + int wmain(int argc, wchar_t *argv[]) { | |
| 1537 | + CLI::App app; | |
| 1538 | + // ... | |
| 1539 | + CLI11_PARSE(app, argc, argv); | |
| 1540 | + } | |
| 1541 | + ``` | |
| 1542 | + | |
| 1543 | +4. Retrieve arguments yourself by using Windows APIs like | |
| 1544 | + [`CommandLineToArgvW`](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw) | |
| 1545 | + and pass them to CLI. This is what the library is doing under the hood in | |
| 1546 | + `CLI::argv()`. | |
| 1547 | + | |
| 1548 | +The library provides functions to convert between UTF-8 and wide strings: | |
| 1549 | + | |
| 1550 | +```cpp | |
| 1551 | +namespace CLI { | |
| 1552 | + std::string narrow(const std::wstring &str); | |
| 1553 | + std::string narrow(const wchar_t *str); | |
| 1554 | + std::string narrow(const wchar_t *str, std::size_t size); | |
| 1555 | + std::string narrow(std::wstring_view str); // C++17 | |
| 1556 | + | |
| 1557 | + std::wstring widen(const std::string &str); | |
| 1558 | + std::wstring widen(const char *str); | |
| 1559 | + std::wstring widen(const char *str, std::size_t size); | |
| 1560 | + std::wstring widen(std::string_view str); // C++17 | |
| 1561 | +} | |
| 1562 | +``` | |
| 1563 | + | |
| 1564 | +#### Note on using Unicode paths | |
| 1565 | + | |
| 1566 | +When creating a `filesystem::path` from a UTF-8 path on Windows, you need to | |
| 1567 | +convert it to a wide string first. CLI11 provides a platform-independent | |
| 1568 | +`to_path` function, which will convert a UTF-8 string to path, the right way: | |
| 1569 | + | |
| 1570 | +```cpp | |
| 1571 | +std::string utf8_name = "Hello Halló Привет 你好 👩🚀❤️.txt"; | |
| 1572 | + | |
| 1573 | +std::filesystem::path p = CLI::to_path(utf8_name); | |
| 1574 | +std::ifstream stream(CLI::to_path(utf8_name)); | |
| 1575 | +// etc. | |
| 1576 | +``` | |
| 1577 | + | |
| 1471 | 1578 | ### Utilities |
| 1472 | 1579 | |
| 1473 | 1580 | There are a few other utilities that are often useful in CLI programming. These | ... | ... |
include/CLI/App.hpp
| ... | ... | @@ -35,9 +35,9 @@ namespace CLI { |
| 35 | 35 | // [CLI11:app_hpp:verbatim] |
| 36 | 36 | |
| 37 | 37 | #ifndef CLI11_PARSE |
| 38 | -#define CLI11_PARSE(app, argc, argv) \ | |
| 38 | +#define CLI11_PARSE(app, ...) \ | |
| 39 | 39 | try { \ |
| 40 | - (app).parse((argc), (argv)); \ | |
| 40 | + (app).parse(__VA_ARGS__); \ | |
| 41 | 41 | } catch(const CLI::ParseError &e) { \ |
| 42 | 42 | return (app).exit(e); \ |
| 43 | 43 | } |
| ... | ... | @@ -837,15 +837,25 @@ class App { |
| 837 | 837 | /// Reset the parsed data |
| 838 | 838 | void clear(); |
| 839 | 839 | |
| 840 | + /// Parse the command-line arguments passed to the main function of the executable. | |
| 841 | + /// This overload will correctly parse unicode arguments on Windows. | |
| 842 | + void parse(); | |
| 843 | + | |
| 840 | 844 | /// Parses the command line - throws errors. |
| 841 | 845 | /// This must be called after the options are in but before the rest of the program. |
| 842 | 846 | void parse(int argc, const char *const *argv); |
| 847 | + void parse(int argc, const wchar_t *const *argv); | |
| 848 | + | |
| 849 | + private: | |
| 850 | + template <class CharT> void parse_char_t(int argc, const CharT *const *argv); | |
| 843 | 851 | |
| 852 | + public: | |
| 844 | 853 | /// Parse a single string as if it contained command line arguments. |
| 845 | 854 | /// This function splits the string into arguments then calls parse(std::vector<std::string> &) |
| 846 | 855 | /// the function takes an optional boolean argument specifying if the programName is included in the string to |
| 847 | 856 | /// process |
| 848 | 857 | void parse(std::string commandline, bool program_name_included = false); |
| 858 | + void parse(std::wstring commandline, bool program_name_included = false); | |
| 849 | 859 | |
| 850 | 860 | /// The real work is done here. Expects a reversed vector. |
| 851 | 861 | /// Changes the vector to the remaining options. | ... | ... |
include/CLI/Argv.hpp
0 → 100644
| 1 | +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner | |
| 2 | +// under NSF AWARD 1414736 and by the respective contributors. | |
| 3 | +// All rights reserved. | |
| 4 | +// | |
| 5 | +// SPDX-License-Identifier: BSD-3-Clause | |
| 6 | + | |
| 7 | +#pragma once | |
| 8 | + | |
| 9 | +#include <CLI/Macros.hpp> | |
| 10 | + | |
| 11 | +namespace CLI { | |
| 12 | +// [CLI11:argv_hpp:verbatim] | |
| 13 | + | |
| 14 | +/// argc as passed in to this executable. | |
| 15 | +CLI11_INLINE int argc(); | |
| 16 | + | |
| 17 | +/// argv as passed in to this executable, converted to utf-8 on Windows. | |
| 18 | +CLI11_INLINE const char *const *argv(); | |
| 19 | + | |
| 20 | +// [CLI11:argv_hpp:end] | |
| 21 | +} // namespace CLI | |
| 22 | + | |
| 23 | +#ifndef CLI11_COMPILE | |
| 24 | +#include "impl/Argv_inl.hpp" | |
| 25 | +#endif | ... | ... |
include/CLI/CLI.hpp
include/CLI/Encoding.hpp
0 → 100644
| 1 | +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner | |
| 2 | +// under NSF AWARD 1414736 and by the respective contributors. | |
| 3 | +// All rights reserved. | |
| 4 | +// | |
| 5 | +// SPDX-License-Identifier: BSD-3-Clause | |
| 6 | + | |
| 7 | +#pragma once | |
| 8 | + | |
| 9 | +#include <CLI/Macros.hpp> | |
| 10 | + | |
| 11 | +// [CLI11:public_includes:set] | |
| 12 | +#include <string> | |
| 13 | +// [CLI11:public_includes:end] | |
| 14 | + | |
| 15 | +// [CLI11:encoding_includes:verbatim] | |
| 16 | +#ifdef CLI11_CPP17 | |
| 17 | +#include <string_view> | |
| 18 | +#endif // CLI11_CPP17 | |
| 19 | + | |
| 20 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 | |
| 21 | +#include <filesystem> | |
| 22 | +#include <string_view> // NOLINT(build/include) | |
| 23 | +#endif // CLI11_HAS_FILESYSTEM | |
| 24 | +// [CLI11:encoding_includes:end] | |
| 25 | + | |
| 26 | +namespace CLI { | |
| 27 | +// [CLI11:encoding_hpp:verbatim] | |
| 28 | + | |
| 29 | +/// Convert a wide string to a narrow string. | |
| 30 | +CLI11_INLINE std::string narrow(const std::wstring &str); | |
| 31 | +CLI11_INLINE std::string narrow(const wchar_t *str); | |
| 32 | +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size); | |
| 33 | + | |
| 34 | +/// Convert a narrow string to a wide string. | |
| 35 | +CLI11_INLINE std::wstring widen(const std::string &str); | |
| 36 | +CLI11_INLINE std::wstring widen(const char *str); | |
| 37 | +CLI11_INLINE std::wstring widen(const char *str, std::size_t size); | |
| 38 | + | |
| 39 | +#ifdef CLI11_CPP17 | |
| 40 | +CLI11_INLINE std::string narrow(std::wstring_view str); | |
| 41 | +CLI11_INLINE std::wstring widen(std::string_view str); | |
| 42 | +#endif // CLI11_CPP17 | |
| 43 | + | |
| 44 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 | |
| 45 | +/// Convert a char-string to a native path correctly. | |
| 46 | +CLI11_INLINE std::filesystem::path to_path(std::string_view str); | |
| 47 | +#endif // CLI11_HAS_FILESYSTEM | |
| 48 | + | |
| 49 | +// [CLI11:encoding_hpp:end] | |
| 50 | +} // namespace CLI | |
| 51 | + | |
| 52 | +#ifndef CLI11_COMPILE | |
| 53 | +#include "impl/Encoding_inl.hpp" | |
| 54 | +#endif | ... | ... |
include/CLI/Macros.hpp
| ... | ... | @@ -66,6 +66,62 @@ |
| 66 | 66 | #endif |
| 67 | 67 | #endif |
| 68 | 68 | |
| 69 | +/** <filesystem> availability */ | |
| 70 | +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM | |
| 71 | +#if __has_include(<filesystem>) | |
| 72 | +// Filesystem cannot be used if targeting macOS < 10.15 | |
| 73 | +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 | |
| 74 | +#define CLI11_HAS_FILESYSTEM 0 | |
| 75 | +#elif defined(__wasi__) | |
| 76 | +// As of wasi-sdk-14, filesystem is not implemented | |
| 77 | +#define CLI11_HAS_FILESYSTEM 0 | |
| 78 | +#else | |
| 79 | +#include <filesystem> | |
| 80 | +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 | |
| 81 | +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 | |
| 82 | +#define CLI11_HAS_FILESYSTEM 1 | |
| 83 | +#elif defined(__GLIBCXX__) | |
| 84 | +// if we are using gcc and Version <9 default to no filesystem | |
| 85 | +#define CLI11_HAS_FILESYSTEM 0 | |
| 86 | +#else | |
| 87 | +#define CLI11_HAS_FILESYSTEM 1 | |
| 88 | +#endif | |
| 89 | +#else | |
| 90 | +#define CLI11_HAS_FILESYSTEM 0 | |
| 91 | +#endif | |
| 92 | +#endif | |
| 93 | +#endif | |
| 94 | +#endif | |
| 95 | + | |
| 96 | +/** <codecvt> availability */ | |
| 97 | +#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 | |
| 98 | +#define CLI11_HAS_CODECVT 0 | |
| 99 | +#else | |
| 100 | +#define CLI11_HAS_CODECVT 1 | |
| 101 | +#include <codecvt> | |
| 102 | +#endif | |
| 103 | + | |
| 104 | +/** disable deprecations */ | |
| 105 | +#if defined(__GNUC__) // GCC or clang | |
| 106 | +#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") | |
| 107 | +#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") | |
| 108 | + | |
| 109 | +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") | |
| 110 | + | |
| 111 | +#elif defined(_MSC_VER) | |
| 112 | +#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push)) | |
| 113 | +#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) | |
| 114 | + | |
| 115 | +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) | |
| 116 | + | |
| 117 | +#else | |
| 118 | +#define CLI11_DIAGNOSTIC_PUSH | |
| 119 | +#define CLI11_DIAGNOSTIC_POP | |
| 120 | + | |
| 121 | +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED | |
| 122 | + | |
| 123 | +#endif | |
| 124 | + | |
| 69 | 125 | /** Inline macro **/ |
| 70 | 126 | #ifdef CLI11_COMPILE |
| 71 | 127 | #define CLI11_INLINE | ... | ... |
include/CLI/TypeTools.hpp
| ... | ... | @@ -18,6 +18,7 @@ |
| 18 | 18 | #include <vector> |
| 19 | 19 | // [CLI11:public_includes:end] |
| 20 | 20 | |
| 21 | +#include "Encoding.hpp" | |
| 21 | 22 | #include "StringTools.hpp" |
| 22 | 23 | |
| 23 | 24 | namespace CLI { |
| ... | ... | @@ -242,8 +243,10 @@ struct is_mutable_container< |
| 242 | 243 | decltype(std::declval<T>().clear()), |
| 243 | 244 | decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(), |
| 244 | 245 | std::declval<const typename T::value_type &>()))>, |
| 245 | - void>> | |
| 246 | - : public conditional_t<std::is_constructible<T, std::string>::value, std::false_type, std::true_type> {}; | |
| 246 | + void>> : public conditional_t<std::is_constructible<T, std::string>::value || | |
| 247 | + std::is_constructible<T, std::wstring>::value, | |
| 248 | + std::false_type, | |
| 249 | + std::true_type> {}; | |
| 247 | 250 | |
| 248 | 251 | // check to see if an object is a mutable container (fail by default) |
| 249 | 252 | template <typename T, typename _ = void> struct is_readable_container : std::false_type {}; |
| ... | ... | @@ -546,6 +549,8 @@ enum class object_category : int { |
| 546 | 549 | // string like types |
| 547 | 550 | string_assignable = 23, |
| 548 | 551 | string_constructible = 24, |
| 552 | + wstring_assignable = 25, | |
| 553 | + wstring_constructible = 26, | |
| 549 | 554 | other = 45, |
| 550 | 555 | // special wrapper or container types |
| 551 | 556 | wrapper_value = 50, |
| ... | ... | @@ -613,6 +618,27 @@ struct classify_object< |
| 613 | 618 | static constexpr object_category value{object_category::string_constructible}; |
| 614 | 619 | }; |
| 615 | 620 | |
| 621 | +/// Wide strings | |
| 622 | +template <typename T> | |
| 623 | +struct classify_object<T, | |
| 624 | + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | |
| 625 | + !std::is_assignable<T &, std::string>::value && | |
| 626 | + !std::is_constructible<T, std::string>::value && | |
| 627 | + std::is_assignable<T &, std::wstring>::value>::type> { | |
| 628 | + static constexpr object_category value{object_category::wstring_assignable}; | |
| 629 | +}; | |
| 630 | + | |
| 631 | +template <typename T> | |
| 632 | +struct classify_object< | |
| 633 | + T, | |
| 634 | + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | |
| 635 | + !std::is_assignable<T &, std::string>::value && | |
| 636 | + !std::is_constructible<T, std::string>::value && | |
| 637 | + !std::is_assignable<T &, std::wstring>::value && (type_count<T>::value == 1) && | |
| 638 | + std::is_constructible<T, std::wstring>::value>::type> { | |
| 639 | + static constexpr object_category value{object_category::wstring_constructible}; | |
| 640 | +}; | |
| 641 | + | |
| 616 | 642 | /// Enumerations |
| 617 | 643 | template <typename T> struct classify_object<T, typename std::enable_if<std::is_enum<T>::value>::type> { |
| 618 | 644 | static constexpr object_category value{object_category::enumeration}; |
| ... | ... | @@ -625,12 +651,13 @@ template <typename T> struct classify_object<T, typename std::enable_if<is_compl |
| 625 | 651 | /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, |
| 626 | 652 | /// vectors, and enumerations |
| 627 | 653 | template <typename T> struct uncommon_type { |
| 628 | - using type = typename std::conditional<!std::is_floating_point<T>::value && !std::is_integral<T>::value && | |
| 629 | - !std::is_assignable<T &, std::string>::value && | |
| 630 | - !std::is_constructible<T, std::string>::value && !is_complex<T>::value && | |
| 631 | - !is_mutable_container<T>::value && !std::is_enum<T>::value, | |
| 632 | - std::true_type, | |
| 633 | - std::false_type>::type; | |
| 654 | + using type = typename std::conditional< | |
| 655 | + !std::is_floating_point<T>::value && !std::is_integral<T>::value && | |
| 656 | + !std::is_assignable<T &, std::string>::value && !std::is_constructible<T, std::string>::value && | |
| 657 | + !std::is_assignable<T &, std::wstring>::value && !std::is_constructible<T, std::wstring>::value && | |
| 658 | + !is_complex<T>::value && !is_mutable_container<T>::value && !std::is_enum<T>::value, | |
| 659 | + std::true_type, | |
| 660 | + std::false_type>::type; | |
| 634 | 661 | static constexpr bool value = type::value; |
| 635 | 662 | }; |
| 636 | 663 | |
| ... | ... | @@ -1005,6 +1032,23 @@ bool lexical_cast(const std::string &input, T &output) { |
| 1005 | 1032 | return true; |
| 1006 | 1033 | } |
| 1007 | 1034 | |
| 1035 | +/// Wide strings | |
| 1036 | +template < | |
| 1037 | + typename T, | |
| 1038 | + enable_if_t<classify_object<T>::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> | |
| 1039 | +bool lexical_cast(const std::string &input, T &output) { | |
| 1040 | + output = widen(input); | |
| 1041 | + return true; | |
| 1042 | +} | |
| 1043 | + | |
| 1044 | +template < | |
| 1045 | + typename T, | |
| 1046 | + enable_if_t<classify_object<T>::value == object_category::wstring_constructible, detail::enabler> = detail::dummy> | |
| 1047 | +bool lexical_cast(const std::string &input, T &output) { | |
| 1048 | + output = T{widen(input)}; | |
| 1049 | + return true; | |
| 1050 | +} | |
| 1051 | + | |
| 1008 | 1052 | /// Enumerations |
| 1009 | 1053 | template <typename T, |
| 1010 | 1054 | enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy> |
| ... | ... | @@ -1133,7 +1177,9 @@ template <typename AssignTo, |
| 1133 | 1177 | typename ConvertTo, |
| 1134 | 1178 | enable_if_t<std::is_same<AssignTo, ConvertTo>::value && |
| 1135 | 1179 | (classify_object<AssignTo>::value == object_category::string_assignable || |
| 1136 | - classify_object<AssignTo>::value == object_category::string_constructible), | |
| 1180 | + classify_object<AssignTo>::value == object_category::string_constructible || | |
| 1181 | + classify_object<AssignTo>::value == object_category::wstring_assignable || | |
| 1182 | + classify_object<AssignTo>::value == object_category::wstring_constructible), | |
| 1137 | 1183 | detail::enabler> = detail::dummy> |
| 1138 | 1184 | bool lexical_assign(const std::string &input, AssignTo &output) { |
| 1139 | 1185 | return lexical_cast(input, output); |
| ... | ... | @@ -1144,7 +1190,9 @@ template <typename AssignTo, |
| 1144 | 1190 | typename ConvertTo, |
| 1145 | 1191 | enable_if_t<std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, AssignTo>::value && |
| 1146 | 1192 | classify_object<AssignTo>::value != object_category::string_assignable && |
| 1147 | - classify_object<AssignTo>::value != object_category::string_constructible, | |
| 1193 | + classify_object<AssignTo>::value != object_category::string_constructible && | |
| 1194 | + classify_object<AssignTo>::value != object_category::wstring_assignable && | |
| 1195 | + classify_object<AssignTo>::value != object_category::wstring_constructible, | |
| 1148 | 1196 | detail::enabler> = detail::dummy> |
| 1149 | 1197 | bool lexical_assign(const std::string &input, AssignTo &output) { |
| 1150 | 1198 | if(input.empty()) { | ... | ... |
include/CLI/Validators.hpp
| ... | ... | @@ -26,34 +26,6 @@ |
| 26 | 26 | |
| 27 | 27 | // [CLI11:validators_hpp_filesystem:verbatim] |
| 28 | 28 | |
| 29 | -// C standard library | |
| 30 | -// Only needed for existence checking | |
| 31 | -#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM | |
| 32 | -#if __has_include(<filesystem>) | |
| 33 | -// Filesystem cannot be used if targeting macOS < 10.15 | |
| 34 | -#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 | |
| 35 | -#define CLI11_HAS_FILESYSTEM 0 | |
| 36 | -#elif defined(__wasi__) | |
| 37 | -// As of wasi-sdk-14, filesystem is not implemented | |
| 38 | -#define CLI11_HAS_FILESYSTEM 0 | |
| 39 | -#else | |
| 40 | -#include <filesystem> | |
| 41 | -#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 | |
| 42 | -#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 | |
| 43 | -#define CLI11_HAS_FILESYSTEM 1 | |
| 44 | -#elif defined(__GLIBCXX__) | |
| 45 | -// if we are using gcc and Version <9 default to no filesystem | |
| 46 | -#define CLI11_HAS_FILESYSTEM 0 | |
| 47 | -#else | |
| 48 | -#define CLI11_HAS_FILESYSTEM 1 | |
| 49 | -#endif | |
| 50 | -#else | |
| 51 | -#define CLI11_HAS_FILESYSTEM 0 | |
| 52 | -#endif | |
| 53 | -#endif | |
| 54 | -#endif | |
| 55 | -#endif | |
| 56 | - | |
| 57 | 29 | #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 |
| 58 | 30 | #include <filesystem> // NOLINT(build/include) |
| 59 | 31 | #else | ... | ... |
include/CLI/impl/App_inl.hpp
| ... | ... | @@ -9,6 +9,9 @@ |
| 9 | 9 | // This include is only needed for IDEs to discover symbols |
| 10 | 10 | #include <CLI/App.hpp> |
| 11 | 11 | |
| 12 | +#include <CLI/Argv.hpp> | |
| 13 | +#include <CLI/Encoding.hpp> | |
| 14 | + | |
| 12 | 15 | // [CLI11:public_includes:set] |
| 13 | 16 | #include <algorithm> |
| 14 | 17 | #include <memory> |
| ... | ... | @@ -474,17 +477,31 @@ CLI11_INLINE void App::clear() { |
| 474 | 477 | } |
| 475 | 478 | } |
| 476 | 479 | |
| 477 | -CLI11_INLINE void App::parse(int argc, const char *const *argv) { | |
| 480 | +CLI11_INLINE void App::parse() { parse(argc(), argv()); } // LCOV_EXCL_LINE | |
| 481 | + | |
| 482 | +CLI11_INLINE void App::parse(int argc, const char *const *argv) { parse_char_t(argc, argv); } | |
| 483 | +CLI11_INLINE void App::parse(int argc, const wchar_t *const *argv) { parse_char_t(argc, argv); } | |
| 484 | + | |
| 485 | +namespace detail { | |
| 486 | + | |
| 487 | +// Do nothing or perform narrowing | |
| 488 | +CLI11_INLINE const char *maybe_narrow(const char *str) { return str; } | |
| 489 | +CLI11_INLINE std::string maybe_narrow(const wchar_t *str) { return narrow(str); } | |
| 490 | + | |
| 491 | +} // namespace detail | |
| 492 | + | |
| 493 | +template <class CharT> CLI11_INLINE void App::parse_char_t(int argc, const CharT *const *argv) { | |
| 478 | 494 | // If the name is not set, read from command line |
| 479 | 495 | if(name_.empty() || has_automatic_name_) { |
| 480 | 496 | has_automatic_name_ = true; |
| 481 | - name_ = argv[0]; | |
| 497 | + name_ = detail::maybe_narrow(argv[0]); | |
| 482 | 498 | } |
| 483 | 499 | |
| 484 | 500 | std::vector<std::string> args; |
| 485 | 501 | args.reserve(static_cast<std::size_t>(argc) - 1U); |
| 486 | 502 | for(auto i = static_cast<std::size_t>(argc) - 1U; i > 0U; --i) |
| 487 | - args.emplace_back(argv[i]); | |
| 503 | + args.emplace_back(detail::maybe_narrow(argv[i])); | |
| 504 | + | |
| 488 | 505 | parse(std::move(args)); |
| 489 | 506 | } |
| 490 | 507 | |
| ... | ... | @@ -515,6 +532,10 @@ CLI11_INLINE void App::parse(std::string commandline, bool program_name_included |
| 515 | 532 | parse(std::move(args)); |
| 516 | 533 | } |
| 517 | 534 | |
| 535 | +CLI11_INLINE void App::parse(std::wstring commandline, bool program_name_included) { | |
| 536 | + parse(narrow(commandline), program_name_included); | |
| 537 | +} | |
| 538 | + | |
| 518 | 539 | CLI11_INLINE void App::parse(std::vector<std::string> &args) { |
| 519 | 540 | // Clear if parsed |
| 520 | 541 | if(parsed_ > 0) | ... | ... |
include/CLI/impl/Argv_inl.hpp
0 → 100644
| 1 | +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner | |
| 2 | +// under NSF AWARD 1414736 and by the respective contributors. | |
| 3 | +// All rights reserved. | |
| 4 | +// | |
| 5 | +// SPDX-License-Identifier: BSD-3-Clause | |
| 6 | + | |
| 7 | +#pragma once | |
| 8 | + | |
| 9 | +// This include is only needed for IDEs to discover symbols | |
| 10 | +#include <CLI/Argv.hpp> | |
| 11 | + | |
| 12 | +#include <CLI/Encoding.hpp> | |
| 13 | + | |
| 14 | +// [CLI11:public_includes:set] | |
| 15 | +#include <algorithm> | |
| 16 | +#include <memory> | |
| 17 | +#include <stdexcept> | |
| 18 | +#include <string> | |
| 19 | +#include <vector> | |
| 20 | +// [CLI11:public_includes:end] | |
| 21 | + | |
| 22 | +#ifdef _WIN32 | |
| 23 | +#include "SlimWindowsH.hpp" | |
| 24 | +#endif // _WIN32 | |
| 25 | + | |
| 26 | +// [CLI11:argv_inl_includes:verbatim] | |
| 27 | +#if defined(_WIN32) | |
| 28 | +#include <processenv.h> | |
| 29 | +#include <shellapi.h> | |
| 30 | +#elif defined(__APPLE__) | |
| 31 | +#include <crt_externs.h> | |
| 32 | +#endif | |
| 33 | +// [CLI11:argv_inl_includes:end] | |
| 34 | + | |
| 35 | +namespace CLI { | |
| 36 | +// [CLI11:argv_inl_hpp:verbatim] | |
| 37 | + | |
| 38 | +namespace detail { | |
| 39 | + | |
| 40 | +#ifdef __APPLE__ | |
| 41 | +// Copy argc and argv as early as possible to avoid modification | |
| 42 | +static const std::vector<const char *> static_args = [] { | |
| 43 | + static const std::vector<std::string> static_args_as_strings = [] { | |
| 44 | + std::vector<std::string> args_as_strings; | |
| 45 | + int argc = *_NSGetArgc(); | |
| 46 | + char **argv = *_NSGetArgv(); | |
| 47 | + | |
| 48 | + args_as_strings.reserve(static_cast<size_t>(argc)); | |
| 49 | + for(size_t i = 0; i < static_cast<size_t>(argc); i++) { | |
| 50 | + args_as_strings.push_back(argv[i]); | |
| 51 | + } | |
| 52 | + | |
| 53 | + return args_as_strings; | |
| 54 | + }(); | |
| 55 | + | |
| 56 | + std::vector<const char *> static_args_result; | |
| 57 | + static_args_result.reserve(static_args_as_strings.size()); | |
| 58 | + | |
| 59 | + for(const auto &arg : static_args_as_strings) { | |
| 60 | + static_args_result.push_back(arg.data()); | |
| 61 | + } | |
| 62 | + | |
| 63 | + return static_args_result; | |
| 64 | +}(); | |
| 65 | +#endif | |
| 66 | + | |
| 67 | +/// Command-line arguments, as passed in to this executable, converted to utf-8 on Windows. | |
| 68 | +CLI11_INLINE const std::vector<const char *> &args() { | |
| 69 | + // This function uses initialization via lambdas extensively to take advantage of the thread safety of static | |
| 70 | + // variable initialization [stmt.dcl.3] | |
| 71 | + | |
| 72 | +#ifdef _WIN32 | |
| 73 | + static const std::vector<const char *> static_args = [] { | |
| 74 | + static const std::vector<std::string> static_args_as_strings = [] { | |
| 75 | + // On Windows, take arguments from GetCommandLineW and convert them to utf-8. | |
| 76 | + std::vector<std::string> args_as_strings; | |
| 77 | + int argc = 0; | |
| 78 | + | |
| 79 | + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; | |
| 80 | + // NOLINTBEGIN(*-avoid-c-arrays) | |
| 81 | + auto wargv = | |
| 82 | + std::unique_ptr<wchar_t *[], decltype(deleter)>(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); | |
| 83 | + // NOLINTEND(*-avoid-c-arrays) | |
| 84 | + | |
| 85 | + if(wargv == nullptr) { | |
| 86 | + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); | |
| 87 | + } | |
| 88 | + | |
| 89 | + args_as_strings.reserve(static_cast<size_t>(argc)); | |
| 90 | + for(size_t i = 0; i < static_cast<size_t>(argc); ++i) { | |
| 91 | + args_as_strings.push_back(narrow(wargv[i])); | |
| 92 | + } | |
| 93 | + | |
| 94 | + return args_as_strings; | |
| 95 | + }(); | |
| 96 | + | |
| 97 | + std::vector<const char *> static_args_result; | |
| 98 | + static_args_result.reserve(static_args_as_strings.size()); | |
| 99 | + | |
| 100 | + for(const auto &arg : static_args_as_strings) { | |
| 101 | + static_args_result.push_back(arg.data()); | |
| 102 | + } | |
| 103 | + | |
| 104 | + return static_args_result; | |
| 105 | + }(); | |
| 106 | + | |
| 107 | + return static_args; | |
| 108 | + | |
| 109 | +#elif defined(__APPLE__) | |
| 110 | + | |
| 111 | + return static_args; | |
| 112 | + | |
| 113 | +#else | |
| 114 | + static const std::vector<const char *> static_args = [] { | |
| 115 | + static const std::vector<char> static_cmdline = [] { | |
| 116 | + // On posix, retrieve arguments from /proc/self/cmdline, separated by null terminators. | |
| 117 | + std::vector<char> cmdline; | |
| 118 | + | |
| 119 | + auto deleter = [](FILE *f) { std::fclose(f); }; | |
| 120 | + std::unique_ptr<FILE, decltype(deleter)> fp_unique(std::fopen("/proc/self/cmdline", "r"), deleter); | |
| 121 | + FILE *fp = fp_unique.get(); | |
| 122 | + if(!fp) { | |
| 123 | + throw std::runtime_error("could not open /proc/self/cmdline for reading"); // LCOV_EXCL_LINE | |
| 124 | + } | |
| 125 | + | |
| 126 | + size_t size = 0; | |
| 127 | + while(std::feof(fp) == 0) { | |
| 128 | + cmdline.resize(size + 128); | |
| 129 | + size += std::fread(cmdline.data() + size, 1, 128, fp); | |
| 130 | + | |
| 131 | + if(std::ferror(fp) != 0) { | |
| 132 | + throw std::runtime_error("error during reading /proc/self/cmdline"); // LCOV_EXCL_LINE | |
| 133 | + } | |
| 134 | + } | |
| 135 | + cmdline.resize(size); | |
| 136 | + | |
| 137 | + return cmdline; | |
| 138 | + }(); | |
| 139 | + | |
| 140 | + std::size_t argc = static_cast<std::size_t>(std::count(static_cmdline.begin(), static_cmdline.end(), '\0')); | |
| 141 | + std::vector<const char *> static_args_result; | |
| 142 | + static_args_result.reserve(argc); | |
| 143 | + | |
| 144 | + for(auto it = static_cmdline.begin(); it != static_cmdline.end(); | |
| 145 | + it = std::find(it, static_cmdline.end(), '\0') + 1) { | |
| 146 | + static_args_result.push_back(static_cmdline.data() + (it - static_cmdline.begin())); | |
| 147 | + } | |
| 148 | + | |
| 149 | + return static_args_result; | |
| 150 | + }(); | |
| 151 | + | |
| 152 | + return static_args; | |
| 153 | +#endif | |
| 154 | +} | |
| 155 | + | |
| 156 | +} // namespace detail | |
| 157 | + | |
| 158 | +CLI11_INLINE const char *const *argv() { return detail::args().data(); } | |
| 159 | +CLI11_INLINE int argc() { return static_cast<int>(detail::args().size()); } | |
| 160 | + | |
| 161 | +// [CLI11:argv_inl_hpp:end] | |
| 162 | +} // namespace CLI | ... | ... |
include/CLI/impl/Encoding_inl.hpp
0 → 100644
| 1 | +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner | |
| 2 | +// under NSF AWARD 1414736 and by the respective contributors. | |
| 3 | +// All rights reserved. | |
| 4 | +// | |
| 5 | +// SPDX-License-Identifier: BSD-3-Clause | |
| 6 | + | |
| 7 | +#pragma once | |
| 8 | + | |
| 9 | +// This include is only needed for IDEs to discover symbols | |
| 10 | +#include <CLI/Encoding.hpp> | |
| 11 | +#include <CLI/Macros.hpp> | |
| 12 | + | |
| 13 | +// [CLI11:public_includes:set] | |
| 14 | +#include <array> | |
| 15 | +#include <clocale> | |
| 16 | +#include <cstdlib> | |
| 17 | +#include <cstring> | |
| 18 | +#include <cwchar> | |
| 19 | +#include <locale> | |
| 20 | +#include <stdexcept> | |
| 21 | +#include <string> | |
| 22 | +#include <type_traits> | |
| 23 | +#include <utility> | |
| 24 | +// [CLI11:public_includes:end] | |
| 25 | + | |
| 26 | +namespace CLI { | |
| 27 | +// [CLI11:encoding_inl_hpp:verbatim] | |
| 28 | + | |
| 29 | +namespace detail { | |
| 30 | + | |
| 31 | +#if !CLI11_HAS_CODECVT | |
| 32 | +/// Attempt to set one of the acceptable unicode locales for conversion | |
| 33 | +CLI11_INLINE void set_unicode_locale() { | |
| 34 | + static const std::array<const char *, 3> unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; | |
| 35 | + | |
| 36 | + for(const auto &locale_name : unicode_locales) { | |
| 37 | + if(std::setlocale(LC_ALL, locale_name) != nullptr) { | |
| 38 | + return; | |
| 39 | + } | |
| 40 | + } | |
| 41 | + throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8"); | |
| 42 | +} | |
| 43 | + | |
| 44 | +template <typename F> struct scope_guard_t { | |
| 45 | + F closure; | |
| 46 | + | |
| 47 | + explicit scope_guard_t(F closure_) : closure(closure_) {} | |
| 48 | + ~scope_guard_t() { closure(); } | |
| 49 | +}; | |
| 50 | + | |
| 51 | +template <typename F> CLI11_NODISCARD CLI11_INLINE scope_guard_t<F> scope_guard(F &&closure) { | |
| 52 | + return scope_guard_t<F>{std::forward<F>(closure)}; | |
| 53 | +} | |
| 54 | + | |
| 55 | +#endif // !CLI11_HAS_CODECVT | |
| 56 | + | |
| 57 | +CLI11_DIAGNOSTIC_PUSH | |
| 58 | +CLI11_DIAGNOSTIC_IGNORE_DEPRECATED | |
| 59 | + | |
| 60 | +CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) { | |
| 61 | +#if CLI11_HAS_CODECVT | |
| 62 | +#ifdef _WIN32 | |
| 63 | + return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(str, str + str_size); | |
| 64 | + | |
| 65 | +#else | |
| 66 | + return std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(str, str + str_size); | |
| 67 | + | |
| 68 | +#endif // _WIN32 | |
| 69 | +#else // CLI11_HAS_CODECVT | |
| 70 | + (void)str_size; | |
| 71 | + std::mbstate_t state = std::mbstate_t(); | |
| 72 | + const wchar_t *it = str; | |
| 73 | + | |
| 74 | + std::string old_locale = std::setlocale(LC_ALL, nullptr); | |
| 75 | + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); | |
| 76 | + set_unicode_locale(); | |
| 77 | + | |
| 78 | + std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state); | |
| 79 | + if(new_size == static_cast<std::size_t>(-1)) { | |
| 80 | + throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " + | |
| 81 | + std::to_string(it - str)); | |
| 82 | + } | |
| 83 | + std::string result(new_size, '\0'); | |
| 84 | + std::wcsrtombs(const_cast<char *>(result.data()), &str, new_size, &state); | |
| 85 | + | |
| 86 | + return result; | |
| 87 | + | |
| 88 | +#endif // CLI11_HAS_CODECVT | |
| 89 | +} | |
| 90 | + | |
| 91 | +CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { | |
| 92 | +#if CLI11_HAS_CODECVT | |
| 93 | +#ifdef _WIN32 | |
| 94 | + return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(str, str + str_size); | |
| 95 | + | |
| 96 | +#else | |
| 97 | + return std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(str, str + str_size); | |
| 98 | + | |
| 99 | +#endif // _WIN32 | |
| 100 | +#else // CLI11_HAS_CODECVT | |
| 101 | + (void)str_size; | |
| 102 | + std::mbstate_t state = std::mbstate_t(); | |
| 103 | + const char *it = str; | |
| 104 | + | |
| 105 | + std::string old_locale = std::setlocale(LC_ALL, nullptr); | |
| 106 | + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); | |
| 107 | + set_unicode_locale(); | |
| 108 | + | |
| 109 | + std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state); | |
| 110 | + if(new_size == static_cast<std::size_t>(-1)) { | |
| 111 | + throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " + | |
| 112 | + std::to_string(it - str)); | |
| 113 | + } | |
| 114 | + std::wstring result(new_size, L'\0'); | |
| 115 | + std::mbsrtowcs(const_cast<wchar_t *>(result.data()), &str, new_size, &state); | |
| 116 | + | |
| 117 | + return result; | |
| 118 | + | |
| 119 | +#endif // CLI11_HAS_CODECVT | |
| 120 | +} | |
| 121 | + | |
| 122 | +CLI11_DIAGNOSTIC_POP | |
| 123 | + | |
| 124 | +} // namespace detail | |
| 125 | + | |
| 126 | +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); } | |
| 127 | +CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); } | |
| 128 | +// Flawfinder: ignore | |
| 129 | +CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); } | |
| 130 | + | |
| 131 | +CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); } | |
| 132 | +CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); } | |
| 133 | +// Flawfinder: ignore | |
| 134 | +CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); } | |
| 135 | + | |
| 136 | +#ifdef CLI11_CPP17 | |
| 137 | +CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); } | |
| 138 | +CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); } | |
| 139 | +#endif // CLI11_CPP17 | |
| 140 | + | |
| 141 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 | |
| 142 | +CLI11_INLINE std::filesystem::path to_path(std::string_view str) { | |
| 143 | + return std::filesystem::path{ | |
| 144 | +#ifdef _WIN32 | |
| 145 | + widen(str) | |
| 146 | +#else | |
| 147 | + str | |
| 148 | +#endif // _WIN32 | |
| 149 | + }; | |
| 150 | +} | |
| 151 | +#endif // CLI11_HAS_FILESYSTEM | |
| 152 | + | |
| 153 | +// [CLI11:encoding_inl_hpp:end] | |
| 154 | +} // namespace CLI | ... | ... |
include/CLI/impl/SlimWindowsH.hpp
0 → 100644
| 1 | +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner | |
| 2 | +// under NSF AWARD 1414736 and by the respective contributors. | |
| 3 | +// All rights reserved. | |
| 4 | +// | |
| 5 | +// SPDX-License-Identifier: BSD-3-Clause | |
| 6 | + | |
| 7 | +#pragma once | |
| 8 | +// [CLI11:slim_windows_h_hpp:verbatim] | |
| 9 | +#ifdef _WIN32 | |
| 10 | +// The most slimmed-down version of Windows.h. | |
| 11 | +#define WIN32_LEAN_AND_MEAN | |
| 12 | +#define WIN32_EXTRA_LEAN | |
| 13 | + | |
| 14 | +// Enable components based on necessity. | |
| 15 | +#define NOGDICAPMASKS | |
| 16 | +#define NOVIRTUALKEYCODES | |
| 17 | +#define NOWINMESSAGES | |
| 18 | +#define NOWINSTYLES | |
| 19 | +#define NOSYSMETRICS | |
| 20 | +#define NOMENUS | |
| 21 | +#define NOICONS | |
| 22 | +#define NOKEYSTATES | |
| 23 | +#define NOSYSCOMMANDS | |
| 24 | +#define NORASTEROPS | |
| 25 | +#define NOSHOWWINDOW | |
| 26 | +#define OEMRESOURCE | |
| 27 | +#define NOATOM | |
| 28 | +#define NOCLIPBOARD | |
| 29 | +#define NOCOLOR | |
| 30 | +#define NOCTLMGR | |
| 31 | +#define NODRAWTEXT | |
| 32 | +#define NOGDI | |
| 33 | +#define NOKERNEL | |
| 34 | +#define NOUSER | |
| 35 | +#define NONLS | |
| 36 | +#define NOMB | |
| 37 | +#define NOMEMMGR | |
| 38 | +#define NOMETAFILE | |
| 39 | +#define NOMINMAX | |
| 40 | +#define NOMSG | |
| 41 | +#define NOOPENFILE | |
| 42 | +#define NOSCROLL | |
| 43 | +#define NOSERVICE | |
| 44 | +#define NOSOUND | |
| 45 | +#define NOTEXTMETRIC | |
| 46 | +#define NOWH | |
| 47 | +#define NOWINOFFSETS | |
| 48 | +#define NOCOMM | |
| 49 | +#define NOKANJI | |
| 50 | +#define NOHELP | |
| 51 | +#define NOPROFILER | |
| 52 | +#define NODEFERWINDOWPOS | |
| 53 | +#define NOMCX | |
| 54 | + | |
| 55 | +#include "Windows.h" | |
| 56 | + | |
| 57 | +#undef WIN32_LEAN_AND_MEAN | |
| 58 | +#undef WIN32_EXTRA_LEAN | |
| 59 | + | |
| 60 | +#undef NOGDICAPMASKS | |
| 61 | +#undef NOVIRTUALKEYCODES | |
| 62 | +#undef NOWINMESSAGES | |
| 63 | +#undef NOWINSTYLES | |
| 64 | +#undef NOSYSMETRICS | |
| 65 | +#undef NOMENUS | |
| 66 | +#undef NOICONS | |
| 67 | +#undef NOKEYSTATES | |
| 68 | +#undef NOSYSCOMMANDS | |
| 69 | +#undef NORASTEROPS | |
| 70 | +#undef NOSHOWWINDOW | |
| 71 | +#undef OEMRESOURCE | |
| 72 | +#undef NOATOM | |
| 73 | +#undef NOCLIPBOARD | |
| 74 | +#undef NOCOLOR | |
| 75 | +#undef NOCTLMGR | |
| 76 | +#undef NODRAWTEXT | |
| 77 | +#undef NOGDI | |
| 78 | +#undef NOKERNEL | |
| 79 | +#undef NOUSER | |
| 80 | +#undef NONLS | |
| 81 | +#undef NOMB | |
| 82 | +#undef NOMEMMGR | |
| 83 | +#undef NOMETAFILE | |
| 84 | +#undef NOMINMAX | |
| 85 | +#undef NOMSG | |
| 86 | +#undef NOOPENFILE | |
| 87 | +#undef NOSCROLL | |
| 88 | +#undef NOSERVICE | |
| 89 | +#undef NOSOUND | |
| 90 | +#undef NOTEXTMETRIC | |
| 91 | +#undef NOWH | |
| 92 | +#undef NOWINOFFSETS | |
| 93 | +#undef NOCOMM | |
| 94 | +#undef NOKANJI | |
| 95 | +#undef NOHELP | |
| 96 | +#undef NOPROFILER | |
| 97 | +#undef NODEFERWINDOWPOS | |
| 98 | +#undef NOMCX | |
| 99 | + | |
| 100 | +#endif // _WIN32 | |
| 101 | +// [CLI11:slim_windows_h_hpp:end] | ... | ... |
include/CLI/impl/Validators_inl.hpp
| ... | ... | @@ -8,6 +8,7 @@ |
| 8 | 8 | |
| 9 | 9 | #include <CLI/Validators.hpp> |
| 10 | 10 | |
| 11 | +#include <CLI/Encoding.hpp> | |
| 11 | 12 | #include <CLI/Macros.hpp> |
| 12 | 13 | #include <CLI/StringTools.hpp> |
| 13 | 14 | #include <CLI/TypeTools.hpp> |
| ... | ... | @@ -127,7 +128,7 @@ namespace detail { |
| 127 | 128 | #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 |
| 128 | 129 | CLI11_INLINE path_type check_path(const char *file) noexcept { |
| 129 | 130 | std::error_code ec; |
| 130 | - auto stat = std::filesystem::status(file, ec); | |
| 131 | + auto stat = std::filesystem::status(to_path(file), ec); | |
| 131 | 132 | if(ec) { |
| 132 | 133 | return path_type::nonexistent; |
| 133 | 134 | } | ... | ... |
src/CMakeLists.txt
| ... | ... | @@ -13,7 +13,9 @@ set(CLI11_headers |
| 13 | 13 | ${CLI11_headerLoc}/StringTools.hpp |
| 14 | 14 | ${CLI11_headerLoc}/TypeTools.hpp |
| 15 | 15 | ${CLI11_headerLoc}/Validators.hpp |
| 16 | - ${CLI11_headerLoc}/Version.hpp) | |
| 16 | + ${CLI11_headerLoc}/Version.hpp | |
| 17 | + ${CLI11_headerLoc}/Encoding.hpp | |
| 18 | + ${CLI11_headerLoc}/Argv.hpp) | |
| 17 | 19 | |
| 18 | 20 | set(CLI11_implLoc "${PROJECT_SOURCE_DIR}/include/CLI/impl") |
| 19 | 21 | |
| ... | ... | @@ -24,7 +26,10 @@ set(CLI11_impl_headers |
| 24 | 26 | ${CLI11_implLoc}/Option_inl.hpp |
| 25 | 27 | ${CLI11_implLoc}/Split_inl.hpp |
| 26 | 28 | ${CLI11_implLoc}/StringTools_inl.hpp |
| 27 | - ${CLI11_implLoc}/Validators_inl.hpp) | |
| 29 | + ${CLI11_implLoc}/Validators_inl.hpp | |
| 30 | + ${CLI11_implLoc}/Encoding_inl.hpp | |
| 31 | + ${CLI11_implLoc}/Argv_inl.hpp | |
| 32 | + ${CLI11_implLoc}/SlimWindowsH.hpp) | |
| 28 | 33 | |
| 29 | 34 | set(CLI11_library_headers ${CLI11_headerLoc}/CLI.hpp ${CLI11_headerLoc}/Timer.hpp) |
| 30 | 35 | ... | ... |
src/Precompile.cpp
| ... | ... | @@ -5,7 +5,9 @@ |
| 5 | 5 | // SPDX-License-Identifier: BSD-3-Clause |
| 6 | 6 | |
| 7 | 7 | #include <CLI/impl/App_inl.hpp> |
| 8 | +#include <CLI/impl/Argv_inl.hpp> | |
| 8 | 9 | #include <CLI/impl/Config_inl.hpp> |
| 10 | +#include <CLI/impl/Encoding_inl.hpp> | |
| 9 | 11 | #include <CLI/impl/Formatter_inl.hpp> |
| 10 | 12 | #include <CLI/impl/Option_inl.hpp> |
| 11 | 13 | #include <CLI/impl/Split_inl.hpp> | ... | ... |
tests/AppTest.cpp
| ... | ... | @@ -7,6 +7,7 @@ |
| 7 | 7 | #include "app_helper.hpp" |
| 8 | 8 | #include <cmath> |
| 9 | 9 | |
| 10 | +#include <array> | |
| 10 | 11 | #include <complex> |
| 11 | 12 | #include <cstdint> |
| 12 | 13 | #include <cstdlib> |
| ... | ... | @@ -261,6 +262,28 @@ TEST_CASE_METHOD(TApp, "OneString", "[app]") { |
| 261 | 262 | CHECK("mystring" == str); |
| 262 | 263 | } |
| 263 | 264 | |
| 265 | +TEST_CASE_METHOD(TApp, "OneWideString", "[app]") { | |
| 266 | + std::wstring str; | |
| 267 | + app.add_option("-s,--string", str); | |
| 268 | + args = {"--string", "mystring"}; | |
| 269 | + run(); | |
| 270 | + CHECK(app.count("-s") == 1u); | |
| 271 | + CHECK(app.count("--string") == 1u); | |
| 272 | + CHECK(L"mystring" == str); | |
| 273 | +} | |
| 274 | + | |
| 275 | +TEST_CASE_METHOD(TApp, "OneStringWideInput", "[app][unicode]") { | |
| 276 | + std::string str; | |
| 277 | + app.add_option("-s,--string", str); | |
| 278 | + | |
| 279 | + std::array<const wchar_t *, 3> cmdline{{L"app", L"--string", L"mystring"}}; | |
| 280 | + app.parse(static_cast<int>(cmdline.size()), cmdline.data()); | |
| 281 | + | |
| 282 | + CHECK(app.count("-s") == 1u); | |
| 283 | + CHECK(app.count("--string") == 1u); | |
| 284 | + CHECK("mystring" == str); | |
| 285 | +} | |
| 286 | + | |
| 264 | 287 | TEST_CASE_METHOD(TApp, "OneStringWindowsStyle", "[app]") { |
| 265 | 288 | std::string str; |
| 266 | 289 | app.add_option("-s,--string", str); |
| ... | ... | @@ -282,6 +305,16 @@ TEST_CASE_METHOD(TApp, "OneStringSingleStringInput", "[app]") { |
| 282 | 305 | CHECK("mystring" == str); |
| 283 | 306 | } |
| 284 | 307 | |
| 308 | +TEST_CASE_METHOD(TApp, "OneStringSingleWideStringInput", "[app][unicode]") { | |
| 309 | + std::string str; | |
| 310 | + app.add_option("-s,--string", str); | |
| 311 | + | |
| 312 | + app.parse(L"--string mystring"); | |
| 313 | + CHECK(app.count("-s") == 1u); | |
| 314 | + CHECK(app.count("--string") == 1u); | |
| 315 | + CHECK("mystring" == str); | |
| 316 | +} | |
| 317 | + | |
| 285 | 318 | TEST_CASE_METHOD(TApp, "OneStringEqualVersion", "[app]") { |
| 286 | 319 | std::string str; |
| 287 | 320 | app.add_option("-s,--string", str); |
| ... | ... | @@ -2463,3 +2496,21 @@ TEST_CASE("C20_compile", "simple") { |
| 2463 | 2496 | app.parse("--flag"); |
| 2464 | 2497 | CHECK_FALSE(flag->empty()); |
| 2465 | 2498 | } |
| 2499 | + | |
| 2500 | +// #14 | |
| 2501 | +TEST_CASE("System Args", "[app]") { | |
| 2502 | + const char *commandline = CLI11_SYSTEM_ARGS_EXE " 1234 false \"hello world\""; | |
| 2503 | + int retval = std::system(commandline); | |
| 2504 | + | |
| 2505 | + if(retval == -1) { | |
| 2506 | + FAIL("Executable '" << commandline << "' reported different argc count"); | |
| 2507 | + } | |
| 2508 | + | |
| 2509 | + if(retval > 0) { | |
| 2510 | + FAIL("Executable '" << commandline << "' reported different argv at index " << (retval - 1)); | |
| 2511 | + } | |
| 2512 | + | |
| 2513 | + if(retval != 0) { | |
| 2514 | + FAIL("Executable '" << commandline << "' failed with an unknown return code"); | |
| 2515 | + } | |
| 2516 | +} | ... | ... |
tests/CMakeLists.txt
| ... | ... | @@ -49,7 +49,8 @@ set(CLI11_TESTS |
| 49 | 49 | StringParseTest |
| 50 | 50 | ComplexTypeTest |
| 51 | 51 | TrueFalseTest |
| 52 | - OptionGroupTest) | |
| 52 | + OptionGroupTest | |
| 53 | + EncodingTest) | |
| 53 | 54 | |
| 54 | 55 | if(WIN32) |
| 55 | 56 | list(APPEND CLI11_TESTS WindowsTest) |
| ... | ... | @@ -89,6 +90,40 @@ else() |
| 89 | 90 | target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") |
| 90 | 91 | endif() |
| 91 | 92 | |
| 93 | +# Add special target that copies the data directory for tests | |
| 94 | +file( | |
| 95 | + GLOB_RECURSE DATA_FILES | |
| 96 | + LIST_DIRECTORIES false | |
| 97 | + RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" | |
| 98 | + "${CMAKE_CURRENT_SOURCE_DIR}/data/*") | |
| 99 | + | |
| 100 | +foreach(DATA_FILE IN LISTS DATA_FILES) | |
| 101 | + add_custom_command( | |
| 102 | + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}" | |
| 103 | + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/${DATA_FILE}" | |
| 104 | + "${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}" | |
| 105 | + MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${DATA_FILE}" | |
| 106 | + VERBATIM) | |
| 107 | + target_sources(catch_main PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}") | |
| 108 | +endforeach() | |
| 109 | + | |
| 110 | +# Build dependent applications which are launched from test code | |
| 111 | +set(CLI11_DEPENDENT_APPLICATIONS system_args) | |
| 112 | + | |
| 113 | +foreach(APP IN LISTS CLI11_DEPENDENT_APPLICATIONS) | |
| 114 | + add_executable(${APP} applications/${APP}.cpp) | |
| 115 | + target_include_directories(${APP} PRIVATE ${CMAKE_SOURCE_DIR}/include) | |
| 116 | + add_dependencies(catch_main ${APP}) | |
| 117 | +endforeach() | |
| 118 | + | |
| 119 | +function(add_dependent_application_definitions TARGET) | |
| 120 | + foreach(APP IN LISTS CLI11_DEPENDENT_APPLICATIONS) | |
| 121 | + string(TOUPPER ${APP} APP_UPPERCASE) | |
| 122 | + target_compile_definitions(${TARGET} | |
| 123 | + PRIVATE CLI11_${APP_UPPERCASE}_EXE="$<TARGET_FILE:${APP}>") | |
| 124 | + endforeach() | |
| 125 | +endfunction() | |
| 126 | + | |
| 92 | 127 | # Target must already exist |
| 93 | 128 | macro(add_catch_test TESTNAME) |
| 94 | 129 | target_link_libraries(${TESTNAME} PUBLIC catch_main) |
| ... | ... | @@ -108,6 +143,8 @@ foreach(T IN LISTS CLI11_TESTS) |
| 108 | 143 | set_property(SOURCE ${T}.cpp PROPERTY LANGUAGE CUDA) |
| 109 | 144 | endif() |
| 110 | 145 | add_executable(${T} ${T}.cpp) |
| 146 | + | |
| 147 | + add_dependent_application_definitions(${T}) | |
| 111 | 148 | add_sanitizers(${T}) |
| 112 | 149 | if(NOT CLI11_CUDA_TESTS) |
| 113 | 150 | target_link_libraries(${T} PRIVATE CLI11_warnings) |
| ... | ... | @@ -117,6 +154,7 @@ foreach(T IN LISTS CLI11_TESTS) |
| 117 | 154 | |
| 118 | 155 | if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) |
| 119 | 156 | add_executable(${T}_Single ${T}.cpp) |
| 157 | + add_dependent_application_definitions(${T}_Single) | |
| 120 | 158 | target_link_libraries(${T}_Single PRIVATE CLI11_SINGLE) |
| 121 | 159 | add_catch_test(${T}_Single) |
| 122 | 160 | set_property(TARGET ${T}_Single PROPERTY FOLDER "Tests Single File") |
| ... | ... | @@ -125,6 +163,7 @@ endforeach() |
| 125 | 163 | |
| 126 | 164 | foreach(T IN LISTS CLI11_MULTIONLY_TESTS) |
| 127 | 165 | add_executable(${T} ${T}.cpp) |
| 166 | + add_dependent_application_definitions(${T}) | |
| 128 | 167 | add_sanitizers(${T}) |
| 129 | 168 | target_link_libraries(${T} PUBLIC CLI11) |
| 130 | 169 | add_catch_test(${T}) | ... | ... |
tests/EncodingTest.cpp
0 → 100644
| 1 | +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner | |
| 2 | +// under NSF AWARD 1414736 and by the respective contributors. | |
| 3 | +// All rights reserved. | |
| 4 | +// | |
| 5 | +// SPDX-License-Identifier: BSD-3-Clause | |
| 6 | + | |
| 7 | +#include "app_helper.hpp" | |
| 8 | + | |
| 9 | +#include <array> | |
| 10 | +#include <string> | |
| 11 | + | |
| 12 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 | |
| 13 | +#include <filesystem> | |
| 14 | +#endif // CLI11_HAS_FILESYSTEM | |
| 15 | + | |
| 16 | +// "abcd" | |
| 17 | +static const std::string abcd_str = "abcd"; // NOLINT(runtime/string) | |
| 18 | +static const std::wstring abcd_wstr = L"abcd"; // NOLINT(runtime/string) | |
| 19 | + | |
| 20 | +// "𓂀𓂀𓂀" - 4-byte utf8 characters | |
| 21 | +static const std::array<uint8_t, 12 + 1> egypt_utf8_codeunits{ | |
| 22 | + {0xF0, 0x93, 0x82, 0x80, 0xF0, 0x93, 0x82, 0x80, 0xF0, 0x93, 0x82, 0x80}}; | |
| 23 | +static const std::string egypt_str(reinterpret_cast<const char *>(egypt_utf8_codeunits.data())); | |
| 24 | + | |
| 25 | +#ifdef _WIN32 | |
| 26 | +static const std::array<uint16_t, 6 + 1> egypt_utf16_codeunits{{0xD80C, 0xDC80, 0xD80C, 0xDC80, 0xD80C, 0xDC80}}; | |
| 27 | +static const std::wstring egypt_wstr(reinterpret_cast<const wchar_t *>(egypt_utf16_codeunits.data())); | |
| 28 | + | |
| 29 | +#else | |
| 30 | +static const std::array<uint32_t, 3 + 1> egypt_utf32_codeunits{{0x00013080, 0x00013080, 0x00013080}}; | |
| 31 | +static const std::wstring egypt_wstr(reinterpret_cast<const wchar_t *>(egypt_utf32_codeunits.data())); | |
| 32 | + | |
| 33 | +#endif | |
| 34 | + | |
| 35 | +// "Hello Halló Привет 你好 👩🚀❤️" - many languages and complex emojis | |
| 36 | +static const std::array<uint8_t, 50 + 1> hello_utf8_codeunits{ | |
| 37 | + {0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x48, 0x61, 0x6c, 0x6c, 0xc3, 0xb3, 0x20, 0xd0, 0x9f, 0xd1, 0x80, | |
| 38 | + 0xd0, 0xb8, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x82, 0x20, 0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd, 0x20, 0xf0, | |
| 39 | + 0x9f, 0x91, 0xa9, 0xe2, 0x80, 0x8d, 0xf0, 0x9f, 0x9a, 0x80, 0xe2, 0x9d, 0xa4, 0xef, 0xb8, 0x8f}}; | |
| 40 | +static const std::string hello_str(reinterpret_cast<const char *>(hello_utf8_codeunits.data())); | |
| 41 | + | |
| 42 | +#ifdef _WIN32 | |
| 43 | +static const std::array<uint16_t, 29 + 1> hello_utf16_codeunits{ | |
| 44 | + {0x0048, 0x0065, 0x006c, 0x006c, 0x006f, 0x0020, 0x0048, 0x0061, 0x006c, 0x006c, | |
| 45 | + 0x00f3, 0x0020, 0x041f, 0x0440, 0x0438, 0x0432, 0x0435, 0x0442, 0x0020, 0x4f60, | |
| 46 | + 0x597d, 0x0020, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xde80, 0x2764, 0xfe0f}}; | |
| 47 | +static const std::wstring hello_wstr(reinterpret_cast<const wchar_t *>(hello_utf16_codeunits.data())); | |
| 48 | + | |
| 49 | +#else | |
| 50 | +static const std::array<uint32_t, 27 + 1> hello_utf32_codeunits{ | |
| 51 | + {0x00000048, 0x00000065, 0x0000006c, 0x0000006c, 0x0000006f, 0x00000020, 0x00000048, 0x00000061, 0x0000006c, | |
| 52 | + 0x0000006c, 0x000000f3, 0x00000020, 0x0000041f, 0x00000440, 0x00000438, 0x00000432, 0x00000435, 0x00000442, | |
| 53 | + 0x00000020, 0x00004f60, 0x0000597d, 0x00000020, 0x0001f469, 0x0000200d, 0x0001f680, 0x00002764, 0x0000fe0f}}; | |
| 54 | +static const std::wstring hello_wstr(reinterpret_cast<const wchar_t *>(hello_utf32_codeunits.data())); | |
| 55 | + | |
| 56 | +#endif | |
| 57 | + | |
| 58 | +// #14 | |
| 59 | +TEST_CASE("Encoding: Widen", "[unicode]") { | |
| 60 | + using CLI::widen; | |
| 61 | + | |
| 62 | + CHECK(abcd_wstr == widen(abcd_str)); | |
| 63 | + CHECK(egypt_wstr == widen(egypt_str)); | |
| 64 | + CHECK(hello_wstr == widen(hello_str)); | |
| 65 | + | |
| 66 | + CHECK(hello_wstr == widen(hello_str.c_str())); | |
| 67 | + CHECK(hello_wstr == widen(hello_str.c_str(), hello_str.size())); | |
| 68 | + | |
| 69 | +#ifdef CLI11_CPP17 | |
| 70 | + CHECK(hello_wstr == widen(std::string_view{hello_str})); | |
| 71 | +#endif // CLI11_CPP17 | |
| 72 | +} | |
| 73 | + | |
| 74 | +// #14 | |
| 75 | +TEST_CASE("Encoding: Narrow", "[unicode]") { | |
| 76 | + using CLI::narrow; | |
| 77 | + | |
| 78 | + CHECK(abcd_str == narrow(abcd_wstr)); | |
| 79 | + CHECK(egypt_str == narrow(egypt_wstr)); | |
| 80 | + CHECK(hello_str == narrow(hello_wstr)); | |
| 81 | + | |
| 82 | + CHECK(hello_str == narrow(hello_wstr.c_str())); | |
| 83 | + CHECK(hello_str == narrow(hello_wstr.c_str(), hello_wstr.size())); | |
| 84 | + | |
| 85 | +#ifdef CLI11_CPP17 | |
| 86 | + CHECK(hello_str == narrow(std::wstring_view{hello_wstr})); | |
| 87 | +#endif // CLI11_CPP17 | |
| 88 | +} | |
| 89 | + | |
| 90 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 | |
| 91 | +// #14 | |
| 92 | +TEST_CASE("Encoding: to_path roundtrip", "[unicode]") { | |
| 93 | + using std::filesystem::path; | |
| 94 | + | |
| 95 | +#ifdef _WIN32 | |
| 96 | + std::wstring native_str = CLI::widen(hello_str); | |
| 97 | +#else | |
| 98 | + std::string native_str = hello_str; | |
| 99 | +#endif // _WIN32 | |
| 100 | + | |
| 101 | + CHECK(CLI::to_path(hello_str).native() == native_str); | |
| 102 | +} | |
| 103 | + | |
| 104 | +#endif // CLI11_HAS_FILESYSTEM | ... | ... |
tests/app_helper.hpp
| ... | ... | @@ -13,6 +13,9 @@ |
| 13 | 13 | #endif |
| 14 | 14 | |
| 15 | 15 | #include "catch.hpp" |
| 16 | +#include <array> | |
| 17 | +#include <fstream> | |
| 18 | +#include <iomanip> | |
| 16 | 19 | #include <iostream> |
| 17 | 20 | #include <string> |
| 18 | 21 | #include <utility> |
| ... | ... | @@ -67,3 +70,54 @@ inline void unset_env(std::string name) { |
| 67 | 70 | unsetenv(name.c_str()); |
| 68 | 71 | #endif |
| 69 | 72 | } |
| 73 | + | |
| 74 | +CLI11_INLINE void check_identical_files(const char *path1, const char *path2) { | |
| 75 | + std::string err1 = CLI::ExistingFile(path1); | |
| 76 | + if(!err1.empty()) { | |
| 77 | + FAIL("Could not open " << path1 << ": " << err1); | |
| 78 | + } | |
| 79 | + | |
| 80 | + std::string err2 = CLI::ExistingFile(path2); | |
| 81 | + if(!err2.empty()) { | |
| 82 | + FAIL("Could not open " << path2 << ": " << err2); | |
| 83 | + } | |
| 84 | + | |
| 85 | + // open files at the end to compare size first | |
| 86 | + std::ifstream file1(path1, std::ifstream::ate | std::ifstream::binary); | |
| 87 | + std::ifstream file2(path2, std::ifstream::ate | std::ifstream::binary); | |
| 88 | + | |
| 89 | + if(!file1.good()) { | |
| 90 | + FAIL("File " << path1 << " is corrupted"); | |
| 91 | + } | |
| 92 | + | |
| 93 | + if(!file2.good()) { | |
| 94 | + FAIL("File " << path2 << " is corrupted"); | |
| 95 | + } | |
| 96 | + | |
| 97 | + if(file1.tellg() != file2.tellg()) { | |
| 98 | + FAIL("Different file sizes:\n " << file1.tellg() << " bytes in " << path1 << "\n " << file2.tellg() | |
| 99 | + << " bytes in " << path2); | |
| 100 | + } | |
| 101 | + | |
| 102 | + // rewind files | |
| 103 | + file1.seekg(0); | |
| 104 | + file2.seekg(0); | |
| 105 | + | |
| 106 | + std::array<uint8_t, 10240> buffer1; | |
| 107 | + std::array<uint8_t, 10240> buffer2; | |
| 108 | + | |
| 109 | + for(size_t ibuffer = 0; file1.good(); ++ibuffer) { | |
| 110 | + // Flawfinder: ignore | |
| 111 | + file1.read(reinterpret_cast<char *>(buffer1.data()), static_cast<std::streamsize>(buffer1.size())); | |
| 112 | + // Flawfinder: ignore | |
| 113 | + file2.read(reinterpret_cast<char *>(buffer2.data()), static_cast<std::streamsize>(buffer2.size())); | |
| 114 | + | |
| 115 | + for(size_t i = 0; i < static_cast<size_t>(file1.gcount()); ++i) { | |
| 116 | + if(buffer1[i] != buffer2[i]) { | |
| 117 | + FAIL(std::hex << std::setfill('0') << "Different bytes at position " << (ibuffer * 10240 + i) << ":\n " | |
| 118 | + << "0x" << std::setw(2) << static_cast<int>(buffer1[i]) << " in " << path1 << "\n " | |
| 119 | + << "0x" << std::setw(2) << static_cast<int>(buffer2[i]) << " in " << path2); | |
| 120 | + } | |
| 121 | + } | |
| 122 | + } | |
| 123 | +} | ... | ... |
tests/applications/system_args.cpp
0 → 100644
| 1 | +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner | |
| 2 | +// under NSF AWARD 1414736 and by the respective contributors. | |
| 3 | +// All rights reserved. | |
| 4 | +// | |
| 5 | +// SPDX-License-Identifier: BSD-3-Clause | |
| 6 | + | |
| 7 | +#include <CLI/CLI.hpp> | |
| 8 | +#include <cstring> | |
| 9 | + | |
| 10 | +int main(int argc, char **argv) { | |
| 11 | + if(argc != CLI::argc()) { | |
| 12 | + return -1; | |
| 13 | + } | |
| 14 | + | |
| 15 | + for(int i = 0; i < argc; i++) { | |
| 16 | + if(std::strcmp(argv[i], CLI::argv()[i]) != 0) { | |
| 17 | + return i + 1; | |
| 18 | + } | |
| 19 | + } | |
| 20 | + | |
| 21 | + return 0; | |
| 22 | +} | ... | ... |
tests/data/unicode.txt
0 → 100644
| 1 | +Hello Halló Привет 你好 👩🚀❤️ | ... | ... |
tests/meson.build
| ... | ... | @@ -57,6 +57,20 @@ testnames = [ |
| 57 | 57 | ['link_test_2', {'link_with': link_test_lib}], |
| 58 | 58 | ] |
| 59 | 59 | |
| 60 | +dependent_applications = [ | |
| 61 | + 'system_args' | |
| 62 | +] | |
| 63 | +dependent_applications_definitions = [] | |
| 64 | +#dependent_applications_targets = [] | |
| 65 | +foreach app: dependent_applications | |
| 66 | + app_target = executable(app, 'applications'/app + '.cpp', | |
| 67 | + build_by_default: false | |
| 68 | + ) | |
| 69 | + | |
| 70 | + #dependent_applications_targets += dependency(app_target) | |
| 71 | + dependent_applications_definitions += '-DCLI11_@0@_EXE="@1@"'.format(app.to_upper(), app_target) | |
| 72 | +endforeach | |
| 73 | + | |
| 60 | 74 | if host_machine.system() == 'windows' |
| 61 | 75 | testnames += [['WindowsTest', {}]] |
| 62 | 76 | endif |
| ... | ... | @@ -69,7 +83,7 @@ foreach n: testnames |
| 69 | 83 | name = n[0] |
| 70 | 84 | kwargs = n[1] |
| 71 | 85 | t = executable(name, name + '.cpp', |
| 72 | - cpp_args: kwargs.get('cpp_args', []), | |
| 86 | + cpp_args: kwargs.get('cpp_args', []) + dependent_applications_definitions, | |
| 73 | 87 | build_by_default: false, |
| 74 | 88 | dependencies: [testdep] + kwargs.get('dependencies', []), |
| 75 | 89 | link_with: kwargs.get('link_with', []) | ... | ... |