commit a57343076df674064476c1104772aa70b06a463b
Author: David Sorber <david.sorber@gmail.com>
Date:   Tue Jul 5 22:07:43 2022 -0400

    Use boost::program_options for the main program.

diff --git a/software/read_test/CMakeLists.txt b/software/read_test/CMakeLists.txt
index d3b912b..7f36221 100644
--- a/software/read_test/CMakeLists.txt
+++ b/software/read_test/CMakeLists.txt
@@ -34,7 +34,7 @@ ADD_CUSTOM_TARGET(ReleaseWithDebug
 #
 # Set the compile flags
 #
-SET(CMAKE_CXX_STANDARD 14)
+SET(CMAKE_CXX_STANDARD 17)
 SET(CMAKE_CXX_STANDARD_REQUIRED ON)
 SET(CMAKE_CXX_EXTENSIONS OFF)
 
diff --git a/software/read_test/src/AIO_ExecDriver.h b/software/read_test/src/AIO_ExecDriver.h
index 870d6fc..cdfc052 100644
--- a/software/read_test/src/AIO_ExecDriver.h
+++ b/software/read_test/src/AIO_ExecDriver.h
@@ -38,17 +38,17 @@ public:
         const std::string& inputFile,
         uint32_t numReaderThreads);
     
-    virtual ~AIO_ExecDriver();
+    ~AIO_ExecDriver() override;
     
-    void start();
+    void start() override;
     
 //    void waitUntilComplete();
     
 private:
     
-    virtual void readerThreadBody(
+    void readerThreadBody(
         chunk_queue_t* inQueue,
-        chunk_queue_t* outQueue);
+        chunk_queue_t* outQueue) override;
     
 };
 
diff --git a/software/read_test/src/main.cc b/software/read_test/src/main.cc
index a8281bc..5504723 100644
--- a/software/read_test/src/main.cc
+++ b/software/read_test/src/main.cc
@@ -1,15 +1,30 @@
+#include <algorithm>
 #include <chrono>
 #include <iomanip>
 #include <iostream>
 #include <string>
 #include <vector>
 
+#include <boost/program_options.hpp>
+
 #include "AIO_ExecDriver.h"
 #include "ExecDriver.h"
 #include "Utilities/BufferAllocator.h"
 #include "Utilities/FileUtils.h"
 #include "Utilities/SearchLynxConfiguration.h"
 
+// Alias the namespace for readability
+namespace bpo = boost::program_options;
+namespace bsl = BlackLynx::SearchLynx;
+
+const std::string BOLD("\033[1m");
+const std::string ENDC("\033[0m");
+const std::string RED("\033[31m");
+const std::string YELLOW("\033[33m");
+const std::string UP_ONE = "\033[1A";
+
+const std::string VERSION("ReadTest version 1.1.0-dev");
+
 std::vector<std::string> UNITS{"b/sec", "KB/s", "MB/s", "GB/s", "TB/s"};
 
 double printRate(double rawRate, uint32_t& unitIdx)
@@ -26,33 +41,169 @@ double printRate(double rawRate, uint32_t& unitIdx)
     return rate;
 }
 
+void usage()
+{
+    std::cout << BOLD << "Read Test Utility\n\n" << ENDC;
+    std::cout << "Usage: read_test <--help | -h>\n";
+    std::cout << "       read_test --version\n";
+}
+
+template <typename T>
+void lowercase(std::basic_string<T>& str)
+{    
+    std::transform(str.begin(), str.end(), str.begin(), tolower);
+}
+
+// Type used to set "read-type" value
+enum class ReadType
+{
+    DEFAULT_INVALID,
+    AUTO,
+    NORMAL,
+    AIO
+};
+
+struct ReadTypeValue 
+{
+    ReadTypeValue()
+    {
+    }
+    
+    ReadTypeValue(std::string const& val)
+        : value(val)
+    { 
+    }
+    
+    ReadType getValue()
+    {
+        if (value == "auto")
+        {
+            return ReadType::AUTO;
+        }
+        else if (value == "normal")
+        {
+            return ReadType::NORMAL;
+        }
+        else if (value == "aio")
+        {
+            return ReadType::AIO;
+        }
+        return ReadType::DEFAULT_INVALID;
+    }
+    
+    std::string value;
+};
+
+void validate(
+    boost::any& v, 
+    std::vector<std::string> const& values,
+    ReadTypeValue*,
+    int)
+{
+    // Make sure no previous assignment to 'v' was made.
+    bpo::validators::check_first_occurrence(v);
+    // Extract the first string from 'values'. If there is more than one string,
+    // it's an error, and exception will be thrown.
+    std::string val = bpo::validators::get_single_string(values);    
+//    BlackLynx::SearchLynx::CharacterUtilities::lowercaseInPlace(val);
+    lowercase(val);
+    // Check for valid values
+    if (val == "auto" || val == "normal" || val == "aio")
+    {
+        v = boost::any(ReadTypeValue(val));
+    } 
+    else 
+    {
+        throw bpo::validation_error(bpo::validation_error::invalid_option_value);
+    }
+}
 
 int main(int argc, char** argv) 
 {
-    std::cout << "Read Test Utility\n" << std::endl;
+    bpo::variables_map optionsMap;
+    ReadTypeValue readType("default");
+    std::string filename;
     
-    if (argc < 2)
+    try
+    {       
+        // General options
+        bpo::options_description generalOpts("General options");
+        generalOpts.add_options()
+            ("help,h", "display this help message")
+                
+            ("version", 
+             "output version number and exit")
+                
+            ("read-type", bpo::value<ReadTypeValue>(&readType),
+             "read type")
+        
+            ("input-file", bpo::value<std::string>(&filename)->required(), 
+             "input file")
+            ;
+     
+        // Positional options
+        bpo::positional_options_description posOpts;
+        posOpts.add("input-file", 1);
+        
+        bpo::parsed_options parsed = bpo::command_line_parser(argc, argv).
+                                        options(generalOpts).positional(posOpts)
+                                        .run();
+        
+        bpo::store(parsed, optionsMap);
+        
+        // Display help if the option is listed or if no arguments are provided
+        if (optionsMap.count("help"))// || (argc == 1)) 
+        {
+            usage();
+            return 1;
+        }
+        
+        // Display version and exist if option listed
+        if (optionsMap.count("version"))
+        {
+            std::cout << VERSION << std::endl;
+            return 2;
+        }
+        
+        // Notify any errors
+        bpo::notify(optionsMap);
+    }
+    catch (std::exception& e) 
     {
-        std::cerr << "ERROR: please provide an input file" << std::endl;
+        std::cerr << BOLD << RED << "\nERROR: " << ENDC << e.what() << "\n"
+                  << std::endl;
         return -1;
     }
+    
+    std::cout << "Read Test Utility\n" << std::endl;
+    std::cout << "Read type:    " << readType.value << "\n";
+    std::cout << "Input file:   " << filename << "\n";
 
     // Start time measurement
     auto start = std::chrono::system_clock::now();
     
-    // Prime the config object
-    auto slConfig = BlackLynx::SearchLynx::SearchLynxConfiguration::getInstance();
-    
-    // Allocate buffers
-    auto bufAllocator = BlackLynx::SearchLynx::BufferAllocator::getInstance();
+    // Prime the config object then allocate buffers
+    auto slConfig = bsl::SearchLynxConfiguration::getInstance();    
+    auto bufAllocator = bsl::BufferAllocator::getInstance();
     bufAllocator->initAllocation();
     
-    // Read in the file
-    std::string filename(argv[1]);
-//    BlackLynx::SearchLynx::ExecDriver driver(filename, 22);
-    BlackLynx::SearchLynx::AIO_ExecDriver driver(filename, 3);
-    driver.start();
-    driver.waitUntilComplete();
+    // Use the read type argument to decide which ExecDriver instance to create
+    bsl::ExecDriver* driver = nullptr;
+    switch (readType.getValue())
+    {
+        case ReadType::DEFAULT_INVALID:
+        case ReadType::AUTO:
+        case ReadType::AIO:
+            driver = new bsl::AIO_ExecDriver(filename, 2);
+            break;
+            
+        case ReadType::NORMAL:
+            driver = new bsl::ExecDriver(filename, 22);
+            break;
+    }
+    
+    driver->start();
+    driver->waitUntilComplete();
     
     // Deallocate buffers
     bufAllocator->deallocateBuffers();
@@ -61,18 +212,22 @@ int main(int argc, char** argv)
     auto end = std::chrono::system_clock::now();
     auto diff = end - start;
     double duration = std::chrono::duration <double>(diff).count() ;
-    std::cout << "Duration: "  << std::fixed << std::setprecision(3) 
+    std::cout << "Duration:     "  << std::fixed << std::setprecision(3) 
               << duration << " ms" << std::endl;
 
-    uint64_t fileSize = BlackLynx::SearchLynx::FileUtils::getFileSize(filename);
+    uint64_t fileSize = bsl::FileUtils::getFileSize(filename);
     
     double rawRate = fileSize / duration;
-    std::cout << "Raw rate: "  << rawRate << " b/sec" << std::endl;
+    std::cout << "Raw rate:     "  << rawRate << " b/sec" << std::endl;
     
     uint32_t idx = 0;
     double rate = printRate(rawRate, idx);
-    std::cout << "Rate:     "  << std::fixed << std::setprecision(3) << rate 
+    std::cout << "Rate:         "  << std::fixed << std::setprecision(3) << rate 
               << " " << UNITS[idx] << std::endl;
     
+    // Clean up
+    delete driver;
+    driver = nullptr;
+    
     return 0;
 }
