Repository: CLIUtils/CLI11 (C++)

Issue:
Displaying the help should take precedence over checking env vars & other possible checks
This is an issue which was only slightly mentioned in #903, so I will describe this specific case here in more details.

Basic example:

```cpp
auto CliApp = new CLI::App("myapp");
auto sub1 = CliApp->add_subcommand("sub1");
std::filesystem::path someFile = "";
sub1->add_option("-f,--file", someFile)->envname("SOME_FILE")->required()->expected(1)->check(CLI::ExistingFile);

try
{
	CliApp->parse(argsCount, args);
}
catch (const CLI::ParseError& e)
{
	return CliApp->exit(e);
}
```

Make sure `%SOME_FILE%` env var is defined as invalid/non-existing file path.
Then call the app like that: `myapp.exe sub1 -h`:
-Normally it should only display the help about `sub1` expectations & requirements and exit, as that's a completely separate control flow path for the app, just like displaying the app version with `-v`.
-Actually it fails `CLI::ExistingFile` validator check, suggesting to `Run with --help for more information.`. But it's impossible to call for help.


Discussion:
I was able to replicate the issue.  Working on a fix.  Thanks
The issue stems from the fact that validation errors are given higher precedence then the help "error" trigger.    This is a deliberate choice.  In most cases if you really want help you can remove other arguments.    In the case of environmental values, that fail validation there is no way to not trigger it.  This is probably not a good situation.    The fix I would propose is to make the environmental variables do a pre-validation,  like the `validate_positionals()` modifier.    Except in this case it is not an optional modifier.    It would be like not having the environmental variable defined to begin with if it doesn't pass validation.  This gets around the issue described in the bug report.  There might be other possible solutions if CLI11 were to ever work through all the details to make it work with no exceptions, but for now I think prevalidating the results for environmental variables is probably the best and simplest approach.   I will make a PR shortly and have @henryiii check to make sure that is approach we want to take.  



Patch:
diff --git a/README.md b/README.md
index 5550f92b4..3127be8a3 100644
--- a/README.md
+++ b/README.md
@@ -417,7 +417,8 @@ Before parsing, you can set the following options:
   option. Options can be removed from the excludes list with
   `->remove_excludes(opt)`
 - `->envname(name)`: Gets the value from the environment if present and not
-  passed on the command line.
+  passed on the command line. 🚧 The value must also pass any validators to be
+  used.
 - `->group(name)`: The help group to put the option in. No effect for positional
   options. Defaults to `"Options"`. Options given an empty string will not show
   up in the help print (hidden).
diff --git a/book/chapters/options.md b/book/chapters/options.md
index f3cf25326..bc32cea7b 100644
--- a/book/chapters/options.md
+++ b/book/chapters/options.md
@@ -214,7 +214,7 @@ that to add option modifiers. A full listing of the option modifiers:
 | `->type_size(Nmin,Nmax)`                                | specify that each block of values would consist of between Nmin and Nmax elements                                                                                                                                                                                                                                                                                                                                                                         |
 | `->needs(opt)`                                          | This option requires another option to also be present, opt is an `Option` pointer or a string with the name of the option. Can be removed with `->remove_needs(opt)`                                                                                                                                                                                                                                                                                     |
 | `->excludes(opt)`                                       | This option cannot be given with `opt` present, opt is an `Option` pointer or a string with the name of the option. Can be removed with `->remove_excludes(opt)`                                                                                                                                                                                                                                                                                          |
-| `->envname(name)`                                       | Gets the value from the environment if present and not passed on the command line.                                                                                                                                                                                                                                                                                                                                                                        |
+| `->envname(name)`                                       | Gets the value from the environment if present and not passed on the command line and passes any validators.                                                                                                                                                                                                                                                                                                                                              |
 | `->group(name)`                                         | The help group to put the option in. No effect for positional options. Defaults to `"Options"`. Options given an empty string for the group name will not show up in the help print.                                                                                                                                                                                                                                                                      |
 | `->description(string)`                                 | Set/change the description                                                                                                                                                                                                                                                                                                                                                                                                                                |
 | `->ignore_case()`                                       | Ignore the case on the command line (also works on subcommands, does not affect arguments).                                                                                                                                                                                                                                                                                                                                                               |
diff --git a/include/CLI/impl/App_inl.hpp b/include/CLI/impl/App_inl.hpp
index c5f79ca58..0fed48b0f 100644
--- a/include/CLI/impl/App_inl.hpp
+++ b/include/CLI/impl/App_inl.hpp
@@ -1080,7 +1080,11 @@ CLI11_INLINE void App::_process_env() {
         if(opt->count() == 0 && !opt->envname_.empty()) {
             std::string ename_string = detail::get_environment_value(opt->envname_);
             if(!ename_string.empty()) {
-                opt->add_result(ename_string);
+                std::string result = ename_string;
+                result = opt->_validate(result, 0);
+                if(result.empty()) {
+                    opt->add_result(ename_string);
+                }
             }
         }
     }


Test patch:
diff --git a/tests/HelpTest.cpp b/tests/HelpTest.cpp
index c4403f754..4af0a3b8e 100644
--- a/tests/HelpTest.cpp
+++ b/tests/HelpTest.cpp
@@ -10,6 +10,8 @@
 #include "CLI/CLI.hpp"
 #endif
 
+#include "app_helper.hpp"
+
 #include "catch.hpp"
 #include <fstream>
 
@@ -718,6 +720,22 @@ TEST_CASE("THelp: CustomHelp", "[help]") {
     }
 }
 
+TEST_CASE("THelp: HelpSubcommandPriority", "[help]") {
+    CLI::App app{"My prog"};
+
+    app.set_help_flag("-h", "display help and exit");
+
+    auto *sub1 = app.add_subcommand("sub1");
+    std::string someFile = "";
+
+    put_env("SOME_FILE", "NOT_A_FILE");
+    sub1->add_option("-f,--file", someFile)->envname("SOME_FILE")->required()->expected(1)->check(CLI::ExistingFile);
+
+    std::string input{"sub1 -h"};
+    CHECK_THROWS_AS(app.parse(input), CLI::CallForHelp);
+    unset_env("SOME_FILE");
+}
+
 TEST_CASE("THelp: NextLineShouldBeAlignmentInMultilineDescription", "[help]") {
     CLI::App app;
     int i{0};
diff --git a/tests/SubcommandTest.cpp b/tests/SubcommandTest.cpp
index bdcd4bf8d..7c3f3e21a 100644
--- a/tests/SubcommandTest.cpp
+++ b/tests/SubcommandTest.cpp
@@ -2138,6 +2138,6 @@ TEST_CASE_METHOD(TApp, "subcommandEnvironmentName", "[subcom]") {
     CHECK_NOTHROW(run());
 
     args = {"sub1", "-v", "111"};
-    CHECK_THROWS_AS(run(), CLI::ValidationError);
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
     unset_env("SOME_FILE");
 }