diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml deleted file mode 100644 index b42ac91..0000000 --- a/.github/workflows/cla.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: CLA Assistant - -# CLA Assistant Lite (contributor-assistant/github-action). -# -# On every pull request, the bot checks whether each contributor has signed the -# CLA in CLA.md. Unsigned contributors are asked to sign by posting a comment on -# their PR with the exact phrase: -# -# I have read the CLA Document and I hereby sign the CLA -# -# Signatures are appended to a JSON file (path-to-signatures) committed to the -# `branch` configured below. Posting `recheck` re-runs the check. -# -# ── One-time setup ──────────────────────────────────────────────────────────── -# 1. The `branch` used to store signatures MUST exist and MUST NOT be branch- -# protected (the bot pushes directly to it). Create it once: -# git branch cla-signatures && git push -u origin cla-signatures -# 2. If you store signatures in the same repo on an UNPROTECTED branch, the -# built-in GITHUB_TOKEN is enough. PERSONAL_ACCESS_TOKEN is only required -# when storing signatures in a *remote* repo, or when the storage branch is -# protected. If needed, add a repo-scoped PAT as the secret -# PERSONAL_ACCESS_TOKEN in Settings → Secrets and variables → Actions. -# 3. Edit `allowlist` below to include maintainers and bots (they skip the CLA). - -on: - issue_comment: - types: [created] - pull_request_target: - types: [opened, synchronize, closed] - -permissions: - actions: write - contents: write - pull-requests: write - statuses: write - -jobs: - cla-assistant: - runs-on: ubuntu-latest - steps: - - name: CLA Assistant - if: >- - (github.event.comment.body == 'recheck' - || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') - || github.event_name == 'pull_request_target' - uses: contributor-assistant/github-action@v2.6.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # only for remote/protected storage - with: - path-to-document: 'https://github.com/PlotJuggler/plotjuggler_core/blob/development/CLA.md' - path-to-signatures: 'signatures/version1/cla.json' - branch: 'cla-signatures' - allowlist: facontidavide,bot*,*[bot] - custom-notsigned-prcomment: >- - Thank you for your contribution. Before we can merge it, please sign the - [Contributor License Agreement](https://github.com/PlotJuggler/plotjuggler_core/blob/development/CLA.md) - by posting a comment on this pull request with exactly the text below: - custom-pr-sign-comment: 'I have read the CLA Document and I hereby sign the CLA' - custom-allsigned-prcomment: 'All contributors have signed the CLA. Thank you!' diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml new file mode 100644 index 0000000..f5e2496 --- /dev/null +++ b/.github/workflows/macos-ci.yml @@ -0,0 +1,48 @@ +name: macOS CI +on: + push: + branches: [development, main] + pull_request: + branches: [development, main] + types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + if: github.event.pull_request.draft == false + runs-on: macos-15-intel + steps: + - uses: actions/checkout@v4 + + - uses: conan-io/setup-conan@v1 + with: + cache_packages: true + + - name: Install cmake and ninja + run: pip install cmake ninja + + - name: Conan install + run: > + conan install . --output-folder=build --build=missing + -s build_type=RelWithDebInfo -s compiler.cppstd=20 + -o "plotjuggler_core/*:with_tests=True" + -o "plotjuggler_core/*:with_parquet_example=False" + + - name: Configure + run: > + cmake -S . -B build -G Ninja + -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/build/conan_toolchain.cmake + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DPJ_BUILD_PARQUET_IMPORT_EXAMPLE=OFF + + - name: Build + run: cmake --build build + + - name: Test + env: + QT_QPA_PLATFORM: offscreen + run: ctest --test-dir build --output-on-failure diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..ac37e91 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,25 @@ +name: pre-commit +on: + push: + branches: [development, main] + pull_request: + branches: [development, main] + types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + pre-commit: + if: github.event.pull_request.draft == false + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - uses: pre-commit/action@v3.0.1 diff --git a/CLA.md b/CLA.md deleted file mode 100644 index acb3f2c..0000000 --- a/CLA.md +++ /dev/null @@ -1,95 +0,0 @@ - - -# PlotJuggler Core — Individual Contributor License Agreement - -Thank you for contributing to PlotJuggler Core (the "Project"). This Contributor -License Agreement ("Agreement") sets out the terms under which You provide -Contributions to the Project. It protects You, the Project, and downstream users -by making the intellectual-property terms of Your Contributions explicit. - -By signing this Agreement (see "How to sign" below) You accept and agree to these -terms for Your past, present, and future Contributions to the Project. - -## 1. Definitions - -- **"You"** (or **"Your"**) means the individual who submits a Contribution, or - the legal entity on whose behalf the Contribution is submitted. -- **"Project Owner"** means Davide Faconti, the maintainer and copyright holder - of PlotJuggler Core, and any successor maintainer or assignee. -- **"Contribution"** means any original work of authorship, including any - modification of or addition to existing work, that You intentionally submit to - the Project (e.g. via pull request, patch, or issue attachment) for inclusion - in or documentation of the Project. - -## 2. Copyright License - -You grant the Project Owner and recipients of software distributed by the Project -a **perpetual, worldwide, non-exclusive, royalty-free, irrevocable** copyright -license to reproduce, prepare derivative works of, publicly display, publicly -perform, **sublicense, relicense, and distribute** Your Contributions and such -derivative works. - -You expressly agree that the Project Owner may **license and distribute Your -Contributions under any license terms, including open-source, proprietary, and -commercial terms** (for example, dual-licensing the storage engine). This right -to relicense survives even if the Project's default license changes. You retain -all right, title, and interest in Your Contributions not expressly granted here, -including the right to use them for any other purpose. - -## 3. Patent License - -You grant the Project Owner and recipients of software distributed by the Project -a perpetual, worldwide, non-exclusive, royalty-free, irrevocable (except as -stated below) patent license to make, have made, use, offer to sell, sell, -import, and otherwise transfer Your Contributions, where such license applies -only to those patent claims licensable by You that are necessarily infringed by -Your Contribution alone or by combination of Your Contribution with the Project. - -If any entity institutes patent litigation alleging that Your Contribution, or -the Project to which You contributed, constitutes direct or contributory patent -infringement, then any patent licenses granted to that entity under this -Agreement for that Contribution terminate as of the date such litigation is -filed. - -## 4. Moral Rights - -To the fullest extent permitted by applicable law, You waive, and agree not to -assert, any moral rights in Your Contributions against the Project Owner or -downstream recipients. - -## 5. Your Representations - -You represent that: - -1. Each Contribution is either Your original creation, or You have sufficient - rights to submit it under the terms of this Agreement. -2. The grants above do not violate any agreement You have with a third party. If - Your employer has rights to intellectual property You create, You represent - that You have received permission to make the Contributions on behalf of that - employer, or that the employer has waived such rights for Your Contributions. -3. You are not aware of any third-party rights that would make the grants in this - Agreement inaccurate, and You will notify the Project Owner if You later become - aware of any such rights. - -## 6. No Warranty / No Obligation - -Unless required by applicable law or agreed to in writing, You provide Your -Contributions "AS IS", without warranties or conditions of any kind. The Project -Owner is under no obligation to accept, use, or distribute any Contribution. - -## How to sign - -To sign this Agreement, post a comment on your pull request containing exactly: - -> I have read the CLA Document and I hereby sign the CLA - -The automated CLA Assistant records your signature against your GitHub username. -If you are contributing on behalf of a company, ensure you are authorized to do -so before signing. diff --git a/pj_base/include/pj_base/number_parse.hpp b/pj_base/include/pj_base/number_parse.hpp new file mode 100644 index 0000000..101d2d9 --- /dev/null +++ b/pj_base/include/pj_base/number_parse.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace PJ { + +/// Parse the *entire* @p text as a number of type T, returning nullopt unless +/// the whole string is a valid, in-range value (empty input, trailing +/// characters, or overflow all yield nullopt). +/// +/// Use this instead of std::from_chars when T may be floating-point: Apple +/// Clang's libc++ does not implement the std::from_chars floating-point +/// overloads, so those are routed through std::strto* here. Integral types go +/// straight to std::from_chars on every toolchain. +template +[[nodiscard]] std::optional parseNumber(std::string_view text) { + static_assert( + std::is_arithmetic_v && !std::is_same_v, + "parseNumber supports integral and floating-point types, not bool"); + if (text.empty()) { + return std::nullopt; + } + if constexpr (std::is_floating_point_v) { + // std::strto* needs a null-terminated buffer and @p text may be a + // non-terminated view, so copy it. Number strings are short — the + // allocation is negligible for the config/settings paths this serves. + const std::string buffer(text); + const char* begin = buffer.c_str(); + char* last = nullptr; + errno = 0; + T out{}; + if constexpr (std::is_same_v) { + out = std::strtof(begin, &last); + } else if constexpr (std::is_same_v) { + out = std::strtold(begin, &last); + } else { + out = std::strtod(begin, &last); + } + if (errno != 0 || last != begin + buffer.size()) { + return std::nullopt; + } + return out; + } else { + T out{}; + const char* begin = text.data(); + const char* end = begin + text.size(); + const auto [ptr, ec] = std::from_chars(begin, end, out); + if (ec != std::errc{} || ptr != end) { + return std::nullopt; + } + return out; + } +} + +} // namespace PJ diff --git a/pj_base/include/pj_base/sdk/plugin_data_api.hpp b/pj_base/include/pj_base/sdk/plugin_data_api.hpp index 1362878..23329ec 100644 --- a/pj_base/include/pj_base/sdk/plugin_data_api.hpp +++ b/pj_base/include/pj_base/sdk/plugin_data_api.hpp @@ -2,7 +2,6 @@ // Copyright 2026 Davide Faconti // SPDX-License-Identifier: Apache-2.0 -#include #include #include #include @@ -18,6 +17,7 @@ #include "pj_base/builtin/builtin_object.hpp" #include "pj_base/expected.hpp" +#include "pj_base/number_parse.hpp" #include "pj_base/plugin_data_api.h" #include "pj_base/sdk/arrow.hpp" #include "pj_base/sdk/object_bytes.hpp" @@ -1309,11 +1309,11 @@ class SettingsValue { } [[nodiscard]] std::int64_t toInt(std::int64_t def = 0) const { - return parse(def); + return raw_.has_value() ? parseNumber(*raw_).value_or(def) : def; } [[nodiscard]] double toDouble(double def = 0.0) const { - return parse(def); + return raw_.has_value() ? parseNumber(*raw_).value_or(def) : def; } /// "true"/"1"/"on" → true; "false"/"0"/"off" → false; otherwise @p def. @@ -1332,18 +1332,6 @@ class SettingsValue { } private: - template - [[nodiscard]] T parse(T def) const { - if (!raw_.has_value()) { - return def; - } - T out{}; - const char* begin = raw_->data(); - const char* end = begin + raw_->size(); - auto [ptr, ec] = std::from_chars(begin, end, out); - return (ec == std::errc{} && ptr == end) ? out : def; - } - std::optional raw_; };