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 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
... ... @@ -6,8 +6,10 @@ a.out*
6 6 /CMakeFiles/*
7 7 /cmake_install.cmake
8 8 /*.kdev4
  9 +/.vscode
9 10 /html/*
10 11 !/meson.build
  12 +/CMakeUserPresets.json
11 13  
12 14 /node_modules/*
13 15 /package.json
... ...
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(&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 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
... ... @@ -13,6 +13,10 @@
13 13  
14 14 #include "Macros.hpp"
15 15  
  16 +#include "Encoding.hpp"
  17 +
  18 +#include "Argv.hpp"
  19 +
16 20 #include "StringTools.hpp"
17 21  
18 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 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&lt;
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&lt;
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 &lt;typename T&gt; struct classify_object&lt;T, typename std::enable_if&lt;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 &amp;input, T &amp;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 &lt;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 &lt;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, &quot;OneString&quot;, &quot;[app]&quot;) {
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, &quot;OneStringSingleStringInput&quot;, &quot;[app]&quot;) {
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(&quot;C20_compile&quot;, &quot;simple&quot;) {
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', [])
... ...