From 11a2384c53a0155726f2787b2ed48b6e29110896 Mon Sep 17 00:00:00 2001 From: pgdalmeida Date: Mon, 22 Jun 2026 12:51:57 +0200 Subject: [PATCH] Modularize into interfaces with real + mock implementations --- CMakeLists.txt | 18 +- Camera.cpp | 433 ---- Camera.h | 110 - JPEG_XL.h | 110 - Log.h | 3 - MQTT.cpp | 155 -- MQTT.h | 157 -- Parser.h | 102 - Serial.h | 144 -- cxxopts.hpp | 2908 ------------------------ include/fgc/Application.h | 41 + include/fgc/CommandParser.h | 28 + include/fgc/ICameraSource.h | 3 + include/fgc/ImagePipeline.h | 66 + include/fgc/JpegXlEncoder.h | 20 + include/fgc/MqttControlChannel.h | 62 + include/fgc/SerialMotorController.h | 30 + include/fgc/VimbaCameraSource.h | 34 + include/fgc/mock/MockCameraSource.h | 57 + include/fgc/mock/MockMotorController.h | 47 + include/fgc/mock/NullControlChannel.h | 26 + main.cpp | 407 +--- src/camera/ImagePipeline.cpp | 106 + src/camera/JpegXlEncoder.cpp | 98 + src/camera/VimbaCameraSource.cpp | 161 ++ src/core/Application.cpp | 238 ++ src/core/CommandParser.cpp | 56 + src/mqtt/MqttControlChannel.cpp | 123 + src/serial/SerialMotorController.cpp | 102 + timing.h | 116 - 30 files changed, 1381 insertions(+), 4580 deletions(-) delete mode 100644 Camera.cpp delete mode 100644 Camera.h delete mode 100644 JPEG_XL.h delete mode 100644 Log.h delete mode 100644 MQTT.cpp delete mode 100644 MQTT.h delete mode 100644 Parser.h delete mode 100644 Serial.h delete mode 100644 cxxopts.hpp create mode 100644 include/fgc/Application.h create mode 100644 include/fgc/CommandParser.h create mode 100644 include/fgc/ImagePipeline.h create mode 100644 include/fgc/JpegXlEncoder.h create mode 100644 include/fgc/MqttControlChannel.h create mode 100644 include/fgc/SerialMotorController.h create mode 100644 include/fgc/VimbaCameraSource.h create mode 100644 include/fgc/mock/MockCameraSource.h create mode 100644 include/fgc/mock/MockMotorController.h create mode 100644 include/fgc/mock/NullControlChannel.h create mode 100644 src/camera/ImagePipeline.cpp create mode 100644 src/camera/JpegXlEncoder.cpp create mode 100644 src/camera/VimbaCameraSource.cpp create mode 100644 src/core/Application.cpp create mode 100644 src/core/CommandParser.cpp create mode 100644 src/mqtt/MqttControlChannel.cpp create mode 100644 src/serial/SerialMotorController.cpp delete mode 100644 timing.h diff --git a/CMakeLists.txt b/CMakeLists.txt index be8bc55..6a11bc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ add_library(fgc_core STATIC src/core/Logger.cpp src/core/TelemetryParser.cpp src/core/CaptureScheduler.cpp + src/core/CommandParser.cpp ini.c ) target_include_directories(fgc_core PUBLIC @@ -55,14 +56,23 @@ target_include_directories(fgc_core PUBLIC target_link_libraries(fgc_core PUBLIC Threads::Threads) # --------------------------------------------------------------------------- -# Application executable (current flat layout; restructured into src/ in a -# later phase). Pulls in the heavy/proprietary deps. +# Application executable. Camera image pipeline + serial controller always +# build (OpenCV/libjxl/Boost.Asio are available everywhere); MQTT and Vimba +# sources are added only when their option is ON. # --------------------------------------------------------------------------- set(FGC_SOURCES main.cpp - MQTT.cpp - Camera.cpp + src/core/Application.cpp + src/camera/JpegXlEncoder.cpp + src/camera/ImagePipeline.cpp + src/serial/SerialMotorController.cpp ) +if(WITH_MQTT) + list(APPEND FGC_SOURCES src/mqtt/MqttControlChannel.cpp) +endif() +if(WITH_VIMBA) + list(APPEND FGC_SOURCES src/camera/VimbaCameraSource.cpp) +endif() add_executable(fire_gimbal_control ${FGC_SOURCES}) target_include_directories(fire_gimbal_control PRIVATE ${CMAKE_SOURCE_DIR}) diff --git a/Camera.cpp b/Camera.cpp deleted file mode 100644 index 91621aa..0000000 --- a/Camera.cpp +++ /dev/null @@ -1,433 +0,0 @@ -#include "Camera.h" -#include "JPEG_XL.h" -#include "timing.h" -#include -#include -#include -#include - - -class FrameObserver : public IFrameObserver -{ -public: - typedef std::function Callback; - Callback call; - int cam_id = 0; - std::atomic settle = { 3 }; - - void registerCallback(Callback f) { - call = f; - } - - FrameObserver(CameraPtr camera) : IFrameObserver(camera) {}; - - void FrameReceived(const FramePtr pFrame) - { - int old_val = settle.fetch_sub(1); - std::cout << "[FrameObserver cam=" << cam_id << "] FrameReceived settle=" << old_val << " → new=" << old_val - 1 << std::endl; - - if (old_val > 0) { - std::cout << "[FrameObserver cam=" << cam_id << "] DUMP (settling)" << std::endl; - m_pCamera->QueueFrame(pFrame); - return; - } - - bool bQueueDirectly = true; - VmbFrameStatusType eReceiveStatus; - - if (VmbErrorSuccess == pFrame->GetReceiveStatus(eReceiveStatus)) - { - /* ignore any incompletely frame */ - if (VmbFrameStatusComplete != eReceiveStatus) - { - VmbUint64_t id; - std::string name; - std::string serial; - pFrame->GetFrameID(id); - m_pCamera->GetModel(name); - m_pCamera->GetSerialNumber(serial); - std::cout << "!!!incomplete frame, rcvd error: " << eReceiveStatus << " id: " << id << " camera: " << name << " + " << serial << std::endl; - m_pCamera->QueueFrame(pFrame); - return; - } - VmbUint64_t id; - pFrame->GetFrameID(id); - std::cout << "[FrameObserver cam=" << cam_id << "] rcvd frame ID=" << id << " → ENQUEUE" << std::endl; - // Lock the frame queue - m_FramesMutex.lock(); - // Add frame to queue - m_Frames.push(pFrame); - // Unlock frame queue - m_FramesMutex.unlock(); - // Emit the frame received signal - call(cam_id); - // callback! - bQueueDirectly = false; - } - else { - std::cout << "[FrameObserver cam=" << cam_id << "] frame rcvd error" << std::endl; - } - - // If any error occurred we queue the frame without notification - if (true == bQueueDirectly) - { - m_pCamera->QueueFrame(pFrame); - } - }; - - FramePtr GetFrame() { - FramePtr res; - // Lock the frame queue - m_FramesMutex.lock(); - // Pop frame from queue - if (!m_Frames.empty()) - { - res = m_Frames.front(); - m_Frames.pop(); - } - // Unlock frame queue - m_FramesMutex.unlock(); - return res; - } - - void QueueF(FramePtr& frame) { - m_pCamera->QueueFrame(frame); - } - - void ClearFrameQueue() { - // Lock the frame queue - m_FramesMutex.lock(); - std::queue empty; - std::swap(m_Frames, empty); - m_FramesMutex.unlock(); - } -private: - std::queue m_Frames; - std::mutex m_FramesMutex; -}; - -VimbaHandler::VimbaHandler(std::vector cameraIds, MQTTClient* mqtt_c, motor_info* motor_i, bool demo) : - m_vmbSystem(VmbSystem::GetInstance()), mqtt_client(mqtt_c), gimbal_data(motor_i), demo_flag(demo) -{ - VmbErrorType err = m_vmbSystem.Startup(); - - if (err != VmbErrorSuccess) - { - throw std::runtime_error("Could not start API, err=" + std::to_string(err)); - } - - //CameraPtrVector cameras; - //err = m_vmbSystem.GetCameras(cameras); - //if (err != VmbErrorSuccess) - //{ - // m_vmbSystem.Shutdown(); - // throw std::runtime_error("Could not get cameras, err=" + std::to_string(err)); - //} - //if (cameras.empty()) - //{ - // m_vmbSystem.Shutdown(); - // throw std::runtime_error("No cameras found."); - //} - - if (cameraIds.size() > 0) - { - int id = 0; - - for (std::string cameraId : cameraIds) - { - CameraPtr cam; - err = m_vmbSystem.GetCameraByID(cameraId.c_str(), cam); - if (err != VmbErrorSuccess) - { - m_vmbSystem.Shutdown(); - throw std::runtime_error("No camera found with ID=" + std::string(cameraId) + ", err = " + std::to_string(err)); - } - else { - m_cameras.push_back(cam); - } - } - } - - //else - //{ - // m_camera = cameras[0]; - //} - for (CameraPtr camptr : m_cameras) - { - err = camptr->Open(VmbAccessModeFull); - if (err != VmbErrorSuccess) - { - m_vmbSystem.Shutdown(); - throw std::runtime_error("Could not open camera, err=" + std::to_string(err)); - } - else { - std::string name; - if (camptr->GetName(name) == VmbErrorSuccess) - { - std::cout << "Opened Camera " << name << std::endl; - } - } - } - -} - - - -VimbaHandler::~VimbaHandler() -{ - try - { - Stop(); - } - catch (std::runtime_error& e) - { - std::cout << e.what() << std::endl; - } - - m_vmbSystem.Shutdown(); -} - -void VimbaHandler::Open() -{ -} - -void VimbaHandler::Close() -{ -} - -void VimbaHandler::Start() -{ - save_thread_running = true; - std::cout << "starting Camera thread" << std::endl; - image_saver_thread = std::thread(&VimbaHandler::SaveImage, this); - int cam_id = 0; - for (CameraPtr cam : m_cameras) { - IFrameObserverPtr FO_ptr; - ImageQueue8 image_queue; - m_ImageQueue_vec.push_back(image_queue); - SP_SET(FO_ptr, new FrameObserver(cam)); - SP_DYN_CAST(FO_ptr)->cam_id = cam_id; - cam_id++; - VmbErrorType err = cam->StartContinuousImageAcquisition(5, FO_ptr); - FO_ptr_vec.push_back(FO_ptr); - if (err != VmbErrorSuccess) - { - throw std::runtime_error("Could not start acquisition, err=" + std::to_string(err)); - } - else { - - SP_DYN_CAST(FO_ptr)->registerCallback(std::bind(&VimbaHandler::EnqueueToStoreStruct, this, std::placeholders::_1)); - } - } - cam_started = true; -} - -void VimbaHandler::Stop() -{ - for (CameraPtr cam : m_cameras) { - VmbErrorType err = cam->StopContinuousImageAcquisition(); - if (err != VmbErrorSuccess) - { - throw std::runtime_error("Could not stop acquisition, err=" + std::to_string(err)); - } - } - cam_started = false; - save_thread_running = false; - if (image_saver_thread.joinable()) { - image_saver_thread.join(); - } -} - -void VimbaHandler::evaluateCommand(std::string cmd, double val) -{ - if (cmd == "fps" && val > 0) - ChangeFramerate(val); - else if (cmd == "jxlq" && val > 0) - jxlq = val; - else if (cmd == "jxle" && val > 0) - jxle = val; - else if (cmd == "display" && val >= 0) - display_image = int(val); -} - -void VimbaHandler::EnqueueToStoreStruct(int cam_id) -{ - FramePtr frame = SP_DYN_CAST(FO_ptr_vec[cam_id])->GetFrame(); - - VmbUint32_t Width; - VmbUint32_t Height; - VmbUint32_t BufferSize; - VmbPixelFormatType PixelFormat; - const VmbUchar_t* pBuffer(NULL); - - if (VmbErrorSuccess == frame->GetPixelFormat(PixelFormat) - && VmbErrorSuccess == frame->GetWidth(Width) - && VmbErrorSuccess == frame->GetHeight(Height) - && VmbErrorSuccess == frame->GetBufferSize(BufferSize) - && VmbErrorSuccess == frame->GetBuffer(pBuffer)) - { - NanoUnixTimer time; - long long tstamp = time.Stamp_longlong(); - std::lock_guard lg(queue_mut); - ImageStore8Ptr pFrame; - // in case we reached the maximum number of queued frames - // take of the oldest and reuse it to store the newly arriving frame - - std::vector data_in = std::vector(pBuffer, pBuffer + BufferSize); - if (m_ImageQueue_vec[cam_id].size() >= 100) - { - pFrame = m_ImageQueue_vec[cam_id].front(); - m_ImageQueue_vec[cam_id].pop(); - if (!pFrame->equal(Width, Height, PixelFormat)) - { - pFrame.reset(); - } - } - if (pFrame == NULL) - { - pFrame = ImageStore8Ptr(new image_store_8bit(data_in.data(), data_in.size(), Width, Height, PixelFormat, tstamp, cam_id)); - } - else - { - pFrame->setData(data_in.data(), data_in.size(), tstamp); - } - m_ImageQueue_vec[cam_id].push(pFrame); - - SP_DYN_CAST(FO_ptr_vec[cam_id])->QueueF(frame); - std::cout << "Enqueue finished Camera:" << cam_id << " -- Queue size: " << m_ImageQueue_vec[cam_id].size() << std::endl; - if (cam_id == 0) - cv_proc.notify_one(); - } -} - -void VimbaHandler::SaveImage() -{ - while (save_thread_running) { - std::unique_lock lock(proc_wait_mut); - cv_proc.wait(lock); - ImageStore8Ptr pFrame; - for (ImageQueue8& imageQueue : m_ImageQueue_vec) { - while (imageQueue.size() > 0 && save_jxl) - { - Timer time; - queue_mut.lock(); - pFrame = imageQueue.front(); - imageQueue.pop(); - queue_count_rgb = imageQueue.size(); - queue_mut.unlock(); - - - int imagetype; - int imagechannels; - if (pFrame->dataSize() / (pFrame->height() * pFrame->width()) == 1) { - imagetype = CV_8UC1; - imagechannels = 1; - } - else if (pFrame->dataSize() / (pFrame->height() * pFrame->width()) == 3) { - imagetype = CV_8UC3; - imagechannels = 3; - } - - cv::Mat img_to_rotate = cv::Mat(pFrame->height(), pFrame->width(), imagetype, pFrame->data()); - cv::Mat img; - cv::rotate(img_to_rotate, img, cv::ROTATE_90_COUNTERCLOCKWISE); - if (display_image) { - cv::namedWindow("Display Image", cv::WINDOW_NORMAL); - cv::resizeWindow("Display Image", img.cols / 4, img.rows / 4); - cv::imshow("Display Image", img); - cv::waitKey(10); - } - std::string cameraname; - if (pFrame->getCamId() == 0) - cameraname = "RGB"; - if (pFrame->getCamId() == 1) - cameraname = "ACR"; - if (pFrame->getCamId() == 2) - cameraname = "NIR"; - std::string filename = output_dir + "/" + cameraname + "/" + std::to_string(pFrame->getTimestamp()) + ".jxl"; - std::filesystem::path path(filename); - // Extract the directory part of the path - std::filesystem::path dir = path.parent_path(); - // Create directories if they don't exist - if (!dir.empty() && !std::filesystem::exists(dir)) { - std::filesystem::create_directories(dir); - std::cout << "Created directories: " << dir << std::endl; - } - if (!demo_flag) { - JPEGXL jxl_writer(img.cols, img.rows, img.data, imagechannels, (float)jxlq, (int)jxle); - jxl_writer.WriteFile(filename.c_str()); - std::cout << "Camera: " + std::to_string(pFrame->getCamId()) + " -- Compress TIME:" << std::to_string(time.ElapsedMillis()) << " -- SaveQueue size:" << imageQueue.size() << std::endl; - } - else { - std::filesystem::copy("test_smoke.jxl", filename); - } - time.Reset(); - // NOTE: optional network upload of saved images to the ground - // station was removed here; it had hardcoded NFS/SMB paths. If - // reintroduced it should read its destination from config. - std::string payload = "{ \"fwt\":\"" + fwt_name + "\" ,\"cam\":\"" + cameraname + "\", \"hdg\":" + std::to_string((int)(gimbal_data->hdg * 10)) + ", \"time\":" + std::to_string(pFrame->getTimestamp()) + " }"; - mqtt_client->publish(mqtt_RGB, payload); - } - } - } -} - -bool VimbaHandler::ChangeFramerate(double fr) -{ - VmbErrorType result; - for (CameraPtr cam : m_cameras) { - FeaturePtr pFeature; - result = SP_ACCESS(cam)->GetFeatureByName("AcquisitionFrameRate", pFeature); - if (result == VmbErrorSuccess) - result = pFeature->SetValue(fr); - if (result == VmbErrorSuccess) { - std::cout << "camera fps changed: " << fr << std::endl; - } - } - if (result == VmbErrorSuccess) { - return true; - } - else { - std::cout << "camera fps change failed with error:" << result << std::endl; - return false; - } -} - -void VimbaHandler::TriggerSettle() -{ - for (auto& fo : FO_ptr_vec) { - SP_DYN_CAST(fo)->settle.store(3); - } -} - -bool VimbaHandler::TriggerCamera() -{ - TriggerSettle(); - std::cout << "[TriggerCamera] settle reset to 3, firing 4 triggers (3 settle + 1 real)..." << std::endl; - - FeaturePtr pFeature; - VmbErrorType result; - - for (int i = 0; i < 4; i++) { - result = SP_ACCESS(m_cameras[0])->GetFeatureByName("TriggerSoftware", pFeature); - if (result == VmbErrorSuccess) - result = pFeature->RunCommand(); - int sl = SP_DYN_CAST(FO_ptr_vec[0])->settle.load(); - std::cout << "[TriggerCamera] trigger #" << (i+1) << " settle_before=" << sl << " result=" << result << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(400)); - } - - std::cout << "[TriggerCamera] done, 4th frame should be real image" << std::endl; - return (result == VmbErrorSuccess); -} - -void VimbaHandler::SetTowerName(std::string name) -{ - fwt_name = name; - mqtt_RGB = "GGS/FWT/" + fwt_name + "/CamEvent"; -} - -void VimbaHandler::SetOutputDir(std::string dir) -{ - output_dir = std::move(dir); -} diff --git a/Camera.h b/Camera.h deleted file mode 100644 index 0d7657a..0000000 --- a/Camera.h +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include "MQTT.h" -#include "Serial.h" -using namespace VmbCPP; - -struct image_store_8bit -{ -private: - typedef std::vector data_vec; - data_vec m_Data; - VmbUint32_t m_Width; // frame width - VmbUint32_t m_Height; // frame height - VmbPixelFormat_t m_PixelFormat; // frame pixel format - long long m_timestamp; - int m_cam_id; -public: - image_store_8bit(const VmbUchar_t* pBuffer, VmbUint32_t BufferByteSize, VmbUint32_t Width, VmbUint32_t Height, VmbPixelFormatType PixelFormat, long long stamp, int cam_id) - : m_Data(pBuffer, pBuffer + BufferByteSize) - , m_Width(Width) - , m_Height(Height) - , m_PixelFormat(PixelFormat) - , m_timestamp(stamp) - , m_cam_id(cam_id) - { - } - - long long getTimestamp() { - return(m_timestamp); - } - - bool equal(VmbUint32_t Width, VmbUint32_t Height, VmbPixelFormat_t PixelFormat) const - { - return m_Width == Width - && m_Height == Height - && m_PixelFormat == PixelFormat; - } - - bool setData(const VmbUchar_t* Buffer, VmbUint32_t BufferSize, long long stamp) - { - if (BufferSize == dataSize()) - { - std::copy(Buffer, Buffer + BufferSize, m_Data.begin()); - m_timestamp = stamp; - return true; - } - return false; - } - VmbPixelFormat_t pixelFormat() const { return m_PixelFormat; } - VmbUint32_t width() const { return m_Width; } - VmbUint32_t height() const { return m_Height; } - VmbUint32_t dataSize() const { return static_cast(m_Data.size()); } - const VmbUchar_t* data() const { return &*m_Data.begin(); } - VmbUchar_t* data() { return &*m_Data.begin(); } - int getCamId() { return m_cam_id; } -}; - -class VimbaHandler -{ -public: - VimbaHandler(std::vector cameraIds, MQTTClient* mqtt_c, motor_info* motor_i, bool demo); - ~VimbaHandler(); - void Open(); - void Close(); - void Start(); - void Stop(); - void evaluateCommand(std::string cmd, double val); - void EnqueueToStoreStruct(int cam_id); - void SaveImage(); - bool ChangeFramerate(double fr); - bool TriggerCamera(); - void TriggerSettle(); - void SetTowerName(std::string name); - void SetOutputDir(std::string dir); - - bool cam_started = false; - int queue_count_rgb = 0; -private: - bool demo_flag = false; - double jxlq = 2.0; - double jxle = 3.0; - std::string fwt_name = "Rietschen";//"Dev"; //TODO: put in config - std::string output_dir; // base directory for saved images (set from config) - std::string mqtt_RGB = "GGS/FWT/" + fwt_name + "/CamEvent"; - motor_info* gimbal_data; - MQTTClient* mqtt_client; - typedef std::shared_ptr ImageStore8Ptr; - typedef std::queue ImageQueue8; - std::vector m_ImageQueue_vec; - std::mutex queue_mut; - std::mutex proc_wait_mut; - std::condition_variable cv_proc; - bool save_thread_running = false; - bool save_jxl = true; - bool display_image = false; - std::thread image_saver_thread; - - std::vector FO_ptr_vec; - VmbSystem& m_vmbSystem; - CameraPtrVector m_cameras; - -}; - diff --git a/JPEG_XL.h b/JPEG_XL.h deleted file mode 100644 index c47df81..0000000 --- a/JPEG_XL.h +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once -#include -#include // for fopen, fclose -#include -#include -#include -#include -#include -#include -#include -#include -class JPEGXL -{ -public: - JPEGXL(int width, int height, const unsigned char* img, int num_channels, float q, int e, int th = 3) :m_width(width), m_height(height), m_num_channels(num_channels), m_quality(q), m_efford(e) - { - auto enc = JxlEncoderMake(/*memory_manager=*/nullptr); - - auto runner = JxlThreadParallelRunnerMake(nullptr, th); - if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc.get(), - JxlThreadParallelRunner, - runner.get())) { - fprintf(stderr, "JxlEncoderSetParallelRunner failed\n"); - } - - JxlPixelFormat pixel_format = { m_num_channels, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 }; - JxlBasicInfo basic_info; - JxlEncoderInitBasicInfo(&basic_info); - basic_info.xsize = m_width; - basic_info.ysize = m_height; - basic_info.alpha_bits = 0; - basic_info.bits_per_sample = 8; - basic_info.num_color_channels = m_num_channels; - basic_info.uses_original_profile = false; - - if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc.get(), &basic_info)) { - fprintf(stderr, "JxlEncoderSetBasicInfo failed\n"); - return; - } - JxlColorEncoding color_encoding = {}; - JxlColorEncodingSetToSRGB(&color_encoding,/*is_gray=*/pixel_format.num_channels < 3); - if (JXL_ENC_SUCCESS != JxlEncoderSetColorEncoding(enc.get(), &color_encoding)) { - fprintf(stderr, "JxlEncoderSetColorEncoding failed\n"); - return; - } - JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), nullptr); - JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, e); - JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, 0); - - if (q == 0) {//if lossless - JxlEncoderSetFrameDistance(frame_settings, 0); - JxlEncoderSetFrameLossless(frame_settings, true); - } - else { - JxlEncoderSetFrameLossless(frame_settings, false); - JxlEncoderSetFrameDistance(frame_settings, q); - } - if (JXL_ENC_SUCCESS != JxlEncoderAddImageFrame(frame_settings, &pixel_format, (void*)img, sizeof(uint8_t) * m_width * m_height * m_num_channels)) { - fprintf(stderr, "JxlEncoderAddImageFrame failed\n"); - return; - } - JxlEncoderCloseInput(enc.get()); - std::vector* compressed = &compressed_data; - compressed->resize(64); - uint8_t* next_out = compressed->data(); - size_t avail_out = compressed->size() - (next_out - compressed->data()); - JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; - while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { - process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); - if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { - size_t offset = next_out - compressed->data(); - compressed->resize(compressed->size() * 2); - next_out = compressed->data() + offset; - avail_out = compressed->size() - offset; - } - } - compressed->resize(next_out - compressed->data()); - if (JXL_ENC_SUCCESS != process_result) { - fprintf(stderr, "JxlEncoderProcessOutput failed\n"); - return; - } - } - ~JPEGXL() {} - - bool WriteFile(const char* filename) { - FILE* file = fopen(filename, "wb"); - if (!file) { - fprintf(stderr, "Could not open %s for writing\n", filename); - return false; - } - if (fwrite(compressed_data.data(), sizeof(uint8_t), compressed_data.size(), file) != - compressed_data.size()) { - fprintf(stderr, "Could not write bytes to %s\n", filename); - fclose(file); - return false; - } - if (fclose(file) != 0) { - fprintf(stderr, "Could not close %s\n", filename); - return false; - } - return true; - } -private: - int m_width; - int m_height; - int m_num_channels; - float m_quality; - int m_efford; - std::vector compressed_data; -}; diff --git a/Log.h b/Log.h deleted file mode 100644 index 562cc26..0000000 --- a/Log.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once -// Legacy shim: the project's logging now lives in fgc/Logger.h. -#include "fgc/Logger.h" diff --git a/MQTT.cpp b/MQTT.cpp deleted file mode 100644 index 6fe55d2..0000000 --- a/MQTT.cpp +++ /dev/null @@ -1,155 +0,0 @@ -#include "MQTT.h" - -const int QOS = 1; -const int N_RETRY_ATTEMPTS = 5; - - -void MQTTCallback::on_failure(const mqtt::token& tok) -{ - std::cout << "Connection failed" << std::endl; - if (++nretry_ > N_RETRY_ATTEMPTS) - std::cout << "Connection attempt failed already a few times" << std::endl; - reconnect(); -} - - -// (Re)connection success -void MQTTCallback::connected(const std::string& cause) { - std::cout << "\nConnection success" << std::endl; - std::cout << "\nSubscribing to topics.." << std::endl; - - cli_.subscribe(tpoic_target_hdg, QOS, nullptr, subListener_); - cli_.subscribe(topic_control_mode, QOS, nullptr, subListener_); -} - -void MQTTCallback::message_arrived(mqtt::const_message_ptr msg) { - std::cout << "Message arrived" << std::endl; - std::cout << "\ttopic: '" << msg->get_topic() << "'" << std::endl; - std::cout << "\tpayload: '" << msg->to_string() << "'\n" << std::endl; - if (msg->get_topic() == "GGS/FWT/" + fwt_name + "/target_HDG") { - try { - int value = static_cast(std::stoi(msg->to_string())); - std::unique_lock ul(mqtt_mut); - sub_data.set_target_heading(msg->to_string()); - } - catch (const std::invalid_argument& e) { - std::cerr << "MQTT type convertion invalid argument: " << e.what() << std::endl; - } - catch (const std::out_of_range& e) { - std::cerr << "MQTT type convertion out of range: " << e.what() << std::endl; - } - } - else if (msg->get_topic() == "GGS/FWT/" + fwt_name + "/ControlCode") { - try { - std::unique_lock ul(mqtt_mut); - sub_data.set_control_code(static_cast(std::stoi(msg->to_string()))); - } - catch (const std::invalid_argument& e) { - std::cerr << "MQTT type convertion invalid argument: " << e.what() << std::endl; - } - catch (const std::out_of_range& e) { - std::cerr << "MQTT type convertion out of range: " << e.what() << std::endl; - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////7x - -MQTTClient::MQTTClient(const std::string& serverURI, const std::string& clientId, std::string tower_name, std::string login_user, std::string login_pw) - : client(serverURI, clientId), callback(client, connOpts, tower_name) -{ - connOpts.set_keep_alive_interval(20); - connOpts.set_clean_session(true); - connOpts.set_user_name(login_user); - connOpts.set_password(login_pw); - std::cout << "MQTT object created" << std::endl; - client.set_callback(callback); - std::cout << "MQTT callback set" << std::endl; -} - -MQTTClient::~MQTTClient() { - if (client.is_connected()) - disconnect(); -} - -void MQTTClient::connect_client() { - std::cout << "connecting MQTT..." << std::endl; - running = true; - mqtt_thread = std::thread(&MQTTClient::run, this); - -} - -void MQTTClient::run() { - const auto TIMEOUT = std::chrono::seconds(5); - try { - conToken = client.connect(connOpts, nullptr, callback); - conToken->wait_for(TIMEOUT); - connected = true; - } - catch (const mqtt::exception& exc) { - std::cerr << "Error: " << exc.what() << std::endl; - running = false; - connected = false; - return; - } - std::cout << "MQTT connected" << std::endl; - - while (running) - ; -} - -void MQTTClient::disconnect() { - // Disconnect - try { - std::cout << "\nDisconnecting from the MQTT server..." << std::flush; - client.disconnect()->wait(); - std::cout << "OK" << std::endl; - } - catch (const mqtt::exception& exc) { - std::cerr << exc.what() << std::endl; - } - running = false; - mqtt_thread.join(); -} - -void MQTTClient::publish(const std::string& topic, const std::string& payload) { - const auto TIMEOUT = std::chrono::seconds(3); - mqtt::delivery_token_ptr pubtok; - mqtt::message_ptr msg = mqtt::make_message(topic, payload); - msg->set_qos(1); - msg->set_retained(true); - try { - pubtok = client.publish(msg); - pubtok->wait_for(TIMEOUT); - } - catch (const mqtt::exception& exc) { - std::cerr << "Error: " << exc.what() << std::endl; - } -} - -void MQTTClient::subscribe(const std::string& topic) { - try { - subToken = client.subscribe(topic, 1); - subToken->wait(); - } - catch (const mqtt::exception& exc) { - std::cerr << "Error: " << exc.what() << std::endl; - } -} - -void MQTTClient::receiveMessages() { - try { - client.start_consuming(); - } - catch (const mqtt::exception& exc) { - std::cerr << "Error: " << exc.what() << std::endl; - } - while (true) { - - } -} - -bool MQTTClient::is_connected_to_server() -{ - return client.is_connected(); -} diff --git a/MQTT.h b/MQTT.h deleted file mode 100644 index e286b52..0000000 --- a/MQTT.h +++ /dev/null @@ -1,157 +0,0 @@ -#pragma once - -#include -#include -#include -#include -struct mqtt_sub_data -{ - bool ctl_avail = false; - bool hdg_avail = false; - std::string target_heading=""; - int control_code = 0; - void set_control_code(int c) { - ctl_avail = true; - control_code = c; - } - void set_target_heading(std::string target) { - hdg_avail = true; - target_heading = target; - } -}; - -// Callbacks for the success or failures of requested actions. -// This could be used to initiate further action, but here we just log the -// results to the console. -class action_listener : public virtual mqtt::iaction_listener -{ - std::string name_; - - void on_failure(const mqtt::token& tok) override { - std::cout << name_ << " failure"; - if (tok.get_message_id() != 0) - std::cout << " for token: [" << tok.get_message_id() << "]" << std::endl; - std::cout << std::endl; - } - - void on_success(const mqtt::token& tok) override { - std::cout << name_ << " success"; - if (tok.get_message_id() != 0) - std::cout << " for token: [" << tok.get_message_id() << "]" << std::endl; - auto top = tok.get_topics(); - if (top && !top->empty()) - std::cout << "\ttoken topic: '" << (*top)[0] << "', ..." << std::endl; - std::cout << std::endl; - } - -public: - action_listener(const std::string& name) : name_(name) {} -}; - -///////////////////////////////////////////////////////////////////////////// - -/** - * Local callback & listener class for use with the client connection. - * This is primarily intended to receive messages, but it will also monitor - * the connection to the broker. If the connection is lost, it will attempt - * to restore the connection and re-subscribe to the topic. - */ -class MQTTCallback : public virtual mqtt::callback, - public virtual mqtt::iaction_listener - -{ - const std::string tpoic_target_hdg; - const std::string topic_control_mode; - mqtt_sub_data sub_data; - std::mutex mqtt_mut; - std::string fwt_name; - // Counter for the number of connection retries - int nretry_; - // The MQTT client - mqtt::async_client& cli_; - // Options to use if we need to reconnect - mqtt::connect_options& connOpts_; - // An action listener to display the result of actions. - action_listener subListener_; - - // This deomonstrates manually reconnecting to the broker by calling - // connect() again. This is a possibility for an application that keeps - // a copy of it's original connect_options, or if the app wants to - // reconnect with different options. - // Another way this can be done manually, if using the same options, is - // to just call the async_client::reconnect() method. - void reconnect() { - std::this_thread::sleep_for(std::chrono::milliseconds(2500)); - try { - cli_.connect(connOpts_, nullptr, *this); - } - catch (const mqtt::exception& exc) { - std::cerr << "Error: " << exc.what() << std::endl; - } - } - - - // Re-connection failure - void on_failure(const mqtt::token& tok) override; - - // (Re)connection success - // Either this or connected() can be used for callbacks. - void on_success(const mqtt::token& tok) override {} - // Re-connection success - void connected(const std::string& cause) override; - - // Callback for when the connection is lost. - // This will initiate the attempt to manually reconnect. - void connection_lost(const std::string& cause) override { - std::cout << "\nConnection lost" << std::endl; - if (!cause.empty()) - std::cout << "\tcause: " << cause << std::endl; - - std::cout << "Reconnecting..." << std::endl; - nretry_ = 0; - reconnect(); - } - - // Callback for when a message arrives. - void message_arrived(mqtt::const_message_ptr msg) override; - - void delivery_complete(mqtt::delivery_token_ptr token) override { - std::cout << "MQTT delivery complete" << std::endl; - } - -public: - MQTTCallback(mqtt::async_client& cli, mqtt::connect_options& connOpts, std::string tower_name) - : nretry_(0), cli_(cli), connOpts_(connOpts), subListener_("Subscription"), fwt_name(tower_name), tpoic_target_hdg("GGS/FWT/" + tower_name + "/target_HDG"), topic_control_mode("GGS/FWT/" + tower_name + "/ControlCode") {} - mqtt_sub_data get_sub_data() { - std::unique_lock ul(mqtt_mut); - mqtt_sub_data tmp = sub_data; - sub_data.hdg_avail = false; - sub_data.ctl_avail = false; - return tmp; - } -}; - -class MQTTClient { -public: - MQTTClient(const std::string& serverURI, const std::string& clientId, std::string tower_name, std::string login_user, std::string login_pw); - ~MQTTClient(); - - void connect_client(); - void run(); - void disconnect(); - void publish(const std::string& topic, const std::string& payload); - void subscribe(const std::string& topic); - void receiveMessages(); - bool is_connected_to_server(); - - bool running = false; - bool connected = false; - MQTTCallback callback; -private: - std::string fwt_name; - mqtt::async_client client; - mqtt::token_ptr subToken; - mqtt::token_ptr conToken; - mqtt::connect_options connOpts; - std::thread mqtt_thread; -}; diff --git a/Parser.h b/Parser.h deleted file mode 100644 index 6413d46..0000000 --- a/Parser.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once -#include -#include -#include -#include - -namespace qi = boost::spirit::qi; - -enum InputCommands -{ - no_cmd, - startcamera, - stopcamera, - setcamera, - setimagerate, - setgimbal, - setmotorcontrol, - setdebug -}; - -struct parser_data { - std::string command=""; - std::string device = ""; - std::string option = ""; - double command_val=0.0; -}; - -struct Parser -{ - parser_data p_data; - std::mutex mut; - void set_parser_data(parser_data& data) { - std::cout << "parser set data..." << std::endl; - std::lock_guard lg(mut); - p_data = data; - } - - void clear_parser_data() { - //std::cout << "parser clear data..." << std::endl; - std::lock_guard lg(mut); - p_data.command = ""; - } - - parser_data get_parser_data() { - //std::cout << "parser get data..." << std::endl; - std::lock_guard lg(mut); - parser_data temp= p_data; - //clear_parser_data(); - return temp; - } - - void parse_input(std::string input) { - parser_data temp_data; - qi::parse( - input.begin(), input.end(), - (*qi::char_("a-zA-Z") >> ' ' >> *qi::char_("a-zA-Z") >> ' ' >> *qi::char_("a-zA-Z") >> ' ' >> qi::double_), - temp_data.command, temp_data.device, temp_data.option, temp_data.command_val - ); - std::cout << temp_data.command << " ... " << temp_data.device << " ... " << temp_data.option << " ... " << temp_data.command_val << std::endl; - set_parser_data(temp_data); - std::cout << "parser data set" << temp_data.command_val << std::endl; - } -}; - -struct CMD_eval { - InputCommands eval(Parser& parse) { - if (parse.p_data.command == "start") { - std::cout << "starting Camera" << std::endl; - parse.clear_parser_data(); - return startcamera; - } - else if (parse.p_data.command == "stop") { - std::cout << "stopping Camera" << std::endl; - parse.clear_parser_data(); - return stopcamera; - } - else if (parse.p_data.command == "debug") { - parse.clear_parser_data(); - return setdebug; - } - else if (parse.p_data.command == "set") { - //std::cout << "setting" << std::endl; - if (parse.p_data.device == "camera") { - parse.clear_parser_data(); - return setcamera; - } - else if (parse.p_data.device == "fps") { - parse.clear_parser_data(); - return setimagerate; - } - else if (parse.p_data.device == "motorctl") { - parse.clear_parser_data(); - return setmotorcontrol; - - } - - } - else { - return no_cmd; - } - } -}; diff --git a/Serial.h b/Serial.h deleted file mode 100644 index 48e6281..0000000 --- a/Serial.h +++ /dev/null @@ -1,144 +0,0 @@ -#include -#include -#include -#include -#include -struct motor_info -{ - int Xenc; - int Xerr; - int sgt_val; - int sgt_stat; - int is_moving; - int control_status; - float hdg; - int deviation_warn; - int temp; - int humid; - int fan_pwm; -}; -class SerialPort { -public: - SerialPort(const std::string& port, unsigned int baud_rate) - : io_service_(), serial_(io_service_) { - boost::system::error_code ec; - - serial_.open(port, ec); - if (ec) { - throw std::runtime_error("Failed to open port: " + ec.message()); - } - - setOption(boost::asio::serial_port_base::baud_rate(baud_rate), "baud_rate"); - setOption(boost::asio::serial_port_base::character_size(8), "character_size"); - setOption(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none), "parity"); - setOption(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one), "stop_bits"); - setOption(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none), "flow_control"); - } - - void startReading() { - read(); - } - - void sendCommand(std::string cmd) { - write(cmd); - } - - void write(const std::string& data) { - boost::asio::async_write(serial_, boost::asio::buffer(data), - [](const boost::system::error_code& error, std::size_t) { - if (error) { - std::cerr << "Write failed: " << error.message() << std::endl; - } - } - ); - } - - void run() { - io_service_.run(); - } - - void stop() { - io_service_.stop(); - } - - void set_controller_info(motor_info& inf) { - std::lock_guard lg(mut); - motorcontroller_information = inf; - } - - motor_info get_controller_info() { - std::lock_guard lg(mut); - return motorcontroller_information; - } - - bool parser(std::string& message) - { - - if (message[0] == '$') { - //std::cout << message; - std::vector values; - std::string delimiter = ";"; - size_t pos = 0; - while ((pos = message.find(delimiter)) != std::string::npos) { - values.push_back(message.substr(0, pos)); - message.erase(0, pos + delimiter.length()); - } - //for (int i = 0; i < values.size(); i++) { - // std::cout << values[i] << std::endl; - //} - if (values.size() == 12) { - motor_info tmp_info; - tmp_info.Xenc = stoi(values[1]); - tmp_info.Xerr = stoi(values[2]); - tmp_info.sgt_val = stoi(values[3]); - tmp_info.sgt_stat = stoi(values[4]); - tmp_info.is_moving = stoi(values[5]); - tmp_info.control_status = stoi(values[6]); - tmp_info.hdg = stof(values[7]); - tmp_info.deviation_warn = stoi(values[8]); - tmp_info.humid = stoi(values[9]); - tmp_info.temp = stoi(values[10]); - tmp_info.fan_pwm = stoi(values[11]); - set_controller_info(tmp_info); - return true; - } - return false; - //else - //std::cout << values.size() << std::endl; - } - } - -private: - std::mutex mut; - motor_info motorcontroller_information; - - template - void setOption(const Option& option, const std::string& option_name) { - boost::system::error_code ec; - serial_.set_option(option, ec); - if (ec) { - throw std::runtime_error("Failed to set " + option_name + ": " + ec.message()); - } - } - void read() { - boost::asio::async_read_until(serial_, buffer_, '\n', - [this](const boost::system::error_code& error, std::size_t bytes_transferred) { - if (!error) { - std::istream is(&buffer_); - std::string line; - std::getline(is, line); - //std::cout << "Received: " << line << std::endl; - parser(line); - read(); // Continue reading - } - else { - std::cerr << "Read failed: " << error.message() << std::endl; - } - } - ); - } - - boost::asio::io_service io_service_; - boost::asio::serial_port serial_; - boost::asio::streambuf buffer_; -}; \ No newline at end of file diff --git a/cxxopts.hpp b/cxxopts.hpp deleted file mode 100644 index d339314..0000000 --- a/cxxopts.hpp +++ /dev/null @@ -1,2908 +0,0 @@ -/* - -Copyright (c) 2014-2022 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. - -*/ - -// vim: ts=2:sw=2:expandtab - -#ifndef CXXOPTS_HPP_INCLUDED -#define CXXOPTS_HPP_INCLUDED - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef CXXOPTS_NO_EXCEPTIONS -#include -#endif - -#if defined(__GNUC__) && !defined(__clang__) -# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 -# define CXXOPTS_NO_REGEX true -# endif -#endif -#if defined(_MSC_VER) && !defined(__clang__) -#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern -#define CXXOPTS_LINKONCE __declspec(selectany) extern -#else -#define CXXOPTS_LINKONCE_CONST -#define CXXOPTS_LINKONCE -#endif - -#ifndef CXXOPTS_NO_REGEX -# include -#endif // CXXOPTS_NO_REGEX - -// Nonstandard before C++17, which is coincidentally what we also need for -#ifdef __has_include -# if __has_include() -# include -# ifdef __cpp_lib_optional -# define CXXOPTS_HAS_OPTIONAL -# endif -# endif -#endif - -#define CXXOPTS_FALLTHROUGH -#ifdef __has_cpp_attribute -#if __has_cpp_attribute(fallthrough) -#undef CXXOPTS_FALLTHROUGH -#define CXXOPTS_FALLTHROUGH [[fallthrough]] -#endif -#endif - -#if __cplusplus >= 201603L -#define CXXOPTS_NODISCARD [[nodiscard]] -#else -#define CXXOPTS_NODISCARD -#endif - -#ifndef CXXOPTS_VECTOR_DELIMITER -#define CXXOPTS_VECTOR_DELIMITER ',' -#endif - -#define CXXOPTS__VERSION_MAJOR 3 -#define CXXOPTS__VERSION_MINOR 2 -#define CXXOPTS__VERSION_PATCH 1 - -#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 -#define CXXOPTS_NULL_DEREF_IGNORE -#endif - -#if defined(__GNUC__) -#define DO_PRAGMA(x) _Pragma(#x) -#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push) -#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop) -#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x) -#else -// define other compilers here if needed -#define CXXOPTS_DIAGNOSTIC_PUSH -#define CXXOPTS_DIAGNOSTIC_POP -#define CXXOPTS_IGNORE_WARNING(x) -#endif - -#ifdef CXXOPTS_NO_RTTI -#define CXXOPTS_RTTI_CAST static_cast -#else -#define CXXOPTS_RTTI_CAST dynamic_cast -#endif - -namespace cxxopts { - static constexpr struct { - uint8_t major, minor, patch; - } version = { - CXXOPTS__VERSION_MAJOR, - CXXOPTS__VERSION_MINOR, - CXXOPTS__VERSION_PATCH - }; -} // namespace cxxopts - -//when we ask cxxopts to use Unicode, help strings are processed using ICU, -//which results in the correct lengths being computed for strings when they -//are formatted for the help output -//it is necessary to make sure that can be found by the -//compiler, and that icu-uc is linked in to the binary. - -#ifdef CXXOPTS_USE_UNICODE -#include - -namespace cxxopts { - - using String = icu::UnicodeString; - - inline - String - toLocalString(std::string s) - { - return icu::UnicodeString::fromUTF8(std::move(s)); - } - - // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: - // warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor - CXXOPTS_DIAGNOSTIC_PUSH - CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") - // This will be ignored under other compilers like LLVM clang. - class UnicodeStringIterator - { - public: - - using iterator_category = std::forward_iterator_tag; - using value_type = int32_t; - using difference_type = std::ptrdiff_t; - using pointer = value_type*; - using reference = value_type&; - - UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) - : s(string) - , i(pos) - { - } - - value_type - operator*() const - { - return s->char32At(i); - } - - bool - operator==(const UnicodeStringIterator& rhs) const - { - return s == rhs.s && i == rhs.i; - } - - bool - operator!=(const UnicodeStringIterator& rhs) const - { - return !(*this == rhs); - } - - UnicodeStringIterator& - operator++() - { - ++i; - return *this; - } - - UnicodeStringIterator - operator+(int32_t v) - { - return UnicodeStringIterator(s, i + v); - } - - private: - const icu::UnicodeString* s; - int32_t i; - }; - CXXOPTS_DIAGNOSTIC_POP - - inline - String& - stringAppend(String& s, String a) - { - return s.append(std::move(a)); - } - - inline - String& - stringAppend(String& s, std::size_t n, UChar32 c) - { - for (std::size_t i = 0; i != n; ++i) - { - s.append(c); - } - - return s; - } - - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - while (begin != end) - { - s.append(*begin); - ++begin; - } - - return s; - } - - inline - size_t - stringLength(const String& s) - { - return static_cast(s.length()); - } - - inline - std::string - toUTF8String(const String& s) - { - std::string result; - s.toUTF8String(result); - - return result; - } - - inline - bool - empty(const String& s) - { - return s.isEmpty(); - } - -} // namespace cxxopts - -namespace std { - - inline - cxxopts::UnicodeStringIterator - begin(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, 0); - } - - inline - cxxopts::UnicodeStringIterator - end(const icu::UnicodeString& s) - { - return cxxopts::UnicodeStringIterator(&s, s.length()); - } - -} // namespace std - -//ifdef CXXOPTS_USE_UNICODE -#else - -namespace cxxopts { - - using String = std::string; - - template - T - toLocalString(T&& t) - { - return std::forward(t); - } - - inline - std::size_t - stringLength(const String& s) - { - return s.length(); - } - - inline - String& - stringAppend(String& s, const String& a) - { - return s.append(a); - } - - inline - String& - stringAppend(String& s, std::size_t n, char c) - { - return s.append(n, c); - } - - template - String& - stringAppend(String& s, Iterator begin, Iterator end) - { - return s.append(begin, end); - } - - template - std::string - toUTF8String(T&& t) - { - return std::forward(t); - } - - inline - bool - empty(const std::string& s) - { - return s.empty(); - } - -} // namespace cxxopts - -//ifdef CXXOPTS_USE_UNICODE -#endif - -namespace cxxopts { - - namespace { - CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); - CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); - } // namespace - - // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we - // want to silence it: warning: base class 'class - // std::enable_shared_from_this' has accessible non-virtual - // destructor This will be ignored under other compilers like LLVM clang. - CXXOPTS_DIAGNOSTIC_PUSH - CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") - - // some older versions of GCC warn under this warning - CXXOPTS_IGNORE_WARNING("-Weffc++") - class Value : public std::enable_shared_from_this - { - public: - - virtual ~Value() = default; - - virtual - std::shared_ptr - clone() const = 0; - - virtual void - add(const std::string& text) const = 0; - - virtual void - parse(const std::string& text) const = 0; - - virtual void - parse() const = 0; - - virtual bool - has_default() const = 0; - - virtual bool - is_container() const = 0; - - virtual bool - has_implicit() const = 0; - - virtual std::string - get_default_value() const = 0; - - virtual std::string - get_implicit_value() const = 0; - - virtual std::shared_ptr - default_value(const std::string& value) = 0; - - virtual std::shared_ptr - implicit_value(const std::string& value) = 0; - - virtual std::shared_ptr - no_implicit_value() = 0; - - virtual bool - is_boolean() const = 0; - }; - - CXXOPTS_DIAGNOSTIC_POP - - namespace exceptions { - - class exception : public std::exception - { - public: - explicit exception(std::string message) - : m_message(std::move(message)) - { - } - - CXXOPTS_NODISCARD - const char* - what() const noexcept override - { - return m_message.c_str(); - } - - private: - std::string m_message; - }; - - class specification : public exception - { - public: - - explicit specification(const std::string& message) - : exception(message) - { - } - }; - - class parsing : public exception - { - public: - explicit parsing(const std::string& message) - : exception(message) - { - } - }; - - class option_already_exists : public specification - { - public: - explicit option_already_exists(const std::string& option) - : specification("Option " + LQUOTE + option + RQUOTE + " already exists") - { - } - }; - - class invalid_option_format : public specification - { - public: - explicit invalid_option_format(const std::string& format) - : specification("Invalid option format " + LQUOTE + format + RQUOTE) - { - } - }; - - class invalid_option_syntax : public parsing { - public: - explicit invalid_option_syntax(const std::string& text) - : parsing("Argument " + LQUOTE + text + RQUOTE + - " starts with a - but has incorrect syntax") - { - } - }; - - class no_such_option : public parsing - { - public: - explicit no_such_option(const std::string& option) - : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist") - { - } - }; - - class missing_argument : public parsing - { - public: - explicit missing_argument(const std::string& option) - : parsing( - "Option " + LQUOTE + option + RQUOTE + " is missing an argument" - ) - { - } - }; - - class option_requires_argument : public parsing - { - public: - explicit option_requires_argument(const std::string& option) - : parsing( - "Option " + LQUOTE + option + RQUOTE + " requires an argument" - ) - { - } - }; - - class gratuitous_argument_for_option : public parsing - { - public: - gratuitous_argument_for_option - ( - const std::string& option, - const std::string& arg - ) - : parsing( - "Option " + LQUOTE + option + RQUOTE + - " does not take an argument, but argument " + - LQUOTE + arg + RQUOTE + " given" - ) - { - } - }; - - class requested_option_not_present : public parsing - { - public: - explicit requested_option_not_present(const std::string& option) - : parsing("Option " + LQUOTE + option + RQUOTE + " not present") - { - } - }; - - class option_has_no_value : public exception - { - public: - explicit option_has_no_value(const std::string& option) - : exception( - !option.empty() ? - ("Option " + LQUOTE + option + RQUOTE + " has no value") : - "Option has no value") - { - } - }; - - class incorrect_argument_type : public parsing - { - public: - explicit incorrect_argument_type - ( - const std::string& arg - ) - : parsing( - "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" - ) - { - } - }; - - } // namespace exceptions - - - template - void throw_or_mimic(const std::string& text) - { - static_assert(std::is_base_of::value, - "throw_or_mimic only works on std::exception and " - "deriving classes"); - -#ifndef CXXOPTS_NO_EXCEPTIONS - // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw - throw T{ text }; -#else - // Otherwise manually instantiate the exception, print what() to stderr, - // and exit - T exception{ text }; - std::cerr << exception.what() << std::endl; - std::exit(EXIT_FAILURE); -#endif - } - - using OptionNames = std::vector; - - namespace values { - - namespace parser_tool { - - struct IntegerDesc - { - std::string negative = ""; - std::string base = ""; - std::string value = ""; - }; - struct ArguDesc { - std::string arg_name = ""; - bool grouping = false; - bool set_value = false; - std::string value = ""; - }; - -#ifdef CXXOPTS_NO_REGEX - inline IntegerDesc SplitInteger(const std::string& text) - { - if (text.empty()) - { - throw_or_mimic(text); - } - IntegerDesc desc; - const char* pdata = text.c_str(); - if (*pdata == '-') - { - pdata += 1; - desc.negative = "-"; - } - if (strncmp(pdata, "0x", 2) == 0) - { - pdata += 2; - desc.base = "0x"; - } - if (*pdata != '\0') - { - desc.value = std::string(pdata); - } - else - { - throw_or_mimic(text); - } - return desc; - } - - inline bool IsTrueText(const std::string& text) - { - const char* pdata = text.c_str(); - if (*pdata == 't' || *pdata == 'T') - { - pdata += 1; - if (strncmp(pdata, "rue\0", 4) == 0) - { - return true; - } - } - else if (strncmp(pdata, "1\0", 2) == 0) - { - return true; - } - return false; - } - - inline bool IsFalseText(const std::string& text) - { - const char* pdata = text.c_str(); - if (*pdata == 'f' || *pdata == 'F') - { - pdata += 1; - if (strncmp(pdata, "alse\0", 5) == 0) - { - return true; - } - } - else if (strncmp(pdata, "0\0", 2) == 0) - { - return true; - } - return false; - } - - inline OptionNames split_option_names(const std::string& text) - { - OptionNames split_names; - - std::string::size_type token_start_pos = 0; - auto length = text.length(); - - if (length == 0) - { - throw_or_mimic(text); - } - - while (token_start_pos < length) { - const auto& npos = std::string::npos; - auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos); - if (next_non_space_pos == npos) { - throw_or_mimic(text); - } - token_start_pos = next_non_space_pos; - auto next_delimiter_pos = text.find(',', token_start_pos); - if (next_delimiter_pos == token_start_pos) { - throw_or_mimic(text); - } - if (next_delimiter_pos == npos) { - next_delimiter_pos = length; - } - auto token_length = next_delimiter_pos - token_start_pos; - // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/ - { - const char* option_name_valid_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "_-.?"; - - if (!std::isalnum(text[token_start_pos], std::locale::classic()) || - text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) { - throw_or_mimic(text); - } - } - split_names.emplace_back(text.substr(token_start_pos, token_length)); - token_start_pos = next_delimiter_pos + 1; - } - return split_names; - } - - inline ArguDesc ParseArgument(const char* arg, bool& matched) - { - ArguDesc argu_desc; - const char* pdata = arg; - matched = false; - if (strncmp(pdata, "--", 2) == 0) - { - pdata += 2; - if (isalnum(*pdata, std::locale::classic())) - { - argu_desc.arg_name.push_back(*pdata); - pdata += 1; - while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || *pdata == '_') - { - argu_desc.arg_name.push_back(*pdata); - pdata += 1; - } - if (argu_desc.arg_name.length() > 1) - { - if (*pdata == '=') - { - argu_desc.set_value = true; - pdata += 1; - if (*pdata != '\0') - { - argu_desc.value = std::string(pdata); - } - matched = true; - } - else if (*pdata == '\0') - { - matched = true; - } - } - } - } - else if (strncmp(pdata, "-", 1) == 0) - { - pdata += 1; - argu_desc.grouping = true; - while (isalnum(*pdata, std::locale::classic())) - { - argu_desc.arg_name.push_back(*pdata); - pdata += 1; - } - matched = !argu_desc.arg_name.empty() && *pdata == '\0'; - } - return argu_desc; - } - -#else // CXXOPTS_NO_REGEX - - namespace { - CXXOPTS_LINKONCE - const char* const integer_pattern = - "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"; - CXXOPTS_LINKONCE - const char* const truthy_pattern = - "(t|T)(rue)?|1"; - CXXOPTS_LINKONCE - const char* const falsy_pattern = - "(f|F)(alse)?|0"; - CXXOPTS_LINKONCE - const char* const option_pattern = - "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"; - CXXOPTS_LINKONCE - const char* const option_specifier_pattern = - "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"; - CXXOPTS_LINKONCE - const char* const option_specifier_separator_pattern = ", *"; - - } // namespace - - inline IntegerDesc SplitInteger(const std::string& text) - { - static const std::basic_regex integer_matcher(integer_pattern); - - std::smatch match; - std::regex_match(text, match, integer_matcher); - - if (match.length() == 0) - { - throw_or_mimic(text); - } - - IntegerDesc desc; - desc.negative = match[1]; - desc.base = match[2]; - desc.value = match[3]; - - if (match.length(4) > 0) - { - desc.base = match[5]; - desc.value = "0"; - return desc; - } - - return desc; - } - - inline bool IsTrueText(const std::string& text) - { - static const std::basic_regex truthy_matcher(truthy_pattern); - std::smatch result; - std::regex_match(text, result, truthy_matcher); - return !result.empty(); - } - - inline bool IsFalseText(const std::string& text) - { - static const std::basic_regex falsy_matcher(falsy_pattern); - std::smatch result; - std::regex_match(text, result, falsy_matcher); - return !result.empty(); - } - - // Gets the option names specified via a single, comma-separated string, - // and returns the separate, space-discarded, non-empty names - // (without considering which or how many are single-character) - inline OptionNames split_option_names(const std::string& text) - { - static const std::basic_regex option_specifier_matcher(option_specifier_pattern); - if (!std::regex_match(text.c_str(), option_specifier_matcher)) - { - throw_or_mimic(text); - } - - OptionNames split_names; - - static const std::basic_regex option_specifier_separator_matcher(option_specifier_separator_pattern); - constexpr int use_non_matches{ -1 }; - auto token_iterator = std::sregex_token_iterator( - text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches); - std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names)); - return split_names; - } - - inline ArguDesc ParseArgument(const char* arg, bool& matched) - { - static const std::basic_regex option_matcher(option_pattern); - std::match_results result; - std::regex_match(arg, result, option_matcher); - matched = !result.empty(); - - ArguDesc argu_desc; - if (matched) { - argu_desc.arg_name = result[1].str(); - argu_desc.set_value = result[2].length() > 0; - argu_desc.value = result[3].str(); - if (result[4].length() > 0) - { - argu_desc.grouping = true; - argu_desc.arg_name = result[4].str(); - } - } - - return argu_desc; - } - -#endif // CXXOPTS_NO_REGEX -#undef CXXOPTS_NO_REGEX - } // namespace parser_tool - - namespace detail { - - template - struct SignedCheck; - - template - struct SignedCheck - { - template - void - operator()(bool negative, U u, const std::string& text) - { - if (negative) - { - if (u > static_cast((std::numeric_limits::min)())) - { - throw_or_mimic(text); - } - } - else - { - if (u > static_cast((std::numeric_limits::max)())) - { - throw_or_mimic(text); - } - } - } - }; - - template - struct SignedCheck - { - template - void - operator()(bool, U, const std::string&) const {} - }; - - template - void - check_signed_range(bool negative, U value, const std::string& text) - { - SignedCheck::is_signed>()(negative, value, text); - } - - } // namespace detail - - template - void - checked_negate(R& r, T&& t, const std::string&, std::true_type) - { - // if we got to here, then `t` is a positive number that fits into - // `R`. So to avoid MSVC C4146, we first cast it to `R`. - // See https://github.com/jarro2783/cxxopts/issues/62 for more details. - r = static_cast(-static_cast(t - 1) - 1); - } - - template - void - checked_negate(R&, T&&, const std::string& text, std::false_type) - { - throw_or_mimic(text); - } - - template - void - integer_parser(const std::string& text, T& value) - { - parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); - - using US = typename std::make_unsigned::type; - constexpr bool is_signed = std::numeric_limits::is_signed; - - const bool negative = int_desc.negative.length() > 0; - const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; - const std::string& value_match = int_desc.value; - - US result = 0; - - for (char ch : value_match) - { - US digit = 0; - - if (ch >= '0' && ch <= '9') - { - digit = static_cast(ch - '0'); - } - else if (base == 16 && ch >= 'a' && ch <= 'f') - { - digit = static_cast(ch - 'a' + 10); - } - else if (base == 16 && ch >= 'A' && ch <= 'F') - { - digit = static_cast(ch - 'A' + 10); - } - else - { - throw_or_mimic(text); - } - - US limit = 0; - if (negative) - { - limit = static_cast(std::abs(static_cast((std::numeric_limits::min)()))); - } - else - { - limit = (std::numeric_limits::max)(); - } - - if (base != 0 && result > limit / base) - { - throw_or_mimic(text); - } - if (result * base > limit - digit) - { - throw_or_mimic(text); - } - - result = static_cast(result * base + digit); - } - - detail::check_signed_range(negative, result, text); - - if (negative) - { - checked_negate(value, result, text, std::integral_constant()); - } - else - { - value = static_cast(result); - } - } - - template - void stringstream_parser(const std::string& text, T& value) - { - std::stringstream in(text); - in >> value; - if (!in) { - throw_or_mimic(text); - } - } - - template ::value>::type* = nullptr - > - void parse_value(const std::string& text, T& value) - { - integer_parser(text, value); - } - - inline - void - parse_value(const std::string& text, bool& value) - { - if (parser_tool::IsTrueText(text)) - { - value = true; - return; - } - - if (parser_tool::IsFalseText(text)) - { - value = false; - return; - } - - throw_or_mimic(text); - } - - inline - void - parse_value(const std::string& text, std::string& value) - { - value = text; - } - - // The fallback parser. It uses the stringstream parser to parse all types - // that have not been overloaded explicitly. It has to be placed in the - // source code before all other more specialized templates. - template ::value>::type* = nullptr - > - void - parse_value(const std::string& text, T& value) { - stringstream_parser(text, value); - } - -#ifdef CXXOPTS_HAS_OPTIONAL - template - void - parse_value(const std::string& text, std::optional& value) - { - T result; - parse_value(text, result); - value = std::move(result); - } -#endif - - inline - void parse_value(const std::string& text, char& c) - { - if (text.length() != 1) - { - throw_or_mimic(text); - } - - c = text[0]; - } - - template - void - parse_value(const std::string& text, std::vector& value) - { - if (text.empty()) { - T v; - parse_value(text, v); - value.emplace_back(std::move(v)); - return; - } - std::stringstream in(text); - std::string token; - while (!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { - T v; - parse_value(token, v); - value.emplace_back(std::move(v)); - } - } - - template - void - add_value(const std::string& text, T& value) - { - parse_value(text, value); - } - - template - void - add_value(const std::string& text, std::vector& value) - { - T v; - add_value(text, v); - value.emplace_back(std::move(v)); - } - - template - struct type_is_container - { - static constexpr bool value = false; - }; - - template - struct type_is_container> - { - static constexpr bool value = true; - }; - - template - class abstract_value : public Value - { - using Self = abstract_value; - - public: - abstract_value() - : m_result(std::make_shared()) - , m_store(m_result.get()) - { - } - - explicit abstract_value(T* t) - : m_store(t) - { - } - - ~abstract_value() override = default; - - abstract_value& operator=(const abstract_value&) = default; - - abstract_value(const abstract_value& rhs) - { - if (rhs.m_result) - { - m_result = std::make_shared(); - m_store = m_result.get(); - } - else - { - m_store = rhs.m_store; - } - - m_default = rhs.m_default; - m_implicit = rhs.m_implicit; - m_default_value = rhs.m_default_value; - m_implicit_value = rhs.m_implicit_value; - } - - void - add(const std::string& text) const override - { - add_value(text, *m_store); - } - - void - parse(const std::string& text) const override - { - parse_value(text, *m_store); - } - - bool - is_container() const override - { - return type_is_container::value; - } - - void - parse() const override - { - parse_value(m_default_value, *m_store); - } - - bool - has_default() const override - { - return m_default; - } - - bool - has_implicit() const override - { - return m_implicit; - } - - std::shared_ptr - default_value(const std::string& value) override - { - m_default = true; - m_default_value = value; - return shared_from_this(); - } - - std::shared_ptr - implicit_value(const std::string& value) override - { - m_implicit = true; - m_implicit_value = value; - return shared_from_this(); - } - - std::shared_ptr - no_implicit_value() override - { - m_implicit = false; - return shared_from_this(); - } - - std::string - get_default_value() const override - { - return m_default_value; - } - - std::string - get_implicit_value() const override - { - return m_implicit_value; - } - - bool - is_boolean() const override - { - return std::is_same::value; - } - - const T& - get() const - { - if (m_store == nullptr) - { - return *m_result; - } - return *m_store; - } - - protected: - std::shared_ptr m_result{}; - T* m_store{}; - - bool m_default = false; - bool m_implicit = false; - - std::string m_default_value{}; - std::string m_implicit_value{}; - }; - - template - class standard_value : public abstract_value - { - public: - using abstract_value::abstract_value; - - CXXOPTS_NODISCARD - std::shared_ptr - clone() const override - { - return std::make_shared>(*this); - } - }; - - template <> - class standard_value : public abstract_value - { - public: - ~standard_value() override = default; - - standard_value() - { - set_default_and_implicit(); - } - - explicit standard_value(bool* b) - : abstract_value(b) - { - m_implicit = true; - m_implicit_value = "true"; - } - - std::shared_ptr - clone() const override - { - return std::make_shared>(*this); - } - - private: - - void - set_default_and_implicit() - { - m_default = true; - m_default_value = "false"; - m_implicit = true; - m_implicit_value = "true"; - } - }; - - } // namespace values - - template - std::shared_ptr - value() - { - return std::make_shared>(); - } - - template - std::shared_ptr - value(T& t) - { - return std::make_shared>(&t); - } - - class OptionAdder; - - CXXOPTS_NODISCARD - inline - const std::string& - first_or_empty(const OptionNames& long_names) - { - static const std::string empty{ "" }; - return long_names.empty() ? empty : long_names.front(); - } - - class OptionDetails - { - public: - OptionDetails - ( - std::string short_, - OptionNames long_, - String desc, - std::shared_ptr val - ) - : m_short(std::move(short_)) - , m_long(std::move(long_)) - , m_desc(std::move(desc)) - , m_value(std::move(val)) - , m_count(0) - { - m_hash = std::hash{}(first_long_name() + m_short); - } - - OptionDetails(const OptionDetails& rhs) - : m_desc(rhs.m_desc) - , m_value(rhs.m_value->clone()) - , m_count(rhs.m_count) - { - } - - OptionDetails(OptionDetails&& rhs) = default; - - CXXOPTS_NODISCARD - const String& - description() const - { - return m_desc; - } - - CXXOPTS_NODISCARD - const Value& - value() const { - return *m_value; - } - - CXXOPTS_NODISCARD - std::shared_ptr - make_storage() const - { - return m_value->clone(); - } - - CXXOPTS_NODISCARD - const std::string& - short_name() const - { - return m_short; - } - - CXXOPTS_NODISCARD - const std::string& - first_long_name() const - { - return first_or_empty(m_long); - } - - CXXOPTS_NODISCARD - const std::string& - essential_name() const - { - return m_long.empty() ? m_short : m_long.front(); - } - - CXXOPTS_NODISCARD - const OptionNames& - long_names() const - { - return m_long; - } - - std::size_t - hash() const - { - return m_hash; - } - - private: - std::string m_short{}; - OptionNames m_long{}; - String m_desc{}; - std::shared_ptr m_value{}; - int m_count; - - std::size_t m_hash{}; - }; - - struct HelpOptionDetails - { - std::string s; - OptionNames l; - String desc; - bool has_default; - std::string default_value; - bool has_implicit; - std::string implicit_value; - std::string arg_help; - bool is_container; - bool is_boolean; - }; - - struct HelpGroupDetails - { - std::string name{}; - std::string description{}; - std::vector options{}; - }; - - class OptionValue - { - public: - void - add - ( - const std::shared_ptr& details, - const std::string& text - ) - { - ensure_value(details); - ++m_count; - m_value->add(text); - m_long_names = &details->long_names(); - } - - void - parse - ( - const std::shared_ptr& details, - const std::string& text - ) - { - ensure_value(details); - ++m_count; - m_value->parse(text); - m_long_names = &details->long_names(); - } - - void - parse_default(const std::shared_ptr& details) - { - ensure_value(details); - m_default = true; - m_long_names = &details->long_names(); - m_value->parse(); - } - - void - parse_no_value(const std::shared_ptr& details) - { - m_long_names = &details->long_names(); - } - -#if defined(CXXOPTS_NULL_DEREF_IGNORE) - CXXOPTS_DIAGNOSTIC_PUSH - CXXOPTS_IGNORE_WARNING("-Wnull-dereference") -#endif - - CXXOPTS_NODISCARD - std::size_t - count() const noexcept - { - return m_count; - } - -#if defined(CXXOPTS_NULL_DEREF_IGNORE) - CXXOPTS_DIAGNOSTIC_POP -#endif - - // TODO: maybe default options should count towards the number of arguments - CXXOPTS_NODISCARD - bool - has_default() const noexcept - { - return m_default; - } - - template - const T& - as() const - { - if (m_value == nullptr) { - throw_or_mimic( - m_long_names == nullptr ? "" : first_or_empty(*m_long_names)); - } - - return CXXOPTS_RTTI_CAST&>(*m_value).get(); - } - -#ifdef CXXOPTS_HAS_OPTIONAL - template - std::optional - as_optional() const - { - if (m_value == nullptr) { - return std::nullopt; - } - return as(); - } -#endif - - private: - void - ensure_value(const std::shared_ptr& details) - { - if (m_value == nullptr) - { - m_value = details->make_storage(); - } - } - - - const OptionNames* m_long_names = nullptr; - // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, - // where the key has the string we point to. - std::shared_ptr m_value{}; - std::size_t m_count = 0; - bool m_default = false; - }; - - class KeyValue - { - public: - KeyValue(std::string key_, std::string value_) noexcept - : m_key(std::move(key_)) - , m_value(std::move(value_)) - { - } - - CXXOPTS_NODISCARD - const std::string& - key() const - { - return m_key; - } - - CXXOPTS_NODISCARD - const std::string& - value() const - { - return m_value; - } - - template - T - as() const - { - T result; - values::parse_value(m_value, result); - return result; - } - - private: - std::string m_key; - std::string m_value; - }; - - using ParsedHashMap = std::unordered_map; - using NameHashMap = std::unordered_map; - - class ParseResult - { - public: - class Iterator - { - public: - using iterator_category = std::forward_iterator_tag; - using value_type = KeyValue; - using difference_type = void; - using pointer = const KeyValue*; - using reference = const KeyValue&; - - Iterator() = default; - Iterator(const Iterator&) = default; - - // GCC complains about m_iter not being initialised in the member - // initializer list - CXXOPTS_DIAGNOSTIC_PUSH - CXXOPTS_IGNORE_WARNING("-Weffc++") - Iterator(const ParseResult* pr, bool end = false) - : m_pr(pr) - { - if (end) - { - m_sequential = false; - m_iter = m_pr->m_defaults.end(); - } - else - { - m_sequential = true; - m_iter = m_pr->m_sequential.begin(); - - if (m_iter == m_pr->m_sequential.end()) - { - m_sequential = false; - m_iter = m_pr->m_defaults.begin(); - } - } - } - CXXOPTS_DIAGNOSTIC_POP - - Iterator& operator++() - { - ++m_iter; - if (m_sequential && m_iter == m_pr->m_sequential.end()) - { - m_sequential = false; - m_iter = m_pr->m_defaults.begin(); - return *this; - } - return *this; - } - - Iterator operator++(int) - { - Iterator retval = *this; - ++(*this); - return retval; - } - - bool operator==(const Iterator& other) const - { - return (m_sequential == other.m_sequential) && (m_iter == other.m_iter); - } - - bool operator!=(const Iterator& other) const - { - return !(*this == other); - } - - const KeyValue& operator*() - { - return *m_iter; - } - - const KeyValue* operator->() - { - return m_iter.operator->(); - } - - private: - const ParseResult* m_pr; - std::vector::const_iterator m_iter; - bool m_sequential = true; - }; - - ParseResult() = default; - ParseResult(const ParseResult&) = default; - - ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, - std::vector default_opts, std::vector&& unmatched_args) - : m_keys(std::move(keys)) - , m_values(std::move(values)) - , m_sequential(std::move(sequential)) - , m_defaults(std::move(default_opts)) - , m_unmatched(std::move(unmatched_args)) - { - } - - ParseResult& operator=(ParseResult&&) = default; - ParseResult& operator=(const ParseResult&) = default; - - Iterator - begin() const - { - return Iterator(this); - } - - Iterator - end() const - { - return Iterator(this, true); - } - - std::size_t - count(const std::string& o) const - { - auto iter = m_keys.find(o); - if (iter == m_keys.end()) - { - return 0; - } - - auto viter = m_values.find(iter->second); - - if (viter == m_values.end()) - { - return 0; - } - - return viter->second.count(); - } - - const OptionValue& - operator[](const std::string& option) const - { - auto iter = m_keys.find(option); - - if (iter == m_keys.end()) - { - throw_or_mimic(option); - } - - auto viter = m_values.find(iter->second); - - if (viter == m_values.end()) - { - throw_or_mimic(option); - } - - return viter->second; - } - -#ifdef CXXOPTS_HAS_OPTIONAL - template - std::optional - as_optional(const std::string& option) const - { - auto iter = m_keys.find(option); - if (iter != m_keys.end()) - { - auto viter = m_values.find(iter->second); - if (viter != m_values.end()) - { - return viter->second.as_optional(); - } - } - return std::nullopt; - } -#endif - - const std::vector& - arguments() const - { - return m_sequential; - } - - const std::vector& - unmatched() const - { - return m_unmatched; - } - - const std::vector& - defaults() const - { - return m_defaults; - } - - const std::string - arguments_string() const - { - std::string result; - for (const auto& kv : m_sequential) - { - result += kv.key() + " = " + kv.value() + "\n"; - } - for (const auto& kv : m_defaults) - { - result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; - } - return result; - } - - private: - NameHashMap m_keys{}; - ParsedHashMap m_values{}; - std::vector m_sequential{}; - std::vector m_defaults{}; - std::vector m_unmatched{}; - }; - - struct Option - { - Option - ( - std::string opts, - std::string desc, - std::shared_ptr value = ::cxxopts::value(), - std::string arg_help = "" - ) - : opts_(std::move(opts)) - , desc_(std::move(desc)) - , value_(std::move(value)) - , arg_help_(std::move(arg_help)) - { - } - - std::string opts_; - std::string desc_; - std::shared_ptr value_; - std::string arg_help_; - }; - - using OptionMap = std::unordered_map>; - using PositionalList = std::vector; - using PositionalListIterator = PositionalList::const_iterator; - - class OptionParser - { - public: - OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) - : m_options(options) - , m_positional(positional) - , m_allow_unrecognised(allow_unrecognised) - { - } - - ParseResult - parse(int argc, const char* const* argv); - - bool - consume_positional(const std::string& a, PositionalListIterator& next); - - void - checked_parse_arg - ( - int argc, - const char* const* argv, - int& current, - const std::shared_ptr& value, - const std::string& name - ); - - void - add_to_option(const std::shared_ptr& value, const std::string& arg); - - void - parse_option - ( - const std::shared_ptr& value, - const std::string& name, - const std::string& arg = "" - ); - - void - parse_default(const std::shared_ptr& details); - - void - parse_no_value(const std::shared_ptr& details); - - private: - - void finalise_aliases(); - - const OptionMap& m_options; - const PositionalList& m_positional; - - std::vector m_sequential{}; - std::vector m_defaults{}; - bool m_allow_unrecognised; - - ParsedHashMap m_parsed{}; - NameHashMap m_keys{}; - }; - - class Options - { - public: - - explicit Options(std::string program_name, std::string help_string = "") - : m_program(std::move(program_name)) - , m_help_string(toLocalString(std::move(help_string))) - , m_custom_help("[OPTION...]") - , m_positional_help("positional parameters") - , m_show_positional(false) - , m_allow_unrecognised(false) - , m_width(76) - , m_tab_expansion(false) - , m_options(std::make_shared()) - { - } - - Options& - positional_help(std::string help_text) - { - m_positional_help = std::move(help_text); - return *this; - } - - Options& - custom_help(std::string help_text) - { - m_custom_help = std::move(help_text); - return *this; - } - - Options& - show_positional_help() - { - m_show_positional = true; - return *this; - } - - Options& - allow_unrecognised_options() - { - m_allow_unrecognised = true; - return *this; - } - - Options& - set_width(std::size_t width) - { - m_width = width; - return *this; - } - - Options& - set_tab_expansion(bool expansion = true) - { - m_tab_expansion = expansion; - return *this; - } - - ParseResult - parse(int argc, const char* const* argv); - - OptionAdder - add_options(std::string group = ""); - - void - add_options - ( - const std::string& group, - std::initializer_list