Commit a227cd10fc6dd2905965bafc2d3aa9cb9d910ce5

Authored by Andrey Zhukov
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>
.github/workflows/tests.yml
@@ -128,9 +128,9 @@ jobs: @@ -128,9 +128,9 @@ jobs:
128 - name: Build 128 - name: Build
129 run: meson compile -C build-meson 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 steps: 134 steps:
135 - uses: actions/checkout@v3 135 - uses: actions/checkout@v3
136 136
@@ -175,6 +175,12 @@ jobs: @@ -175,6 +175,12 @@ jobs:
175 cmake-version: "3.10" 175 cmake-version: "3.10"
176 if: success() || failure() 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 - name: Check CMake 3.11 (full) 184 - name: Check CMake 3.11 (full)
179 uses: ./.github/actions/quick_cmake 185 uses: ./.github/actions/quick_cmake
180 with: 186 with:
@@ -212,6 +218,12 @@ jobs: @@ -212,6 +218,12 @@ jobs:
212 cmake-version: "3.16" 218 cmake-version: "3.16"
213 if: success() || failure() 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 - name: Check CMake 3.17 227 - name: Check CMake 3.17
216 uses: ./.github/actions/quick_cmake 228 uses: ./.github/actions/quick_cmake
217 with: 229 with:
.gitignore
@@ -6,8 +6,10 @@ a.out* @@ -6,8 +6,10 @@ a.out*
6 /CMakeFiles/* 6 /CMakeFiles/*
7 /cmake_install.cmake 7 /cmake_install.cmake
8 /*.kdev4 8 /*.kdev4
  9 +/.vscode
9 /html/* 10 /html/*
10 !/meson.build 11 !/meson.build
  12 +/CMakeUserPresets.json
11 13
12 /node_modules/* 14 /node_modules/*
13 /package.json 15 /package.json
CLI11.hpp.in
@@ -40,10 +40,24 @@ @@ -40,10 +40,24 @@
40 40
41 {macros_hpp} 41 {macros_hpp}
42 42
  43 +{slim_windows_h_hpp}
  44 +
43 {validators_hpp_filesystem} 45 {validators_hpp_filesystem}
44 46
  47 +{encoding_includes}
  48 +
  49 +{argv_inl_includes}
  50 +
45 namespace {namespace} {{ 51 namespace {namespace} {{
46 52
  53 +{encoding_hpp}
  54 +
  55 +{encoding_inl_hpp}
  56 +
  57 +{argv_hpp}
  58 +
  59 +{argv_inl_hpp}
  60 +
47 {string_tools_hpp} 61 {string_tools_hpp}
48 62
49 {string_tools_inl_hpp} 63 {string_tools_inl_hpp}
CPPLINT.cfg
@@ -4,6 +4,7 @@ linelength=120 # As in .clang-format @@ -4,6 +4,7 @@ linelength=120 # As in .clang-format
4 # Unused filters 4 # Unused filters
5 filter=-build/c++11 # Reports e.g. chrono and thread, which overlap with Chromium's API. Not applicable to general C++ projects. 5 filter=-build/c++11 # Reports e.g. chrono and thread, which overlap with Chromium's API. Not applicable to general C++ projects.
6 filter=-build/include_order # Requires unusual include order that encourages creating not self-contained headers 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 filter=-readability/nolint # Conflicts with clang-tidy 8 filter=-readability/nolint # Conflicts with clang-tidy
8 filter=-readability/check # Catch uses CHECK(a == b) (Tests only) 9 filter=-readability/check # Catch uses CHECK(a == b) (Tests only)
9 filter=-build/namespaces # Currently using it for one test (Tests only) 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,6 +50,7 @@ set with a simple and intuitive interface.
50 - [Formatting](#formatting) 50 - [Formatting](#formatting)
51 - [Subclassing](#subclassing) 51 - [Subclassing](#subclassing)
52 - [How it works](#how-it-works) 52 - [How it works](#how-it-works)
  53 + - [Unicode support](#unicode-support)
53 - [Utilities](#utilities) 54 - [Utilities](#utilities)
54 - [Other libraries](#other-libraries) 55 - [Other libraries](#other-libraries)
55 - [API](#api) 56 - [API](#api)
@@ -164,9 +165,6 @@ this library: @@ -164,9 +165,6 @@ this library:
164 option to disable it). 165 option to disable it).
165 - Autocomplete: This might eventually be added to both Plumbum and CLI11, but it 166 - Autocomplete: This might eventually be added to both Plumbum and CLI11, but it
166 is not supported yet. 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 ## Install 169 ## Install
172 170
@@ -278,13 +276,13 @@ To set up, add options, and run, your main function will look something like @@ -278,13 +276,13 @@ To set up, add options, and run, your main function will look something like
278 this: 276 this:
279 277
280 ```cpp 278 ```cpp
281 -int main(int argc, char** argv) { 279 +int main() {
282 CLI::App app{"App description"}; 280 CLI::App app{"App description"};
283 281
284 std::string filename = "default"; 282 std::string filename = "default";
285 app.add_option("-f,--file", filename, "A help string"); 283 app.add_option("-f,--file", filename, "A help string");
286 284
287 - CLI11_PARSE(app, argc, argv); 285 + CLI11_PARSE(app);
288 return 0; 286 return 0;
289 } 287 }
290 ``` 288 ```
@@ -293,7 +291,7 @@ int main(int argc, char** argv) { @@ -293,7 +291,7 @@ int main(int argc, char** argv) {
293 291
294 ```cpp 292 ```cpp
295 try { 293 try {
296 - app.parse(argc, argv); 294 + app.parse();
297 } catch (const CLI::ParseError &e) { 295 } catch (const CLI::ParseError &e) {
298 return app.exit(e); 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,6 +305,25 @@ other processing for speed and to ensure required options and the like do not
307 interfere. 305 interfere.
308 306
309 </p></details> 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 </br> 327 </br>
311 328
312 The initialization is just one line, adding options is just two each. The parse 329 The initialization is just one line, adding options is just two each. The parse
@@ -1468,6 +1485,96 @@ app.add_option(&quot;--fancy-count&quot;, [](std::vector&lt;std::string&gt; val){ @@ -1468,6 +1485,96 @@ app.add_option(&quot;--fancy-count&quot;, [](std::vector&lt;std::string&gt; 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 ### Utilities 1578 ### Utilities
1472 1579
1473 There are a few other utilities that are often useful in CLI programming. These 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,9 +35,9 @@ namespace CLI {
35 // [CLI11:app_hpp:verbatim] 35 // [CLI11:app_hpp:verbatim]
36 36
37 #ifndef CLI11_PARSE 37 #ifndef CLI11_PARSE
38 -#define CLI11_PARSE(app, argc, argv) \ 38 +#define CLI11_PARSE(app, ...) \
39 try { \ 39 try { \
40 - (app).parse((argc), (argv)); \ 40 + (app).parse(__VA_ARGS__); \
41 } catch(const CLI::ParseError &e) { \ 41 } catch(const CLI::ParseError &e) { \
42 return (app).exit(e); \ 42 return (app).exit(e); \
43 } 43 }
@@ -837,15 +837,25 @@ class App { @@ -837,15 +837,25 @@ class App {
837 /// Reset the parsed data 837 /// Reset the parsed data
838 void clear(); 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 /// Parses the command line - throws errors. 844 /// Parses the command line - throws errors.
841 /// This must be called after the options are in but before the rest of the program. 845 /// This must be called after the options are in but before the rest of the program.
842 void parse(int argc, const char *const *argv); 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 /// Parse a single string as if it contained command line arguments. 853 /// Parse a single string as if it contained command line arguments.
845 /// This function splits the string into arguments then calls parse(std::vector<std::string> &) 854 /// This function splits the string into arguments then calls parse(std::vector<std::string> &)
846 /// the function takes an optional boolean argument specifying if the programName is included in the string to 855 /// the function takes an optional boolean argument specifying if the programName is included in the string to
847 /// process 856 /// process
848 void parse(std::string commandline, bool program_name_included = false); 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 /// The real work is done here. Expects a reversed vector. 860 /// The real work is done here. Expects a reversed vector.
851 /// Changes the vector to the remaining options. 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
@@ -13,6 +13,10 @@ @@ -13,6 +13,10 @@
13 13
14 #include "Macros.hpp" 14 #include "Macros.hpp"
15 15
  16 +#include "Encoding.hpp"
  17 +
  18 +#include "Argv.hpp"
  19 +
16 #include "StringTools.hpp" 20 #include "StringTools.hpp"
17 21
18 #include "Error.hpp" 22 #include "Error.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,6 +66,62 @@
66 #endif 66 #endif
67 #endif 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 /** Inline macro **/ 125 /** Inline macro **/
70 #ifdef CLI11_COMPILE 126 #ifdef CLI11_COMPILE
71 #define CLI11_INLINE 127 #define CLI11_INLINE
include/CLI/TypeTools.hpp
@@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
18 #include <vector> 18 #include <vector>
19 // [CLI11:public_includes:end] 19 // [CLI11:public_includes:end]
20 20
  21 +#include "Encoding.hpp"
21 #include "StringTools.hpp" 22 #include "StringTools.hpp"
22 23
23 namespace CLI { 24 namespace CLI {
@@ -242,8 +243,10 @@ struct is_mutable_container&lt; @@ -242,8 +243,10 @@ struct is_mutable_container&lt;
242 decltype(std::declval<T>().clear()), 243 decltype(std::declval<T>().clear()),
243 decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(), 244 decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(),
244 std::declval<const typename T::value_type &>()))>, 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 // check to see if an object is a mutable container (fail by default) 251 // check to see if an object is a mutable container (fail by default)
249 template <typename T, typename _ = void> struct is_readable_container : std::false_type {}; 252 template <typename T, typename _ = void> struct is_readable_container : std::false_type {};
@@ -546,6 +549,8 @@ enum class object_category : int { @@ -546,6 +549,8 @@ enum class object_category : int {
546 // string like types 549 // string like types
547 string_assignable = 23, 550 string_assignable = 23,
548 string_constructible = 24, 551 string_constructible = 24,
  552 + wstring_assignable = 25,
  553 + wstring_constructible = 26,
549 other = 45, 554 other = 45,
550 // special wrapper or container types 555 // special wrapper or container types
551 wrapper_value = 50, 556 wrapper_value = 50,
@@ -613,6 +618,27 @@ struct classify_object&lt; @@ -613,6 +618,27 @@ struct classify_object&lt;
613 static constexpr object_category value{object_category::string_constructible}; 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 /// Enumerations 642 /// Enumerations
617 template <typename T> struct classify_object<T, typename std::enable_if<std::is_enum<T>::value>::type> { 643 template <typename T> struct classify_object<T, typename std::enable_if<std::is_enum<T>::value>::type> {
618 static constexpr object_category value{object_category::enumeration}; 644 static constexpr object_category value{object_category::enumeration};
@@ -625,12 +651,13 @@ template &lt;typename T&gt; struct classify_object&lt;T, typename std::enable_if&lt;is_compl @@ -625,12 +651,13 @@ template &lt;typename T&gt; struct classify_object&lt;T, typename std::enable_if&lt;is_compl
625 /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, 651 /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point,
626 /// vectors, and enumerations 652 /// vectors, and enumerations
627 template <typename T> struct uncommon_type { 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 static constexpr bool value = type::value; 661 static constexpr bool value = type::value;
635 }; 662 };
636 663
@@ -1005,6 +1032,23 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) { @@ -1005,6 +1032,23 @@ bool lexical_cast(const std::string &amp;input, T &amp;output) {
1005 return true; 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 /// Enumerations 1052 /// Enumerations
1009 template <typename T, 1053 template <typename T,
1010 enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy> 1054 enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy>
@@ -1133,7 +1177,9 @@ template &lt;typename AssignTo, @@ -1133,7 +1177,9 @@ template &lt;typename AssignTo,
1133 typename ConvertTo, 1177 typename ConvertTo,
1134 enable_if_t<std::is_same<AssignTo, ConvertTo>::value && 1178 enable_if_t<std::is_same<AssignTo, ConvertTo>::value &&
1135 (classify_object<AssignTo>::value == object_category::string_assignable || 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 detail::enabler> = detail::dummy> 1183 detail::enabler> = detail::dummy>
1138 bool lexical_assign(const std::string &input, AssignTo &output) { 1184 bool lexical_assign(const std::string &input, AssignTo &output) {
1139 return lexical_cast(input, output); 1185 return lexical_cast(input, output);
@@ -1144,7 +1190,9 @@ template &lt;typename AssignTo, @@ -1144,7 +1190,9 @@ template &lt;typename AssignTo,
1144 typename ConvertTo, 1190 typename ConvertTo,
1145 enable_if_t<std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, AssignTo>::value && 1191 enable_if_t<std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, AssignTo>::value &&
1146 classify_object<AssignTo>::value != object_category::string_assignable && 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 detail::enabler> = detail::dummy> 1196 detail::enabler> = detail::dummy>
1149 bool lexical_assign(const std::string &input, AssignTo &output) { 1197 bool lexical_assign(const std::string &input, AssignTo &output) {
1150 if(input.empty()) { 1198 if(input.empty()) {
include/CLI/Validators.hpp
@@ -26,34 +26,6 @@ @@ -26,34 +26,6 @@
26 26
27 // [CLI11:validators_hpp_filesystem:verbatim] 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 #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 29 #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
58 #include <filesystem> // NOLINT(build/include) 30 #include <filesystem> // NOLINT(build/include)
59 #else 31 #else
include/CLI/impl/App_inl.hpp
@@ -9,6 +9,9 @@ @@ -9,6 +9,9 @@
9 // This include is only needed for IDEs to discover symbols 9 // This include is only needed for IDEs to discover symbols
10 #include <CLI/App.hpp> 10 #include <CLI/App.hpp>
11 11
  12 +#include <CLI/Argv.hpp>
  13 +#include <CLI/Encoding.hpp>
  14 +
12 // [CLI11:public_includes:set] 15 // [CLI11:public_includes:set]
13 #include <algorithm> 16 #include <algorithm>
14 #include <memory> 17 #include <memory>
@@ -474,17 +477,31 @@ CLI11_INLINE void App::clear() { @@ -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 // If the name is not set, read from command line 494 // If the name is not set, read from command line
479 if(name_.empty() || has_automatic_name_) { 495 if(name_.empty() || has_automatic_name_) {
480 has_automatic_name_ = true; 496 has_automatic_name_ = true;
481 - name_ = argv[0]; 497 + name_ = detail::maybe_narrow(argv[0]);
482 } 498 }
483 499
484 std::vector<std::string> args; 500 std::vector<std::string> args;
485 args.reserve(static_cast<std::size_t>(argc) - 1U); 501 args.reserve(static_cast<std::size_t>(argc) - 1U);
486 for(auto i = static_cast<std::size_t>(argc) - 1U; i > 0U; --i) 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 parse(std::move(args)); 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,6 +532,10 @@ CLI11_INLINE void App::parse(std::string commandline, bool program_name_included
515 parse(std::move(args)); 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 CLI11_INLINE void App::parse(std::vector<std::string> &args) { 539 CLI11_INLINE void App::parse(std::vector<std::string> &args) {
519 // Clear if parsed 540 // Clear if parsed
520 if(parsed_ > 0) 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,6 +8,7 @@
8 8
9 #include <CLI/Validators.hpp> 9 #include <CLI/Validators.hpp>
10 10
  11 +#include <CLI/Encoding.hpp>
11 #include <CLI/Macros.hpp> 12 #include <CLI/Macros.hpp>
12 #include <CLI/StringTools.hpp> 13 #include <CLI/StringTools.hpp>
13 #include <CLI/TypeTools.hpp> 14 #include <CLI/TypeTools.hpp>
@@ -127,7 +128,7 @@ namespace detail { @@ -127,7 +128,7 @@ namespace detail {
127 #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 128 #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
128 CLI11_INLINE path_type check_path(const char *file) noexcept { 129 CLI11_INLINE path_type check_path(const char *file) noexcept {
129 std::error_code ec; 130 std::error_code ec;
130 - auto stat = std::filesystem::status(file, ec); 131 + auto stat = std::filesystem::status(to_path(file), ec);
131 if(ec) { 132 if(ec) {
132 return path_type::nonexistent; 133 return path_type::nonexistent;
133 } 134 }
src/CMakeLists.txt
@@ -13,7 +13,9 @@ set(CLI11_headers @@ -13,7 +13,9 @@ set(CLI11_headers
13 ${CLI11_headerLoc}/StringTools.hpp 13 ${CLI11_headerLoc}/StringTools.hpp
14 ${CLI11_headerLoc}/TypeTools.hpp 14 ${CLI11_headerLoc}/TypeTools.hpp
15 ${CLI11_headerLoc}/Validators.hpp 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 set(CLI11_implLoc "${PROJECT_SOURCE_DIR}/include/CLI/impl") 20 set(CLI11_implLoc "${PROJECT_SOURCE_DIR}/include/CLI/impl")
19 21
@@ -24,7 +26,10 @@ set(CLI11_impl_headers @@ -24,7 +26,10 @@ set(CLI11_impl_headers
24 ${CLI11_implLoc}/Option_inl.hpp 26 ${CLI11_implLoc}/Option_inl.hpp
25 ${CLI11_implLoc}/Split_inl.hpp 27 ${CLI11_implLoc}/Split_inl.hpp
26 ${CLI11_implLoc}/StringTools_inl.hpp 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 set(CLI11_library_headers ${CLI11_headerLoc}/CLI.hpp ${CLI11_headerLoc}/Timer.hpp) 34 set(CLI11_library_headers ${CLI11_headerLoc}/CLI.hpp ${CLI11_headerLoc}/Timer.hpp)
30 35
src/Precompile.cpp
@@ -5,7 +5,9 @@ @@ -5,7 +5,9 @@
5 // SPDX-License-Identifier: BSD-3-Clause 5 // SPDX-License-Identifier: BSD-3-Clause
6 6
7 #include <CLI/impl/App_inl.hpp> 7 #include <CLI/impl/App_inl.hpp>
  8 +#include <CLI/impl/Argv_inl.hpp>
8 #include <CLI/impl/Config_inl.hpp> 9 #include <CLI/impl/Config_inl.hpp>
  10 +#include <CLI/impl/Encoding_inl.hpp>
9 #include <CLI/impl/Formatter_inl.hpp> 11 #include <CLI/impl/Formatter_inl.hpp>
10 #include <CLI/impl/Option_inl.hpp> 12 #include <CLI/impl/Option_inl.hpp>
11 #include <CLI/impl/Split_inl.hpp> 13 #include <CLI/impl/Split_inl.hpp>
tests/AppTest.cpp
@@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
7 #include "app_helper.hpp" 7 #include "app_helper.hpp"
8 #include <cmath> 8 #include <cmath>
9 9
  10 +#include <array>
10 #include <complex> 11 #include <complex>
11 #include <cstdint> 12 #include <cstdint>
12 #include <cstdlib> 13 #include <cstdlib>
@@ -261,6 +262,28 @@ TEST_CASE_METHOD(TApp, &quot;OneString&quot;, &quot;[app]&quot;) { @@ -261,6 +262,28 @@ TEST_CASE_METHOD(TApp, &quot;OneString&quot;, &quot;[app]&quot;) {
261 CHECK("mystring" == str); 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 TEST_CASE_METHOD(TApp, "OneStringWindowsStyle", "[app]") { 287 TEST_CASE_METHOD(TApp, "OneStringWindowsStyle", "[app]") {
265 std::string str; 288 std::string str;
266 app.add_option("-s,--string", str); 289 app.add_option("-s,--string", str);
@@ -282,6 +305,16 @@ TEST_CASE_METHOD(TApp, &quot;OneStringSingleStringInput&quot;, &quot;[app]&quot;) { @@ -282,6 +305,16 @@ TEST_CASE_METHOD(TApp, &quot;OneStringSingleStringInput&quot;, &quot;[app]&quot;) {
282 CHECK("mystring" == str); 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 TEST_CASE_METHOD(TApp, "OneStringEqualVersion", "[app]") { 318 TEST_CASE_METHOD(TApp, "OneStringEqualVersion", "[app]") {
286 std::string str; 319 std::string str;
287 app.add_option("-s,--string", str); 320 app.add_option("-s,--string", str);
@@ -2463,3 +2496,21 @@ TEST_CASE(&quot;C20_compile&quot;, &quot;simple&quot;) { @@ -2463,3 +2496,21 @@ TEST_CASE(&quot;C20_compile&quot;, &quot;simple&quot;) {
2463 app.parse("--flag"); 2496 app.parse("--flag");
2464 CHECK_FALSE(flag->empty()); 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,7 +49,8 @@ set(CLI11_TESTS
49 StringParseTest 49 StringParseTest
50 ComplexTypeTest 50 ComplexTypeTest
51 TrueFalseTest 51 TrueFalseTest
52 - OptionGroupTest) 52 + OptionGroupTest
  53 + EncodingTest)
53 54
54 if(WIN32) 55 if(WIN32)
55 list(APPEND CLI11_TESTS WindowsTest) 56 list(APPEND CLI11_TESTS WindowsTest)
@@ -89,6 +90,40 @@ else() @@ -89,6 +90,40 @@ else()
89 target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") 90 target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
90 endif() 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 # Target must already exist 127 # Target must already exist
93 macro(add_catch_test TESTNAME) 128 macro(add_catch_test TESTNAME)
94 target_link_libraries(${TESTNAME} PUBLIC catch_main) 129 target_link_libraries(${TESTNAME} PUBLIC catch_main)
@@ -108,6 +143,8 @@ foreach(T IN LISTS CLI11_TESTS) @@ -108,6 +143,8 @@ foreach(T IN LISTS CLI11_TESTS)
108 set_property(SOURCE ${T}.cpp PROPERTY LANGUAGE CUDA) 143 set_property(SOURCE ${T}.cpp PROPERTY LANGUAGE CUDA)
109 endif() 144 endif()
110 add_executable(${T} ${T}.cpp) 145 add_executable(${T} ${T}.cpp)
  146 +
  147 + add_dependent_application_definitions(${T})
111 add_sanitizers(${T}) 148 add_sanitizers(${T})
112 if(NOT CLI11_CUDA_TESTS) 149 if(NOT CLI11_CUDA_TESTS)
113 target_link_libraries(${T} PRIVATE CLI11_warnings) 150 target_link_libraries(${T} PRIVATE CLI11_warnings)
@@ -117,6 +154,7 @@ foreach(T IN LISTS CLI11_TESTS) @@ -117,6 +154,7 @@ foreach(T IN LISTS CLI11_TESTS)
117 154
118 if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) 155 if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
119 add_executable(${T}_Single ${T}.cpp) 156 add_executable(${T}_Single ${T}.cpp)
  157 + add_dependent_application_definitions(${T}_Single)
120 target_link_libraries(${T}_Single PRIVATE CLI11_SINGLE) 158 target_link_libraries(${T}_Single PRIVATE CLI11_SINGLE)
121 add_catch_test(${T}_Single) 159 add_catch_test(${T}_Single)
122 set_property(TARGET ${T}_Single PROPERTY FOLDER "Tests Single File") 160 set_property(TARGET ${T}_Single PROPERTY FOLDER "Tests Single File")
@@ -125,6 +163,7 @@ endforeach() @@ -125,6 +163,7 @@ endforeach()
125 163
126 foreach(T IN LISTS CLI11_MULTIONLY_TESTS) 164 foreach(T IN LISTS CLI11_MULTIONLY_TESTS)
127 add_executable(${T} ${T}.cpp) 165 add_executable(${T} ${T}.cpp)
  166 + add_dependent_application_definitions(${T})
128 add_sanitizers(${T}) 167 add_sanitizers(${T})
129 target_link_libraries(${T} PUBLIC CLI11) 168 target_link_libraries(${T} PUBLIC CLI11)
130 add_catch_test(${T}) 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,6 +13,9 @@
13 #endif 13 #endif
14 14
15 #include "catch.hpp" 15 #include "catch.hpp"
  16 +#include <array>
  17 +#include <fstream>
  18 +#include <iomanip>
16 #include <iostream> 19 #include <iostream>
17 #include <string> 20 #include <string>
18 #include <utility> 21 #include <utility>
@@ -67,3 +70,54 @@ inline void unset_env(std::string name) { @@ -67,3 +70,54 @@ inline void unset_env(std::string name) {
67 unsetenv(name.c_str()); 70 unsetenv(name.c_str());
68 #endif 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,6 +57,20 @@ testnames = [
57 ['link_test_2', {'link_with': link_test_lib}], 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 if host_machine.system() == 'windows' 74 if host_machine.system() == 'windows'
61 testnames += [['WindowsTest', {}]] 75 testnames += [['WindowsTest', {}]]
62 endif 76 endif
@@ -69,7 +83,7 @@ foreach n: testnames @@ -69,7 +83,7 @@ foreach n: testnames
69 name = n[0] 83 name = n[0]
70 kwargs = n[1] 84 kwargs = n[1]
71 t = executable(name, name + '.cpp', 85 t = executable(name, name + '.cpp',
72 - cpp_args: kwargs.get('cpp_args', []), 86 + cpp_args: kwargs.get('cpp_args', []) + dependent_applications_definitions,
73 build_by_default: false, 87 build_by_default: false,
74 dependencies: [testdep] + kwargs.get('dependencies', []), 88 dependencies: [testdep] + kwargs.get('dependencies', []),
75 link_with: kwargs.get('link_with', []) 89 link_with: kwargs.get('link_with', [])