This is an automated email from the ASF dual-hosted git repository.

fgerlits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit 1dfab222d44100186e3450cd0fc964f04708734b
Author: Gabor Gyimesi <gamezb...@gmail.com>
AuthorDate: Tue Aug 22 15:08:27 2023 +0200

    MINIFICPP-1774 Set properties from command line arguments
    
    Signed-off-by: Ferenc Gerlits <fgerl...@gmail.com>
    This closes #1638
---
 CMakeLists.txt                          |   7 +-
 LICENSE                                 |  32 ++-----
 NOTICE                                  |   1 +
 cmake/{CxxOpts.cmake => ArgParse.cmake} |  19 +---
 controller/CMakeLists.txt               |   6 +-
 controller/MiNiFiController.cpp         | 140 ++++++++++++++++-----------
 encrypt-config/ArgParser.cpp            | 164 --------------------------------
 encrypt-config/ArgParser.h              |  74 --------------
 encrypt-config/CMakeLists.txt           |   3 +-
 encrypt-config/CommandException.h       |  36 -------
 encrypt-config/EncryptConfigMain.cpp    |  33 ++++---
 extensions/ExtensionHeader.txt          |   2 +-
 libminifi/CMakeLists.txt                |   6 +-
 minifi_main/CMakeLists.txt              |   6 +-
 minifi_main/MiNiFiMain.cpp              | 144 +++++++++++++++++++---------
 nanofi/CMakeLists.txt                   |   2 +-
 nanofi/ecu/CMakeLists.txt               |   2 +-
 nanofi/examples/CMakeLists.txt          |   2 +-
 18 files changed, 236 insertions(+), 443 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f602449c3..e74e7ed0c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,9 +20,7 @@
 cmake_minimum_required(VERSION 3.24)
 cmake_policy(SET CMP0096 NEW) # policy to preserve the leading zeros in 
PROJECT_VERSION_{MAJOR,MINOR,PATCH,TWEAK}
 cmake_policy(SET CMP0065 OLD) # default export policy, required for self-dlopen
-if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
-    cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted 
contents to the time of extraction
-endif()
+cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted 
contents to the time of extraction
 
 project(nifi-minifi-cpp VERSION 0.15.0)
 set(PROJECT_NAME "nifi-minifi-cpp")
@@ -296,9 +294,6 @@ add_library(RapidJSON INTERFACE)
 target_include_directories(RapidJSON SYSTEM INTERFACE 
"${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/rapidjson-48fbd8cd202ca54031fe799db2ad44ffa8e77c13/include")
 target_compile_definitions(RapidJSON INTERFACE RAPIDJSON_HAS_STDSTRING)
 
-# cxxopts
-include(CxxOpts)
-
 include(Coroutines)
 enable_coroutines()
 
diff --git a/LICENSE b/LICENSE
index 82cee2dcf..6d2a0d4f7 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1250,28 +1250,6 @@ Redistribution and use in source and binary forms, with 
or without modification,
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
LOSS OF USE, DATA, OR PROFI [...]
 
-This product bundles 'cxxopts' which is available under an MIT license.
-
-Copyright (c) 2014 Jarryd Beck
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
 This product bundles zlib which is available under the zlib license:
 
  (C) 1995-2017 Jean-loup Gailly and Mark Adler
@@ -3416,3 +3394,13 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
+
+This product bundles 'argparse' which is available under The MIT License.
+
+Copyright (c) 2018 Pranav Srinivas Kumar <pranav.srinivas.ku...@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights to 
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
of the Software, and to permit persons to whom the Software is furnished to do 
so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all 
copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
SOFTWARE.
diff --git a/NOTICE b/NOTICE
index 006fa971f..4e889868c 100644
--- a/NOTICE
+++ b/NOTICE
@@ -76,6 +76,7 @@ This software includes third party software subject to the 
following copyrights:
 - OpenSSL - Copyright (c) 1998-2022 The OpenSSL Project, Copyright (c) 
1995-1998 Eric A. Young, Tim J. Hudson. All rights reserved.
 - bitwizeshift.github.io - Copyright (c) 2020 Matthew Rodusek
 - magic_enum - Copyright (c) 2019 - 2023 Daniil Goncharov
+- argparse - Copyright (c) 2018 Pranav Srinivas Kumar 
<pranav.srinivas.ku...@gmail.com>
 
 The licenses for these third party components are included in LICENSE.txt
 
diff --git a/cmake/CxxOpts.cmake b/cmake/ArgParse.cmake
similarity index 53%
rename from cmake/CxxOpts.cmake
rename to cmake/ArgParse.cmake
index c6ad00364..883f34986 100644
--- a/cmake/CxxOpts.cmake
+++ b/cmake/ArgParse.cmake
@@ -16,18 +16,9 @@
 # under the License.
 
 include(FetchContent)
-
-FetchContent_Declare(cxxopts_src
-    URL      
https://github.com/jarro2783/cxxopts/archive/refs/tags/v2.2.1.tar.gz
-    URL_HASH 
SHA256=984aa3c8917d649b14d7f6277104ce38dd142ce378a9198ec926f03302399681
+FetchContent_Declare(
+    argparse
+    URL https://github.com/p-ranav/argparse/archive/refs/tags/v2.9.tar.gz
+    URL_HASH 
SHA256=cd563293580b9dc592254df35b49cf8a19b4870ff5f611c7584cf967d9e6031e
 )
-FetchContent_GetProperties(cxxopts_src)
-if (NOT cxxopts_src_POPULATED)
-    FetchContent_Populate(cxxopts_src)
-    set(CXXOPTS_INCLUDE_DIR "${cxxopts_src_SOURCE_DIR}/include" CACHE STRING 
"" FORCE)
-    add_library(cxxopts INTERFACE)
-    add_library(cxxopts::cxxopts ALIAS cxxopts)
-    target_sources(cxxopts INTERFACE ${CXXOPTS_INCLUDE_DIR}/cxxopts.hpp)
-    target_include_directories(cxxopts SYSTEM INTERFACE ${CXXOPTS_INCLUDE_DIR})
-    target_compile_features(cxxopts INTERFACE cxx_std_11)
-endif()
+FetchContent_MakeAvailable(argparse)
diff --git a/controller/CMakeLists.txt b/controller/CMakeLists.txt
index dd3161198..5ad98fbd4 100644
--- a/controller/CMakeLists.txt
+++ b/controller/CMakeLists.txt
@@ -17,7 +17,8 @@
 # under the License.
 #
 
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.24)
+cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted 
contents to the time of extraction
 
 include_directories(../minifi_main/ ../libminifi/include  
../libminifi/include/c2  ../libminifi/include/c2/protocols/  
../libminifi/include/core/state 
./libminifi/include/core/statemanagement/metrics  
../libminifi/include/core/yaml  ../libminifi/include/core)
 
@@ -39,7 +40,8 @@ if (WIN32)
 endif()
 
 add_executable(minificontroller ${MINIFI_CONTROLLER_SOURCES})
-target_link_libraries(minificontroller ${LIBMINIFI} cxxopts Threads::Threads)
+include(ArgParse)
+target_link_libraries(minificontroller ${LIBMINIFI} argparse Threads::Threads)
 
 set_target_properties(minificontroller PROPERTIES
     OUTPUT_NAME minificontroller
diff --git a/controller/MiNiFiController.cpp b/controller/MiNiFiController.cpp
index 053a5fbae..30930f676 100644
--- a/controller/MiNiFiController.cpp
+++ b/controller/MiNiFiController.cpp
@@ -17,6 +17,8 @@
  */
 #include <vector>
 #include <iostream>
+#include <string>
+#include <string_view>
 
 #include "MainHelper.h"
 #include "properties/Configure.h"
@@ -26,8 +28,9 @@
 #include "core/extension/ExtensionManager.h"
 #include "core/ConfigurationFactory.h"
 #include "Exception.h"
-
-#include "cxxopts.hpp"
+#include "argparse/argparse.hpp"
+#include "range/v3/algorithm/contains.hpp"
+#include "agent/agent_version.h"
 
 namespace minifi = org::apache::nifi::minifi;
 
@@ -106,61 +109,89 @@ int main(int argc, char **argv) {
     socket_data.ssl_context_service = getSSLContextService(configuration);
   } catch(const minifi::Exception& ex) {
     logger->log_error(ex.what());
-    exit(1);
+    std::exit(1);
   }
 
-  cxxopts::Options options("MiNiFiController", "MiNiFi local agent 
controller");
-  options.positional_help("[optional args]").show_positional_help();
-
-  options.add_options()
-      ("h,help", "Shows Help")
-      ("host", "Specifies connecting host name", cxxopts::value<std::string>())
-      ("port", "Specifies connecting host port", cxxopts::value<int>())
-      ("stop", "Shuts down the provided component", 
cxxopts::value<std::vector<std::string>>())
-      ("start", "Starts provided component", 
cxxopts::value<std::vector<std::string>>())
-      ("l,list", "Provides a list of connections or processors", 
cxxopts::value<std::string>())
-      ("c,clear", "Clears the associated connection queue", 
cxxopts::value<std::vector<std::string>>())
-      ("getsize", "Reports the size of the associated connection queue", 
cxxopts::value<std::vector<std::string>>())
-      ("updateflow", "Updates the flow of the agent using the provided flow 
file", cxxopts::value<std::string>())
-      ("getfull", "Reports a list of full connections")
-      ("jstack", "Returns backtraces from the agent")
-      ("manifest", "Generates a manifest for the current binary")
-      ("noheaders", "Removes headers from output streams");
+  argparse::ArgumentParser argument_parser("Apache MiNiFi C++ Controller", 
minifi::AgentBuild::VERSION);
+  argument_parser.add_argument("--host").metavar("HOSTNAME")
+    .help("Specifies connecting host name");
+  argument_parser.add_argument("--port")
+    .metavar("PORT")
+    .help("Specifies connecting host port")
+    .scan<'d', int>();
+  argument_parser.add_argument("--stop")
+    .metavar("COMPONENT")
+    .nargs(argparse::nargs_pattern::at_least_one)
+    .help("Shuts down the provided components");
+  argument_parser.add_argument("--start")
+    .metavar("COMPONENT")
+    .nargs(argparse::nargs_pattern::at_least_one)
+    .help("Starts provided components");
+  argument_parser.add_argument("-l", "--list")
+    .action([](const std::string& value) {
+      if (ranges::contains(std::array{"components", "connections"}, value)) {
+        return value;
+      }
+      throw std::runtime_error("List command only accepts the following 
parameters: [components, connections]");
+    })
+    .help("Provides a list of connections or components (processors). Accepted 
parameters: [components, components]");
+  argument_parser.add_argument("-c", "--clear")
+    .metavar("CONNECTION")
+    .nargs(argparse::nargs_pattern::at_least_one)
+    .help("Clears the associated connection queues");
+  argument_parser.add_argument("--getsize")
+    .metavar("CONNECTION")
+    .nargs(argparse::nargs_pattern::at_least_one)
+    .help("Reports the size of the associated connection queues");
+  argument_parser.add_argument("--updateflow")
+    .metavar("FLOW_CONFIG_PATH")
+    .help("Updates the flow of the agent using the provided flow file");
+
+  auto addFlagOption = [&](std::string_view name, const std::string& help) {
+    argument_parser.add_argument(name)
+      .default_value(false)
+      .implicit_value(true)
+      .help(help);
+  };
+  addFlagOption("--getfull", "Reports a list of full connections");
+  addFlagOption("--jstack", "Returns backtraces from the agent");
+  addFlagOption("--manifest", "Generates a manifest for the current binary");
+  addFlagOption("--noheaders", "Removes headers from output streams");
 
   bool show_headers = true;
 
   try {
-    auto result = options.parse(argc, argv);
-
-    if (result.count("help")) {
-      std::cout << options.help({ "", "Group" }) << std::endl;
-      exit(0);
-    }
+    argument_parser.parse_args(argc, argv);
+  } catch (const std::runtime_error& err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << argument_parser;
+    std::exit(1);
+  }
 
-    if (result.count("host")) {
-      socket_data.host = result["host"].as<std::string>();
+  try {
+    if (const auto& host = argument_parser.present("--host")) {
+      socket_data.host = *host;
     } else {
       configuration->get(minifi::Configure::controller_socket_host, 
socket_data.host);
     }
 
     std::string port_str;
-    if (result.count("port")) {
-      socket_data.port = result["port"].as<int>();
+    if (const auto& port = argument_parser.present<int>("--port")) {
+      socket_data.port = *port;
     } else if (socket_data.port == -1 && 
configuration->get(minifi::Configure::controller_socket_port, port_str)) {
       socket_data.port = std::stoi(port_str);
     }
 
     if ((minifi::IsNullOrEmpty(socket_data.host) && socket_data.port == -1)) {
       std::cout << "MiNiFi Controller is disabled" << std::endl;
-      exit(0);
+      std::exit(0);
     }
-    if (result.count("noheaders")) {
+    if (argument_parser.get<bool>("--noheaders")) {
       show_headers = false;
     }
 
-    if (result.count("stop") > 0) {
-      auto& components = result["stop"].as<std::vector<std::string>>();
-      for (const auto& component : components) {
+    if (const auto& components = 
argument_parser.present<std::vector<std::string>>("--stop")) {
+      for (const auto& component : *components) {
         if (minifi::controller::stopComponent(socket_data, component))
           std::cout << component << " requested to stop" << std::endl;
         else
@@ -168,9 +199,8 @@ int main(int argc, char **argv) {
       }
     }
 
-    if (result.count("start") > 0) {
-      auto& components = result["start"].as<std::vector<std::string>>();
-      for (const auto& component : components) {
+    if (const auto& components = 
argument_parser.present<std::vector<std::string>>("--start")) {
+      for (const auto& component : *components) {
         if (minifi::controller::startComponent(socket_data, component))
           std::cout << component << " requested to start" << std::endl;
         else
@@ -178,9 +208,8 @@ int main(int argc, char **argv) {
       }
     }
 
-    if (result.count("c") > 0) {
-      auto& components = result["c"].as<std::vector<std::string>>();
-      for (const auto& connection : components) {
+    if (const auto& components = 
argument_parser.present<std::vector<std::string>>("--clear")) {
+      for (const auto& connection : *components) {
         if (minifi::controller::clearConnection(socket_data, connection)) {
           std::cout << "Sent clear command to " << connection << "." << 
std::endl;
         } else {
@@ -189,40 +218,39 @@ int main(int argc, char **argv) {
       }
     }
 
-    if (result.count("getsize") > 0) {
-      auto& components = result["getsize"].as<std::vector<std::string>>();
-      for (const auto& component : components) {
+    if (const auto& components = 
argument_parser.present<std::vector<std::string>>("--getsize")) {
+      for (const auto& component : *components) {
         if (!minifi::controller::getConnectionSize(socket_data, std::cout, 
component))
           std::cout << "Could not connect to remote host " << socket_data.host 
<< ":" << socket_data.port << std::endl;
       }
     }
-    if (result.count("l") > 0) {
-      auto& option = result["l"].as<std::string>();
-      if (option == "components") {
+
+    if (const auto& option = argument_parser.present("--list")) {
+      if (*option == "components") {
         if (!minifi::controller::listComponents(socket_data, std::cout, 
show_headers))
           std::cout << "Could not connect to remote host " << socket_data.host 
<< ":" << socket_data.port << std::endl;
-      } else if (option == "connections") {
+      } else if (*option == "connections") {
         if (!minifi::controller::listConnections(socket_data, std::cout, 
show_headers))
           std::cout << "Could not connect to remote host " << socket_data.host 
<< ":" << socket_data.port << std::endl;
       }
     }
-    if (result.count("getfull") > 0) {
+
+    if (argument_parser.get<bool>("--getfull")) {
       if (!minifi::controller::getFullConnections(socket_data, std::cout))
         std::cout << "Could not connect to remote host " << socket_data.host 
<< ":" << socket_data.port << std::endl;
     }
 
-    if (result.count("updateflow") > 0) {
-      auto& flow_file = result["updateflow"].as<std::string>();
-      if (!minifi::controller::updateFlow(socket_data, std::cout, flow_file))
+    if (const auto& flow_file = argument_parser.present("--updateflow")) {
+      if (!minifi::controller::updateFlow(socket_data, std::cout, *flow_file))
         std::cout << "Could not connect to remote host " << socket_data.host 
<< ":" << socket_data.port << std::endl;
     }
 
-    if (result.count("manifest") > 0) {
+    if (argument_parser.get<bool>("--manifest")) {
       if (!minifi::controller::printManifest(socket_data, std::cout))
         std::cout << "Could not connect to remote host " << socket_data.host 
<< ":" << socket_data.port << std::endl;
     }
 
-    if (result.count("jstack") > 0) {
+    if (argument_parser.get<bool>("--jstack")) {
       if (!minifi::controller::getJstacks(socket_data, std::cout))
         std::cout << "Could not connect to remote host " << socket_data.host 
<< ":" << socket_data.port << std::endl;
     }
@@ -230,8 +258,8 @@ int main(int argc, char **argv) {
     // catch anything thrown within try block that derives from std::exception
     std::cerr << exc.what() << std::endl;
   } catch (...) {
-    std::cout << options.help({ "", "Group" }) << std::endl;
-    exit(0);
+    std::cerr << argument_parser;
+    std::exit(1);
   }
   return 0;
 }
diff --git a/encrypt-config/ArgParser.cpp b/encrypt-config/ArgParser.cpp
deleted file mode 100644
index 5a1a2839d..000000000
--- a/encrypt-config/ArgParser.cpp
+++ /dev/null
@@ -1,164 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <string>
-#include <set>
-#include <iostream>
-#include <algorithm>
-#include "ArgParser.h"
-#include "utils/OptionalUtils.h"
-#include "utils/StringUtils.h"
-#include "utils/CollectionUtils.h"
-#include "CommandException.h"
-
-namespace org::apache::nifi::minifi::encrypt_config {
-
-const std::vector<Argument> Arguments::registered_args_{
-    {std::set<std::string>{"--minifi-home", "-m"},
-     true,
-     "minifi home",
-     "Specifies the home directory used by the minifi agent"}
-};
-
-const std::vector<Flag> Arguments::registered_flags_{
-    {std::set<std::string>{"--help", "-h"},
-     "Prints this help message"},
-    {std::set<std::string>{"--encrypt-flow-config"},
-     "If set, the flow configuration file (as specified in minifi.properties) 
is also encrypted."}
-};
-
-std::string Arguments::getHelp() {
-  std::stringstream ss;
-  ss << "Usage: " << "encrypt-config";
-  for (const auto& arg : registered_args_) {
-    ss << " ";
-    if (!arg.required) {
-      ss << "[";
-    }
-    ss << utils::StringUtils::join("|", arg.names)
-       << " <" << arg.value_name << ">";
-    if (!arg.required) {
-      ss << "]";
-    }
-  }
-  for (const auto& flag : registered_flags_) {
-    ss << " [" << utils::StringUtils::join("|", flag.names) << "]";
-  }
-  ss << "\n";
-  for (const auto& arg : registered_args_) {
-    ss << "\t";
-    ss << utils::StringUtils::join("|", arg.names) << " : ";
-    if (arg.required) {
-      ss << "(required)";
-    } else {
-      ss << "(optional)";
-    }
-    ss << " " << arg.description;
-    ss << "\n";
-  }
-  for (const auto& flag : registered_flags_) {
-    ss << "\t" << utils::StringUtils::join("|", flag.names) << " : "
-        << flag.description << "\n";
-  }
-  return ss.str();
-}
-
-void Arguments::set(const std::string& key, const std::string& value) {
-  if (get(key)) {
-    throw CommandException("Key is specified more than once \"" + key + "\"");
-  }
-  args_[key] = value;
-}
-
-void Arguments::set(const std::string& flag) {
-  if (isSet(flag)) {
-    throw CommandException("Flag is specified more than once \"" + flag + 
"\"");
-  }
-  flags_.insert(flag);
-}
-
-std::optional<std::string> Arguments::get(const std::string &key) const {
-  return getArg(key) | utils::flatMap([&] (const Argument& arg) {return 
get(arg);});
-}
-
-std::optional<std::string> Arguments::get(const Argument& arg) const {
-  for (const auto& name : arg.names) {
-    auto it = args_.find(name);
-    if (it != args_.end()) {
-      return it->second;
-    }
-  }
-  return {};
-}
-
-bool Arguments::isSet(const std::string &flag) const {
-  std::optional<Flag> opt_flag = getFlag(flag);
-  if (!opt_flag) {
-    return false;
-  }
-  return utils::haveCommonItem(opt_flag->names, flags_);
-}
-
-Arguments Arguments::parse(int argc, char* argv[]) {
-  Arguments args;
-  for (int argIdx = 1; argIdx < argc; ++argIdx) {
-    std::string key{argv[argIdx]};
-    if (getFlag(key)) {
-      args.set(key);
-      continue;
-    }
-    if (!getArg(key)) {
-      throw CommandException("Unrecognized option: \"" + key + "\"");
-    }
-    if (argIdx == argc - 1) {
-      throw CommandException("No value specified for key \"" + key + "\"");
-    }
-    ++argIdx;
-    std::string value{argv[argIdx]};
-    args.set(key, value);
-  }
-  if (args.isSet("-h")) {
-    std::cout << getHelp();
-    std::exit(0);
-  }
-  for (const auto& arg : registered_args_) {
-    if (arg.required && !args.get(arg)) {
-      throw CommandException("Missing required option " + 
utils::StringUtils::join("|", arg.names));
-    }
-  }
-  return args;
-}
-
-std::optional<Flag> Arguments::getFlag(const std::string &name) {
-  for (const auto& flag : registered_flags_) {
-    if (flag.names.contains(name)) {
-      return flag;
-    }
-  }
-  return {};
-}
-
-std::optional<Argument> Arguments::getArg(const std::string &key) {
-  for (const auto& arg : registered_args_) {
-    if (arg.names.contains(key)) {
-      return arg;
-    }
-  }
-  return {};
-}
-
-}  // namespace org::apache::nifi::minifi::encrypt_config
diff --git a/encrypt-config/ArgParser.h b/encrypt-config/ArgParser.h
deleted file mode 100644
index fda3570d8..000000000
--- a/encrypt-config/ArgParser.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <string>
-#include <set>
-#include <vector>
-#include <map>
-#include <optional>
-
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace encrypt_config {
-
-struct Argument {
-  std::set<std::string> names;
-  bool required;
-  std::string value_name;
-  std::string description;
-};
-
-struct Flag {
-  std::set<std::string> names;
-  std::string description;
-};
-
-class Arguments {
-  static const std::vector<Argument> registered_args_;
-  static const std::vector<Flag> registered_flags_;
-
-  void set(const std::string& key, const std::string& value);
-
-  void set(const std::string& flag);
-
-  static std::optional<Argument> getArg(const std::string& key);
-  static std::optional<Flag> getFlag(const std::string& name);
-
- public:
-  static Arguments parse(int argc, char* argv[]);
-
-  static std::string getHelp();
-
-  [[nodiscard]] std::optional<std::string> get(const std::string& key) const;
-
-  [[nodiscard]] bool isSet(const std::string& flag) const;
-
- private:
-  [[nodiscard]] std::optional<std::string> get(const Argument& arg) const;
-
-  std::map<std::string, std::string> args_;
-  std::set<std::string> flags_;
-};
-
-}  // namespace encrypt_config
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
diff --git a/encrypt-config/CMakeLists.txt b/encrypt-config/CMakeLists.txt
index d54b4f06a..e3c490d61 100644
--- a/encrypt-config/CMakeLists.txt
+++ b/encrypt-config/CMakeLists.txt
@@ -25,7 +25,8 @@ if (WIN32)
 endif()
 add_executable(encrypt-config "${ENCRYPT_CONFIG_FILES}")
 target_include_directories(encrypt-config PRIVATE ../libminifi/include)
-target_link_libraries(encrypt-config libsodium ${LIBMINIFI})
+include(ArgParse)
+target_link_libraries(encrypt-config libsodium argparse ${LIBMINIFI})
 set_target_properties(encrypt-config PROPERTIES OUTPUT_NAME encrypt-config)
 set_target_properties(encrypt-config PROPERTIES RUNTIME_OUTPUT_DIRECTORY 
"${CMAKE_BINARY_DIR}/bin")
 
diff --git a/encrypt-config/CommandException.h 
b/encrypt-config/CommandException.h
deleted file mode 100644
index 1de4494ca..000000000
--- a/encrypt-config/CommandException.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <exception>
-#include <string>
-
-namespace org {
-namespace apache {
-namespace nifi {
-namespace minifi {
-namespace encrypt_config {
-
-struct CommandException : std::logic_error {
-  using std::logic_error::logic_error;
-};
-
-}  // namespace encrypt_config
-}  // namespace minifi
-}  // namespace nifi
-}  // namespace apache
-}  // namespace org
diff --git a/encrypt-config/EncryptConfigMain.cpp 
b/encrypt-config/EncryptConfigMain.cpp
index ae593037d..34760781b 100644
--- a/encrypt-config/EncryptConfigMain.cpp
+++ b/encrypt-config/EncryptConfigMain.cpp
@@ -19,18 +19,33 @@
 #include <typeinfo>
 
 #include "EncryptConfig.h"
-#include "ArgParser.h"
-#include "CommandException.h"
+#include "argparse/argparse.hpp"
+#include "agent/agent_version.h"
 
-using org::apache::nifi::minifi::encrypt_config::Arguments;
 using org::apache::nifi::minifi::encrypt_config::EncryptConfig;
-using org::apache::nifi::minifi::encrypt_config::CommandException;
 
 int main(int argc, char* argv[]) try {
-  Arguments args = Arguments::parse(argc, argv);
-  EncryptConfig encrypt_config{args.get("-m").value()};
+  argparse::ArgumentParser argument_parser("Apache MiNiFi C++ Encrypt-Config", 
org::apache::nifi::minifi::AgentBuild::VERSION);
+  argument_parser.add_argument("-m", "--minifi-home")
+    .required()
+    .metavar("MINIFI_HOME")
+    .help("Specifies the home directory used by the minifi agent");
+  argument_parser.add_argument("-e", "--encrypt-flow-config")
+    .default_value(false)
+    .implicit_value(true)
+    .help("If set, the flow configuration file (as specified in 
minifi.properties) is also encrypted.");
+
+  try {
+    argument_parser.parse_args(argc, argv);
+  } catch (const std::runtime_error& err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << argument_parser;
+    std::exit(1);
+  }
+
+  EncryptConfig encrypt_config{argument_parser.get("-m")};
   EncryptConfig::EncryptionType type = 
encrypt_config.encryptSensitiveProperties();
-  if (args.isSet("--encrypt-flow-config")) {
+  if (argument_parser.get<bool>("--encrypt-flow-config")) {
     encrypt_config.encryptFlowConfig();
   } else if (type == EncryptConfig::EncryptionType::RE_ENCRYPT) {
     std::cout << "WARNING: you did not request the flow config to be updated, "
@@ -38,10 +53,6 @@ int main(int argc, char* argv[]) try {
               << "you won't be able to recover the flow config.\n";
   }
   return 0;
-} catch (const CommandException& c_ex) {
-  std::cerr << c_ex.what() << "\n";
-  std::cerr << Arguments::getHelp() << "\n";
-  return 1;
 } catch (const std::exception& ex) {
   std::cerr << ex.what() << "\n(" << typeid(ex).name() << ")\n";
   return 2;
diff --git a/extensions/ExtensionHeader.txt b/extensions/ExtensionHeader.txt
index db240d100..9a9da14b4 100644
--- a/extensions/ExtensionHeader.txt
+++ b/extensions/ExtensionHeader.txt
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.24)
 
 
 include_directories(../../libminifi/include ../../libminifi/include/core)
diff --git a/libminifi/CMakeLists.txt b/libminifi/CMakeLists.txt
index ca10f5ca0..2a7e581b7 100644
--- a/libminifi/CMakeLists.txt
+++ b/libminifi/CMakeLists.txt
@@ -17,11 +17,9 @@
 # under the License.
 #
 
-cmake_minimum_required (VERSION 3.16)
+cmake_minimum_required (VERSION 3.24)
 cmake_policy(SET CMP0096 NEW) # policy to preserve the leading zeros in 
PROJECT_VERSION_{MAJOR,MINOR,PATCH,TWEAK}
-if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
-    cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted 
contents to the time of extraction
-endif()
+cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted 
contents to the time of extraction
 project(nifi-libcore-minifi VERSION 0.15.0)
 set(PROJECT_NAME "nifi-libcore-minifi")
 
diff --git a/minifi_main/CMakeLists.txt b/minifi_main/CMakeLists.txt
index c1e5e80d2..b80cdbf33 100644
--- a/minifi_main/CMakeLists.txt
+++ b/minifi_main/CMakeLists.txt
@@ -17,7 +17,8 @@
 # under the License.
 #
 
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.24)
+cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted 
contents to the time of extraction
 
 include_directories(../libminifi/include)
 
@@ -63,7 +64,8 @@ if (WIN32)
 endif()
 
 get_property(extensions GLOBAL PROPERTY EXTENSION-OPTIONS)
-target_link_libraries(minifiexe spdlog libsodium gsl-lite ${LIBMINIFI})
+include(ArgParse)
+target_link_libraries(minifiexe spdlog libsodium gsl-lite argparse 
${LIBMINIFI})
 
 set_target_properties(minifiexe PROPERTIES RUNTIME_OUTPUT_DIRECTORY 
"${CMAKE_BINARY_DIR}/bin")
 set_target_properties(minifiexe PROPERTIES OUTPUT_NAME minifi)
diff --git a/minifi_main/MiNiFiMain.cpp b/minifi_main/MiNiFiMain.cpp
index e9bda89ac..56fa28bc4 100644
--- a/minifi_main/MiNiFiMain.cpp
+++ b/minifi_main/MiNiFiMain.cpp
@@ -71,6 +71,8 @@
 #include "c2/C2Agent.h"
 #include "core/state/MetricsPublisherFactory.h"
 #include "core/state/MetricsPublisherStore.h"
+#include "argparse/argparse.hpp"
+#include "agent/agent_version.h"
 
 namespace minifi = org::apache::nifi::minifi;
 namespace core = minifi::core;
@@ -134,7 +136,95 @@ void writeJsonSchema(const 
std::shared_ptr<minifi::Configure> &configuration, st
   out << minifi::docs::generateJsonSchema();
 }
 
+void overridePropertiesFromCommandLine(const argparse::ArgumentParser& parser, 
const std::shared_ptr<minifi::Configure>& configure) {
+  const auto& properties = parser.get<std::vector<std::string>>("--property");
+  for (const auto& property : properties) {
+    auto property_key_and_value = 
utils::StringUtils::splitAndTrimRemovingEmpty(property, "=");
+    if (property_key_and_value.size() != 2) {
+      std::cerr << "Command line property must be defined in <key>=<value> 
format, invalid property: " << property << std::endl;
+      std::cerr << parser;
+      std::exit(1);
+    }
+    configure->set(property_key_and_value[0], property_key_and_value[1]);
+  }
+}
+
+void dumpDocsIfRequested(const argparse::ArgumentParser& parser, const 
std::shared_ptr<minifi::Configure>& configure) {
+  if (!parser.is_used("--docs")) {
+    return;
+  }
+  const auto& docs_params = parser.get<std::vector<std::string>>("--docs");
+  if (utils::file::create_dir(docs_params[0]) != 0) {
+    std::cerr << "Working directory doesn't exist and cannot be created: " << 
docs_params[0] << std::endl;
+    std::exit(1);
+  }
+
+  std::cout << "Dumping docs to " << docs_params[0] << std::endl;
+  if (docs_params.size() > 1) {
+    auto path = std::filesystem::path(docs_params[1]);
+    if (std::filesystem::exists(path) && 
!std::filesystem::is_regular_file(path)) {
+      std::cerr << "PROCESSORS.md target path exists, but it is not a regular 
file: " << path << std::endl;
+      std::exit(1);
+    }
+    auto dir = path.parent_path();
+    if (dir == docs_params[0]) {
+      std::cerr << "Target file should be out of the working directory: " << 
dir << std::endl;
+      std::exit(1);
+    }
+    std::ofstream outref(docs_params[1]);
+    dumpDocs(configure, docs_params[0], outref);
+  } else {
+    dumpDocs(configure, docs_params[0], std::cout);
+  }
+  std::exit(0);
+}
+
+void writeSchemaIfRequested(const argparse::ArgumentParser& parser, const 
std::shared_ptr<minifi::Configure>& configure) {
+  if (!parser.is_used("--schema")) {
+    return;
+  }
+  const auto& schema_path = parser.get("--schema");
+  if (std::filesystem::exists(schema_path) && 
!std::filesystem::is_regular_file(schema_path)) {
+    std::cerr << "JSON schema target path already exists, but it is not a 
regular file: " << schema_path << std::endl;
+    std::exit(1);
+  }
+
+  auto parent_dir = std::filesystem::path(schema_path).parent_path();
+  if (utils::file::create_dir(parent_dir) != 0) {
+    std::cerr << "JSON schema parent directory doesn't exist and cannot be 
created: " << parent_dir << std::endl;
+    std::exit(1);
+  }
+  std::cout << "Writing json schema to " << schema_path << std::endl;
+  {
+    std::ofstream schema_file{schema_path};
+    writeJsonSchema(configure, schema_file);
+  }
+  std::exit(0);
+}
+
 int main(int argc, char **argv) {
+  argparse::ArgumentParser argument_parser("Apache MiNiFi C++", 
minifi::AgentBuild::VERSION);
+  argument_parser.add_argument("-p", "--property")
+    .append()
+    .metavar("KEY=VALUE")
+    .help("Override a property read from minifi.properties file in key=value 
format");
+  argument_parser.add_argument("-d", "--docs")
+    .nargs(1, 2)
+    .metavar("PATH")
+    .help("Generate documentation in the directory specified in the first 
parameter. File path can be specified for the PROCESSORS.md file in the second 
parameter. "
+      "If no separate path is given for the PROCESSORS.md file, it will be 
printed to stdout.");
+  argument_parser.add_argument("-s", "--schema")
+    .metavar("PATH")
+    .help("Generate JSON schema to the specified path");
+
+  try {
+    argument_parser.parse_args(argc, argv);
+  } catch (const std::runtime_error& err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << argument_parser;
+    std::exit(1);
+  }
+
 #ifdef WIN32
   RunAsServiceIfNeeded();
 
@@ -257,64 +347,24 @@ int main(int argc, char **argv) {
     const std::shared_ptr<minifi::Configure> configure = 
std::make_shared<minifi::Configure>(std::move(decryptor), 
std::move(log_properties));
     configure->setHome(minifiHome);
     configure->loadConfigureFile(DEFAULT_NIFI_PROPERTIES_FILE);
+    overridePropertiesFromCommandLine(argument_parser, configure);
 
     minifi::core::extension::ExtensionManager::get().initialize(configure);
 
-    if (argc >= 2 && std::string("docs") == argv[1]) {
-      if (argc < 3 || argc > 4) {
-        std::cerr << "Usage: <minifiexe> docs <directory where to write 
individual doc files> [file where to write PROCESSORS.md]\n";
-        std::cerr << "    If no file name is given for PROCESSORS.md, it will 
be printed to stdout.\n";
-        exit(1);
-      }
-      if (utils::file::create_dir(argv[2]) != 0) {
-        std::cerr << "Working directory doesn't exist and cannot be created: " 
<< argv[2] << std::endl;
-        exit(1);
-      }
-
-      std::cout << "Dumping docs to " << argv[2] << std::endl;
-      if (argc == 4) {
-        auto path = std::filesystem::path(argv[3]);
-        auto dir = path.parent_path();
-        auto filename = path.filename();
-        if (dir == argv[2]) {
-          std::cerr << "Target file should be out of the working directory: " 
<< dir << std::endl;
-          exit(1);
-        }
-        std::ofstream outref(argv[3]);
-        dumpDocs(configure, argv[2], outref);
-      } else {
-        dumpDocs(configure, argv[2], std::cout);
-      }
-      exit(0);
-    }
-
-    if (argc >= 2 && std::string("schema") == argv[1]) {
-      if (argc != 3) {
-        std::cerr << "Malformed schema command, expected '<minifiexe> schema 
<output-file>'" << std::endl;
-        std::exit(1);
-      }
-
-      std::cout << "Writing json schema to " << argv[2] << std::endl;
-
-      {
-        std::ofstream schema_file{argv[2]};
-        writeJsonSchema(configure, schema_file);
-      }
-      std::exit(0);
-    }
+    dumpDocsIfRequested(argument_parser, configure);
+    writeSchemaIfRequested(argument_parser, configure);
 
     std::chrono::milliseconds stop_wait_time = 
configure->get(minifi::Configure::nifi_graceful_shutdown_seconds)
         | 
utils::flatMap(utils::timeutils::StringToDuration<std::chrono::milliseconds>)
         | utils::valueOrElse([] { return 
std::chrono::milliseconds(STOP_WAIT_TIME_MS);});
 
-
     configure->get(minifi::Configure::nifi_provenance_repository_class_name, 
prov_repo_class);
     // Create repos for flow record and provenance
     std::shared_ptr prov_repo = core::createRepository(prov_repo_class, 
"provenance");
 
     if (!prov_repo || !prov_repo->initialize(configure)) {
       logger->log_error("Provenance repository failed to initialize, 
exiting..");
-      exit(1);
+      std::exit(1);
     }
 
     configure->get(minifi::Configure::nifi_flow_repository_class_name, 
flow_repo_class);
@@ -323,7 +373,7 @@ int main(int argc, char **argv) {
 
     if (!flow_repo || !flow_repo->initialize(configure)) {
       logger->log_error("Flow file repository failed to initialize, 
exiting..");
-      exit(1);
+      std::exit(1);
     }
 
     configure->get(minifi::Configure::nifi_content_repository_class_name, 
content_repo_class);
@@ -332,13 +382,13 @@ int main(int argc, char **argv) {
 
     if (!content_repo->initialize(configure)) {
       logger->log_error("Content repository failed to initialize, exiting..");
-      exit(1);
+      std::exit(1);
     }
     const bool is_flow_repo_non_persistent = flow_repo->isNoop() || 
std::dynamic_pointer_cast<core::repository::VolatileFlowFileRepository>(flow_repo)
 != nullptr;
     const bool is_content_repo_non_persistent = 
std::dynamic_pointer_cast<core::repository::VolatileContentRepository>(content_repo)
 != nullptr;
     if (is_flow_repo_non_persistent != is_content_repo_non_persistent) {
       logger->log_error("Both or neither of flowfile and content repositories 
must be persistent! Exiting..");
-      exit(1);
+      std::exit(1);
     }
 
     std::string content_repo_path;
diff --git a/nanofi/CMakeLists.txt b/nanofi/CMakeLists.txt
index e302b89ad..d832150d8 100644
--- a/nanofi/CMakeLists.txt
+++ b/nanofi/CMakeLists.txt
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.24)
 cmake_policy(SET CMP0096 NEW) # policy to preserve the leading zeros in 
PROJECT_VERSION_{MAJOR,MINOR,PATCH,TWEAK}
 
 include_directories(include)
diff --git a/nanofi/ecu/CMakeLists.txt b/nanofi/ecu/CMakeLists.txt
index 8973fae5f..8f077e0d9 100644
--- a/nanofi/ecu/CMakeLists.txt
+++ b/nanofi/ecu/CMakeLists.txt
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.24)
 
 if (NOT WIN32)
     add_executable(log_aggregator log_aggregator.c)
diff --git a/nanofi/examples/CMakeLists.txt b/nanofi/examples/CMakeLists.txt
index 24855e930..fbd9aefec 100644
--- a/nanofi/examples/CMakeLists.txt
+++ b/nanofi/examples/CMakeLists.txt
@@ -17,7 +17,7 @@
 # under the License.
 #
 
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.24)
 
 include(CppVersion)
 set_cpp_version()


Reply via email to