diff --git a/.github/workflows/ci-conan.yml b/.github/workflows/ci-conan.yml index 5528a536..52b4955c 100644 --- a/.github/workflows/ci-conan.yml +++ b/.github/workflows/ci-conan.yml @@ -35,11 +35,12 @@ env: jobs: build: - name: "C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" + name: "${{ matrix.formatting }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: + formatting: ["std::format", "fmtlib"] std: [20, 23] config: # - { @@ -65,6 +66,7 @@ jobs: cxx: "g++-12", }, cxx_modules: "False", + std_format_support: "False", conan-config: "", } - { @@ -78,6 +80,7 @@ jobs: cxx: "g++-13", }, cxx_modules: "False", + std_format_support: "True", conan-config: "", } - { @@ -92,6 +95,7 @@ jobs: }, lib: "libc++", cxx_modules: "False", + std_format_support: "False", conan-config: "", } - { @@ -106,6 +110,7 @@ jobs: }, lib: "libc++", cxx_modules: "True", + std_format_support: "True", conan-config: "", } - { @@ -119,9 +124,13 @@ jobs: cxx: "clang++", }, cxx_modules: "False", + std_format_support: "False", conan-config: "", } build_type: ["Release", "Debug"] + exclude: + - formatting: "std::format" + config: { std_format_support: "False" } env: CC: ${{ matrix.config.compiler.cc }} @@ -137,13 +146,14 @@ jobs: cache-name: cache-conan-data with: path: ~/.conan2/p - key: build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} + key: build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} restore-keys: | - build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- - build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- - build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- - build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- - build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}- build-${{ matrix.config.os }}- - uses: hendrikmuhs/ccache-action@v1.2 if: runner.os == 'Linux' @@ -196,12 +206,13 @@ jobs: sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{ matrix.std }}/' ~/.conan2/profiles/default sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{ matrix.build_type }}/' ~/.conan2/profiles/default conan profile show -pr default + - run: echo "use_fmtlib=$([ "${{ matrix.formatting }}" == "fmtlib" ] && echo "True" || echo "False")" >> $GITHUB_ENV - name: Create Conan package shell: bash run: | conan create . --user mpusz --channel ${CHANNEL} --lockfile-out=package.lock \ -b mp-units/* -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" \ - -c user.build:all=True -o cxx_modules=${{ matrix.config.cxx_modules }} ${{ matrix.config.conan-config }} + -c user.build:all=True -o cxx_modules=${{ matrix.config.cxx_modules }} -o use_fmtlib=${{ env.use_fmtlib }} ${{ matrix.config.conan-config }} - name: Obtain package reference id: get-package-ref shell: bash diff --git a/.github/workflows/ci-test-package-cmake.yml b/.github/workflows/ci-test-package-cmake.yml index faad5309..a24bbefb 100644 --- a/.github/workflows/ci-test-package-cmake.yml +++ b/.github/workflows/ci-test-package-cmake.yml @@ -36,11 +36,12 @@ on: jobs: test_package: - name: "C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" + name: "${{ matrix.formatting }} C++${{ matrix.std }} ${{ matrix.config.name }} ${{ matrix.build_type }}" runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: + formatting: ["std::format", "fmtlib"] std: [20, 23] config: # - { @@ -64,6 +65,7 @@ jobs: cxx: "g++-12", }, cxx_modules: "False", + std_format_support: "False", } - { name: "GCC-13", @@ -76,6 +78,7 @@ jobs: cxx: "g++-13", }, cxx_modules: "False", + std_format_support: "True", } - { name: "Clang-16", @@ -89,6 +92,7 @@ jobs: }, lib: "libc++", cxx_modules: "False", + std_format_support: "False", } - { name: "Clang-17", @@ -102,6 +106,7 @@ jobs: }, lib: "libc++", cxx_modules: "False", + std_format_support: "True", } - { name: "Apple Clang 15", @@ -114,8 +119,12 @@ jobs: cxx: "clang++", }, cxx_modules: "False", + std_format_support: "False", } build_type: ["Release", "Debug"] + exclude: + - formatting: "std::format" + config: { std_format_support: "False" } env: CC: ${{ matrix.config.compiler.cc }} @@ -136,13 +145,14 @@ jobs: cache-name: cache-conan-data with: path: ~/.conan2/p - key: build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} + key: build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}-${{ env.cache_id }} restore-keys: | - build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- - build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- - build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- - build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- - build-${{ matrix.config.os }}-${{ matrix.config.compiler.type }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}-${{ matrix.std }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}-${{ matrix.config.compiler.version }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}-${{ matrix.build_type }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}-${{ matrix.config.lib }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}-${{ matrix.config.compiler.type }}- + build-${{ matrix.config.os }}-${{ matrix.formatting }}- build-${{ matrix.config.os }}- - name: Install gcc-13 if: matrix.config.compiler.type == 'GCC' && matrix.config.compiler.version == '13' @@ -191,10 +201,12 @@ jobs: sed -i.backup '/^\[settings\]$/,/^\[/ s/^compiler.cppstd=.*/compiler.cppstd=${{ matrix.std }}/' ~/.conan2/profiles/default sed -i.backup '/^\[settings\]$/,/^\[/ s/^build_type=.*/build_type=${{ matrix.build_type }}/' ~/.conan2/profiles/default conan profile show -pr default + - run: echo "use_fmtlib=$([ "${{ matrix.formatting }}" == "fmtlib" ] && echo "True" || echo "False")" >> $GITHUB_ENV - name: Install Conan dependencies shell: bash run: | - conan install . -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" -c user.build:all=False -o cxx_modules=${{ matrix.config.cxx_modules }} + conan install . -b missing -c tools.cmake.cmaketoolchain:generator="Ninja Multi-Config" -c user.build:all=False \ + -o cxx_modules=${{ matrix.config.cxx_modules }} -o use_fmtlib=${{ env.use_fmtlib }} mv CMakeUserPresets.json src - name: Configure mp-units CMake if: matrix.config.compiler.type == 'VISUAL' || matrix.config.compiler.type == 'MSVC' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dce1da24..d8464ae8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -105,7 +105,7 @@ jobs: conan profile detect --force conan remote add artifactory https://mpusz.jfrog.io/artifactory/api/conan/conan-oss mkdir _lgtm_build_dir && cd _lgtm_build_dir - conan build .. -s compiler.cppstd=20 -c user.build:all=True -b missing + conan build .. -s compiler.cppstd=20 -c user.build:all=True -o use_fmtlib=True -b missing - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 diff --git a/README.md b/README.md index 47a2d2f4..0781f9f4 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ analysis and unit/quantity manipulation. Here is a small example of possible operations: ```cpp -#include +import mp_units; using namespace mp_units; using namespace mp_units::si::unit_symbols; @@ -66,7 +66,7 @@ using namespace mp_units::si::unit_symbols; // simple numeric operations static_assert(10 * km / 2 == 5 * km); -// unit conversions +// conversions to common units static_assert(1 * h == 3600 * s); static_assert(1 * km + 1 * m == 1001 * m); @@ -90,12 +90,9 @@ and dimensional analysis can be performed without sacrificing on runtime perform accuracy. Please see the below example for a quick preview of basic library features: ```cpp -#include -#include -#include -#include -#include +#include #include +import mp_units; using namespace mp_units; @@ -118,13 +115,13 @@ int main() constexpr quantity v6 = value_cast(v4); constexpr quantity v7 = value_cast(v6); - std::cout << v1 << '\n'; // 110 km/h - std::cout << v2 << '\n'; // 70 mi/h - std::cout << std::format("{}", v3) << '\n'; // 110 km/h - std::cout << std::format("{:*^14}", v4) << '\n'; // ***70 mi/h**** - std::cout << std::format("{:%Q in %q}", v5) << '\n'; // 30.5556 in m/s - std::cout << std::format("{0:%Q} in {0:%q}", v6) << '\n'; // 31.2928 in m/s - std::cout << std::format("{:%Q}", v7) << '\n'; // 31 + std::cout << v1 << '\n'; // 110 km/h + std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h + std::cout << std::format("{:*^10}\n", v3); // *110 km/h* + std::cout << std::format("{:%N in %U}\n", v4); // 70 in mi/h + std::cout << std::format("{:{%N:.2f}%?%U}\n", v5); // 30.56 in m/s + std::cout << std::format("{:{%N:.2f}%?{%U:n}}\n", v6); // 31.29 in m s⁻¹ + std::cout << std::format("{:%N}\n", v7); // 31 } ``` diff --git a/conanfile.py b/conanfile.py index d5f1e0df..2825223b 100644 --- a/conanfile.py +++ b/conanfile.py @@ -28,7 +28,6 @@ from conan.errors import ConanInvalidConfiguration from conan.tools.build import check_min_cppstd from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout from conan.tools.files import copy, load, rmdir -from conan.tools.scm import Version required_conan_version = ">=2.0.0" @@ -60,7 +59,7 @@ class MPUnitsConan(ConanFile): } default_options = { "cxx_modules": False, - "use_fmtlib": True, + "use_fmtlib": False, } tool_requires = "cmake/[>=3.28.1]" exports = ["LICENSE.md"] @@ -88,6 +87,15 @@ class MPUnitsConan(ConanFile): # , "msvc": "192" } + @property + def _std_format_minimum_compilers_version(self): + return { + "gcc": "13", + "clang": "17" + # , "apple-clang": "15" + # , "msvc": "192" + } + @property def _build_all(self): return bool(self.conf.get("user.build:all", default=False)) @@ -129,6 +137,13 @@ class MPUnitsConan(ConanFile): raise ConanInvalidConfiguration( f"{self.ref} requires at least {compiler} {min_version} ({compiler.version} in use)" ) + if not self.options.use_fmtlib: + min_version = self._std_format_minimum_compilers_version.get(str(compiler)) + if min_version and loose_lt_semver(str(compiler.version), min_version): + raise ConanInvalidConfiguration( + f"`std::format` requires at least {compiler} {min_version} ({compiler.version} in use). " + "Use `-o use_fmtlib=True` instead." + ) def layout(self): cmake_layout(self) diff --git a/docs/getting_started/installation_and_usage.md b/docs/getting_started/installation_and_usage.md index a17a4fce..b8a18710 100644 --- a/docs/getting_started/installation_and_usage.md +++ b/docs/getting_started/installation_and_usage.md @@ -23,7 +23,7 @@ specific feature: | Feature | gcc | clang | apple-clang | MSVC | |----------------------|:----:|:-----:|:-----------:|:----:| | **Minimum support** | 12 | 16 | 15 | None | -| **`std::format`** | None | None | None | None | +| **`std::format`** | 13 | 17 | None | None | | **C++ modules** | None | 17 | None | None | | **C++23 extensions** | 14 | 18 | None | None | @@ -185,7 +185,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [use_fmtlib](#use_fmtlib){ #use_fmtlib } -: [:octicons-tag-24: 2.2.0][use fmtlib support] · :octicons-milestone-24: `True`/`False` (Default: `True`) +: [:octicons-tag-24: 2.2.0][use fmtlib support] · :octicons-milestone-24: `True`/`False` (Default: `False`) Forces usage of [{fmt}](https://github.com/fmtlib/fmt) library instead of the C++20 Standard Library features. @@ -232,7 +232,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [`MP_UNITS_USE_FMTLIB`](#MP_UNITS_USE_FMTLIB){ #MP_UNITS_USE_FMTLIB } -: [:octicons-tag-24: 2.2.0][use fmtlib support] · :octicons-milestone-24: `ON`/`OFF` (Default: `ON`) +: [:octicons-tag-24: 2.2.0][use fmtlib support] · :octicons-milestone-24: `ON`/`OFF` (Default: `OFF`) Forces usage of [{fmt}](https://github.com/fmtlib/fmt) library instead of the C++20 Standard Library features. diff --git a/docs/getting_started/look_and_feel.md b/docs/getting_started/look_and_feel.md index 581fd1ba..f0a7307b 100644 --- a/docs/getting_started/look_and_feel.md +++ b/docs/getting_started/look_and_feel.md @@ -13,7 +13,7 @@ Here is a small example of operations possible on scalar quantities: // simple numeric operations static_assert(10 * km / 2 == 5 * km); - // unit conversions + // conversions to common units static_assert(1 * h == 3600 * s); static_assert(1 * km + 1 * m == 1001 * m); @@ -40,7 +40,7 @@ Here is a small example of operations possible on scalar quantities: // simple numeric operations static_assert(10 * km / 2 == 5 * km); - // unit conversions + // conversions to common units static_assert(1 * h == 3600 * s); static_assert(1 * km + 1 * m == 1001 * m); @@ -56,7 +56,7 @@ Here is a small example of operations possible on scalar quantities: static_assert(1000 / (1 * s) == 1 * kHz); ``` -!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/81Ev7qhTd)" +!!! example "[Try it on Compiler Explorer](https://godbolt.org/z/ox8a8dGTz)" This library requires some C++20 features ([concepts and constraints](https://en.cppreference.com/w/cpp/language/constraints), @@ -69,6 +69,7 @@ performed without sacrificing accuracy. Please see the below example for a quick === "C++ modules" ```cpp + #include #include import mp_units; @@ -93,13 +94,13 @@ performed without sacrificing accuracy. Please see the below example for a quick constexpr quantity v6 = value_cast(v4); constexpr quantity v7 = value_cast(v6); - std::cout << v1 << '\n'; // 110 km/h - std::cout << v2 << '\n'; // 70 mi/h - std::cout << std::format("{}", v3) << '\n'; // 110 km/h - std::cout << std::format("{:*^14}", v4) << '\n'; // ***70 mi/h**** - std::cout << std::format("{:%Q in %q}", v5) << '\n'; // 30.5556 in m/s - std::cout << std::format("{0:%Q} in {0:%q}", v6) << '\n'; // 31.2928 in m/s - std::cout << std::format("{:%Q}", v7) << '\n'; // 31 + std::cout << v1 << '\n'; // 110 km/h + std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h + std::cout << std::format("{:*^10}\n", v3); // *110 km/h* + std::cout << std::format("{:%N in %U}\n", v4); // 70 in mi/h + std::cout << std::format("{:{%N:.2f}%?%U}\n", v5); // 30.56 in m/s + std::cout << std::format("{:{%N:.2f}%?{%U:n}}\n", v6); // 31.29 in m s⁻¹ + std::cout << std::format("{:%N}\n", v7); // 31 } ``` @@ -111,6 +112,7 @@ performed without sacrificing accuracy. Please see the below example for a quick #include #include #include + #include #include using namespace mp_units; @@ -134,13 +136,13 @@ performed without sacrificing accuracy. Please see the below example for a quick constexpr quantity v6 = value_cast(v4); constexpr quantity v7 = value_cast(v6); - std::cout << v1 << '\n'; // 110 km/h - std::cout << v2 << '\n'; // 70 mi/h - std::cout << std::format("{}", v3) << '\n'; // 110 km/h - std::cout << std::format("{:*^14}", v4) << '\n'; // ***70 mi/h**** - std::cout << std::format("{:%Q in %q}", v5) << '\n'; // 30.5556 in m/s - std::cout << std::format("{0:%Q} in {0:%q}", v6) << '\n'; // 31.2928 in m/s - std::cout << std::format("{:%Q}", v7) << '\n'; // 31 + std::cout << v1 << '\n'; // 110 km/h + std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h + std::cout << std::format("{:*^10}\n", v3); // *110 km/h* + std::cout << std::format("{:%N in %U}\n", v4); // 70 in mi/h + std::cout << std::format("{:{%N:.2f}%?%U}\n", v5); // 30.56 in m/s + std::cout << std::format("{:{%N:.2f}%?{%U:n}}\n", v6); // 31.29 in m s⁻¹ + std::cout << std::format("{:%N}\n", v7); // 31 } ``` diff --git a/docs/index.md b/docs/index.md index 753ba7a2..1f3fc081 100644 --- a/docs/index.md +++ b/docs/index.md @@ -31,7 +31,7 @@ The library source code is hosted on [GitHub](https://github.com/mpusz/mp-units) | Feature | gcc | clang | apple-clang | MSVC | |----------------------|:----:|:-----:|:-----------:|:----:| | **Minimum support** | 12 | 16 | 15 | None | - | **`std::format`** | None | None | None | None | + | **`std::format`** | 13 | 17 | None | None | | **C++ modules** | None | 17 | None | None | | **C++23 extensions** | 14 | 18 | None | None | diff --git a/docs/users_guide/examples/hello_units.md b/docs/users_guide/examples/hello_units.md index 8e2935a9..61656ef2 100644 --- a/docs/users_guide/examples/hello_units.md +++ b/docs/users_guide/examples/hello_units.md @@ -18,20 +18,20 @@ First, we either import the `mp_units` module or include the headers for: - text formatting and stream output support ```cpp title="hello_units.cpp" linenums="1" ---8<-- "example/hello_units.cpp:28:39" +--8<-- "example/hello_units.cpp:28:40" ``` Also, to shorten the definitions, we "import" all the symbols from the `mp_units` namespace. -```cpp title="hello_units.cpp" linenums="12" ---8<-- "example/hello_units.cpp:40:41" +```cpp title="hello_units.cpp" linenums="13" +--8<-- "example/hello_units.cpp:41:42" ``` Next, we define a simple function that calculates the average speed based on the provided arguments of length and time: -```cpp title="hello_units.cpp" linenums="13" ---8<-- "example/hello_units.cpp:42:45" +```cpp title="hello_units.cpp" linenums="14" +--8<-- "example/hello_units.cpp:43:46" ``` The above function template takes any quantities implicitly convertible to `isq::length` @@ -45,16 +45,16 @@ that its quantity type is implicitly convertible to `isq::speed`. type is beneficial for users of such a function as it provides more information of what to expect from a function than just using `auto`. -```cpp title="hello_units.cpp" linenums="17" ---8<-- "example/hello_units.cpp:47:50" +```cpp title="hello_units.cpp" linenums="18" +--8<-- "example/hello_units.cpp:48:51" ``` The above lines explicitly opt into using unit symbols from two systems of units. As this introduces a lot of short identifiers into the current scope, it is not done implicitly while including a header file. -```cpp title="hello_units.cpp" linenums="21" ---8<-- "example/hello_units.cpp:52:58" +```cpp title="hello_units.cpp" linenums="22" +--8<-- "example/hello_units.cpp:53:59" ``` - Lines `21` & `22` create a quantity of kind `isq::length / isq::time` with the numbers @@ -74,8 +74,8 @@ implicitly while including a header file. - Line `27` does a [value-truncating conversion](../framework_basics/value_conversions.md#value-truncating-conversions) of changing the underlying representation type from `double` to `int`. -```cpp title="hello_units.cpp" linenums="28" ---8<-- "example/hello_units.cpp:60" +```cpp title="hello_units.cpp" linenums="29" +--8<-- "example/hello_units.cpp:61" ``` The above presents [various ways to print a quantity](../framework_basics/text_output.md). diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 36364f12..9e08103d 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -72,4 +72,4 @@ add_example(total_energy) add_example(unmanned_aerial_vehicle example_utils) add_subdirectory(glide_computer_lib) -add_subdirectory(kalman_filter) +# add_subdirectory(kalman_filter) diff --git a/example/clcpp_response.cpp b/example/clcpp_response.cpp index 98664b85..db2be2ca 100644 --- a/example/clcpp_response.cpp +++ b/example/clcpp_response.cpp @@ -118,7 +118,7 @@ void calcs_comparison() const auto L1A = 2.f * fm; const auto L2A = 3.f * fm; const auto LrA = L1A + L2A; - std::cout << MP_UNITS_STD_FMT::format("{:%.30Q %q}\n + {:%.30Q %q}\n = {:%.30Q %q}\n\n", L1A, L2A, LrA); + std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30} %U}\n + {:{%N:.30} %U}\n = {:{%N:.30} %U}\n\n", L1A, L2A, LrA); std::cout << "The single unit method must convert large\n" "or small values in other units to the base unit.\n" @@ -127,17 +127,17 @@ void calcs_comparison() const auto L1B = L1A.in(m); const auto L2B = L2A.in(m); const auto LrB = L1B + L2B; - std::cout << MP_UNITS_STD_FMT::format("{:%.30eQ %q}\n + {:%.30eQ %q}\n = {:%.30eQ %q}\n\n", L1B, L2B, LrB); + std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30e} %U}\n + {:{%N:.30e} %U}\n = {:{%N:.30e} %U}\n\n", L1B, L2B, LrB); std::cout << "In multiplication and division:\n\n"; const quantity ArA = L1A * L2A; - std::cout << MP_UNITS_STD_FMT::format("{:%.30Q %q}\n * {:%.30Q %q}\n = {:%.30Q %q}\n\n", L1A, L2A, ArA); + std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30} %U}\n * {:{%N:.30} %U}\n = {:{%N:.30} %U}\n\n", L1A, L2A, ArA); std::cout << "similar problems arise\n\n"; const quantity ArB = L1B * L2B; - std::cout << MP_UNITS_STD_FMT::format("{:%.30eQ %q}\n * {:%.30eQ %q}\n = {:%.30eQ %q}\n\n", L1B, L2B, ArB); + std::cout << MP_UNITS_STD_FMT::format("{:{%N:.30e} %U}\n * {:{%N:.30e} %U}\n = {:{%N:.30e} %U}\n\n", L1B, L2B, ArB); } } // namespace diff --git a/example/conversion_factor.cpp b/example/conversion_factor.cpp index e98dd694..308fb2a0 100644 --- a/example/conversion_factor.cpp +++ b/example/conversion_factor.cpp @@ -58,7 +58,7 @@ int main() std::cout << MP_UNITS_STD_FMT::format("therefore ratio lengthA / lengthB == {}\n\n", lengthA / lengthB); - std::cout << MP_UNITS_STD_FMT::format("conversion factor from lengthA::unit of {:%q} to lengthB::unit of {:%q}:\n\n", + std::cout << MP_UNITS_STD_FMT::format("conversion factor from lengthA::unit of {:%U} to lengthB::unit of {:%U}:\n\n", lengthA, lengthB) << MP_UNITS_STD_FMT::format("lengthB.value( {} ) == lengthA.value( {} ) * conversion_factor( {} )\n", lengthB.numerical_value_ref_in(lengthB.unit), diff --git a/example/glide_computer.cpp b/example/glide_computer.cpp index b834e917..5408d39f 100644 --- a/example/glide_computer.cpp +++ b/example/glide_computer.cpp @@ -87,8 +87,8 @@ void print(const R& gliders) std::cout << "- Polar:\n"; for (const auto& p : g.polar) { const auto ratio = glide_ratio(g.polar[0]).force_in(one); - std::cout << MP_UNITS_STD_FMT::format(" * {:%.4Q %q} @ {:%.1Q %q} -> {:%.1Q %q} ({:%.1Q %q})\n", p.climb, p.v, - ratio, + std::cout << MP_UNITS_STD_FMT::format(" * {:{%N:.4} %U} @ {:{%N:.1} %U} -> {:{%N:.1} %U} ({:{%N:.1} %U})\n", + p.climb, p.v, ratio, // TODO is it possible to make ADL work below (we need another set of trig // functions for strong angle in a different namespace) si::asin(1 / ratio).force_in(si::degree)); @@ -106,8 +106,8 @@ void print(const R& conditions) for (const auto& c : conditions) { std::cout << "- " << c.first << "\n"; const auto& w = c.second; - std::cout << " * Cloud base: " << MP_UNITS_STD_FMT::format("{:%.0Q %q}", w.cloud_base) << " AGL\n"; - std::cout << " * Thermals strength: " << MP_UNITS_STD_FMT::format("{:%.1Q %q}", w.thermal_strength) << "\n"; + std::cout << " * Cloud base: " << MP_UNITS_STD_FMT::format("{:{%N:.0} %U}", w.cloud_base) << " AGL\n"; + std::cout << " * Thermals strength: " << MP_UNITS_STD_FMT::format("{:{%N:.1} %U}", w.thermal_strength) << "\n"; std::cout << "\n"; } } @@ -119,7 +119,7 @@ void print(const R& waypoints) std::cout << "Waypoints:\n"; std::cout << "==========\n"; for (const auto& w : waypoints) - std::cout << MP_UNITS_STD_FMT::format("- {}: {} {}, {:%.1Q %q}\n", w.name, w.pos.lat, w.pos.lon, w.alt); + std::cout << MP_UNITS_STD_FMT::format("- {}: {} {}, {:{%N:.1} %U}\n", w.name, w.pos.lat, w.pos.lon, w.alt); std::cout << "\n"; } @@ -130,12 +130,12 @@ void print(const task& t) std::cout << "- Start: " << t.get_start().name << "\n"; std::cout << "- Finish: " << t.get_finish().name << "\n"; - std::cout << "- Length: " << MP_UNITS_STD_FMT::format("{:%.1Q %q}", t.get_distance()) << "\n"; + std::cout << "- Length: " << MP_UNITS_STD_FMT::format("{:{%N:.1} %U}", t.get_distance()) << "\n"; std::cout << "- Legs: " << "\n"; for (const auto& l : t.get_legs()) - std::cout << MP_UNITS_STD_FMT::format(" * {} -> {} ({:%.1Q %q})\n", l.begin().name, l.end().name, + std::cout << MP_UNITS_STD_FMT::format(" * {} -> {} ({:{%N:.1} %U})\n", l.begin().name, l.end().name, l.get_distance()); std::cout << "\n"; } @@ -144,7 +144,7 @@ void print(const safety& s) { std::cout << "Safety:\n"; std::cout << "=======\n"; - std::cout << "- Min AGL separation: " << MP_UNITS_STD_FMT::format("{:%.0Q %q}", s.min_agl_height) << "\n"; + std::cout << "- Min AGL separation: " << MP_UNITS_STD_FMT::format("{:{%N:.0} %U}", s.min_agl_height) << "\n"; std::cout << "\n"; } @@ -153,8 +153,8 @@ void print(const aircraft_tow& tow) std::cout << "Tow:\n"; std::cout << "====\n"; std::cout << "- Type: aircraft\n"; - std::cout << "- Height: " << MP_UNITS_STD_FMT::format("{:%.0Q %q}", tow.height_agl) << "\n"; - std::cout << "- Performance: " << MP_UNITS_STD_FMT::format("{:%.1Q %q}", tow.performance) << "\n"; + std::cout << "- Height: " << MP_UNITS_STD_FMT::format("{:{%N:.0} %U}", tow.height_agl) << "\n"; + std::cout << "- Performance: " << MP_UNITS_STD_FMT::format("{:{%N:.1} %U}", tow.performance) << "\n"; std::cout << "\n"; } diff --git a/example/glide_computer_lib/glide_computer_lib.cpp b/example/glide_computer_lib/glide_computer_lib.cpp index 8c3af99f..85cf2a0e 100644 --- a/example/glide_computer_lib/glide_computer_lib.cpp +++ b/example/glide_computer_lib/glide_computer_lib.cpp @@ -82,7 +82,8 @@ void print(std::string_view phase_name, timestamp start_ts, const glide_computer const glide_computer::flight_point& new_point) { std::cout << MP_UNITS_STD_FMT::format( - "| {:<12} | {:>9%.1Q %q} (Total: {:>9%.1Q %q}) | {:>8%.1Q %q} (Total: {:>8%.1Q %q}) | {:>7%.0Q %q} ({:>6%.0Q %q}) " + "| {:<12} | {:>9{%N:.1} %U} (Total: {:>9{%N:.1} %U}) | {:>8{%N:.1} %U} (Total: {:>8{%N:.1} %U}) | {:>7{%N:.0} %U} " + "({:>6{%N:.0} %U}) " "|\n", phase_name, value_cast(new_point.ts - point.ts), value_cast(new_point.ts - start_ts), new_point.dist - point.dist, new_point.dist, new_point.alt - point.alt, new_point.alt); diff --git a/example/hello_units.cpp b/example/hello_units.cpp index 9725926a..c54b9b04 100644 --- a/example/hello_units.cpp +++ b/example/hello_units.cpp @@ -26,6 +26,7 @@ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #include +#include #include #ifdef MP_UNITS_MODULES import mp_units; @@ -57,11 +58,11 @@ int main() constexpr quantity v6 = value_cast(v4); constexpr quantity v7 = value_cast(v6); - std::cout << v1 << '\n'; // 110 km/h - std::cout << v2 << '\n'; // 70 mi/h - std::cout << MP_UNITS_STD_FMT::format("{}", v3) << '\n'; // 110 km/h - std::cout << MP_UNITS_STD_FMT::format("{:*^14}", v4) << '\n'; // ***70 mi/h**** - std::cout << MP_UNITS_STD_FMT::format("{:%Q in %q}", v5) << '\n'; // 30.5556 in m/s - std::cout << MP_UNITS_STD_FMT::format("{0:%Q} in {0:%q}", v6) << '\n'; // 31.2928 in m/s - std::cout << MP_UNITS_STD_FMT::format("{:%Q}", v7) << '\n'; // 31 + std::cout << v1 << '\n'; // 110 km/h + std::cout << std::setw(10) << std::setfill('*') << v2 << '\n'; // ***70 mi/h + std::cout << MP_UNITS_STD_FMT::format("{:*^10}\n", v3); // *110 km/h* + std::cout << MP_UNITS_STD_FMT::format("{:%N in %U}\n", v4); // 70 in mi/h + std::cout << MP_UNITS_STD_FMT::format("{:{%N:.2f}%?%U}\n", v5); // 30.56 in m/s + std::cout << MP_UNITS_STD_FMT::format("{:{%N:.2f}%?{%U:n}}\n", v6); // 31.29 in m s⁻¹ + std::cout << MP_UNITS_STD_FMT::format("{:%N}\n", v7); // 31 } diff --git a/example/include/geographic.h b/example/include/geographic.h index 9ee94919..e2b92124 100644 --- a/example/include/geographic.h +++ b/example/include/geographic.h @@ -55,12 +55,14 @@ std::basic_ostream& operator<<(std::basic_ostream& } // namespace geographic -template<> -struct MP_UNITS_STD_FMT::formatter : formatter { +template +struct MP_UNITS_STD_FMT::formatter : + formatter { template - auto format(const geographic::msl_altitude& a, FormatContext& ctx) + auto format(const geographic::msl_altitude& a, FormatContext& ctx) const -> decltype(ctx.out()) { - formatter::format(a - geographic::mean_sea_level, ctx); + ctx.advance_to( + formatter::format(a - geographic::mean_sea_level, ctx)); return MP_UNITS_STD_FMT::format_to(ctx.out(), " AMSL"); } }; @@ -137,29 +139,29 @@ class std::numeric_limits> : public numeric_limits { static constexpr auto max() noexcept { return geographic::longitude(180); } }; -template -struct MP_UNITS_STD_FMT::formatter> : - formatter::quantity_type> { +template +struct MP_UNITS_STD_FMT::formatter, Char> : + formatter::quantity_type, Char> { template - auto format(geographic::latitude lat, FormatContext& ctx) + auto format(geographic::latitude lat, FormatContext& ctx) const -> decltype(ctx.out()) { const auto& q = lat.quantity_ref_from(geographic::equator); - formatter::quantity_type>::format(is_gteq_zero(q) ? q : -q, ctx); - MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " N" : "S"); - return ctx.out(); + ctx.advance_to( + formatter::quantity_type, Char>::format(is_gteq_zero(q) ? q : -q, ctx)); + return MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " N" : "S"); } }; -template -struct MP_UNITS_STD_FMT::formatter> : - formatter::quantity_type> { +template +struct MP_UNITS_STD_FMT::formatter, Char> : + formatter::quantity_type, Char> { template - auto format(geographic::longitude lon, FormatContext& ctx) + auto format(geographic::longitude lon, FormatContext& ctx) const -> decltype(ctx.out()) { const auto& q = lon.quantity_ref_from(geographic::prime_meridian); - formatter::quantity_type>::format(is_gteq_zero(q) ? q : -q, ctx); - MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " E" : " W"); - return ctx.out(); + ctx.advance_to( + formatter::quantity_type, Char>::format(is_gteq_zero(q) ? q : -q, ctx)); + return MP_UNITS_STD_FMT::format_to(ctx.out(), "{}", is_gteq_zero(q) ? " E" : " W"); } }; diff --git a/example/include/ranged_representation.h b/example/include/ranged_representation.h index 2b7fff52..678f650a 100644 --- a/example/include/ranged_representation.h +++ b/example/include/ranged_representation.h @@ -63,11 +63,11 @@ template inline constexpr bool mp_units::treat_as_floating_point> = mp_units::treat_as_floating_point; -template -struct MP_UNITS_STD_FMT::formatter> : formatter { +template +struct MP_UNITS_STD_FMT::formatter, Char> : formatter { template - auto format(const ranged_representation& v, FormatContext& ctx) + auto format(const ranged_representation& v, FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter::format(v.value(), ctx); + return formatter::format(v.value(), ctx); } }; diff --git a/example/include/validated_type.h b/example/include/validated_type.h index 814cefd7..f9a8e516 100644 --- a/example/include/validated_type.h +++ b/example/include/validated_type.h @@ -123,11 +123,11 @@ std::basic_ostream& operator<<(std::basic_ostream& } -template -struct MP_UNITS_STD_FMT::formatter> : formatter { +template +struct MP_UNITS_STD_FMT::formatter, Char> : formatter { template - auto format(const validated_type& v, FormatContext& ctx) + auto format(const validated_type& v, FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter::format(v.value(), ctx); + return formatter::format(v.value(), ctx); } }; diff --git a/example/si_constants.cpp b/example/si_constants.cpp index 2567210a..a2f38295 100644 --- a/example/si_constants.cpp +++ b/example/si_constants.cpp @@ -45,19 +45,19 @@ int main() using namespace mp_units::si::unit_symbols; std::cout << "The seven defining constants of the SI and the seven corresponding units they define:\n"; - std::cout << MP_UNITS_STD_FMT::format("- hyperfine transition frequency of Cs: {} = {:%.0Q %q}\n", + std::cout << MP_UNITS_STD_FMT::format("- hyperfine transition frequency of Cs: {} = {:{%N:.0} %U}\n", 1. * si2019::hyperfine_structure_transition_frequency_of_cs, (1. * si2019::hyperfine_structure_transition_frequency_of_cs).in(Hz)); - std::cout << MP_UNITS_STD_FMT::format("- speed of light in vacuum: {} = {:%.0Q %q}\n", + std::cout << MP_UNITS_STD_FMT::format("- speed of light in vacuum: {} = {:{%N:.0} %U}\n", 1. * si2019::speed_of_light_in_vacuum, (1. * si2019::speed_of_light_in_vacuum).in(m / s)); - std::cout << MP_UNITS_STD_FMT::format("- Planck constant: {} = {:%.8eQ %q}\n", + std::cout << MP_UNITS_STD_FMT::format("- Planck constant: {} = {:{%N:.8e} %U}\n", 1. * si2019::planck_constant, (1. * si2019::planck_constant).in(J * s)); - std::cout << MP_UNITS_STD_FMT::format("- elementary charge: {} = {:%.9eQ %q}\n", + std::cout << MP_UNITS_STD_FMT::format("- elementary charge: {} = {:{%N:.9e} %U}\n", 1. * si2019::elementary_charge, (1. * si2019::elementary_charge).in(C)); - std::cout << MP_UNITS_STD_FMT::format("- Boltzmann constant: {} = {:%.6eQ %q}\n", + std::cout << MP_UNITS_STD_FMT::format("- Boltzmann constant: {} = {:{%N:.6e} %U}\n", 1. * si2019::boltzmann_constant, (1. * si2019::boltzmann_constant).in(J / K)); - std::cout << MP_UNITS_STD_FMT::format("- Avogadro constant: {} = {:%.8eQ %q}\n", + std::cout << MP_UNITS_STD_FMT::format("- Avogadro constant: {} = {:{%N:.8e} %U}\n", 1. * si2019::avogadro_constant, (1. * si2019::avogadro_constant).in(one / mol)); std::cout << MP_UNITS_STD_FMT::format("- luminous efficacy: {} = {}\n", 1. * si2019::luminous_efficacy, (1. * si2019::luminous_efficacy).in(lm / W)); diff --git a/example/spectroscopy_units.cpp b/example/spectroscopy_units.cpp index 0badec0b..78869409 100644 --- a/example/spectroscopy_units.cpp +++ b/example/spectroscopy_units.cpp @@ -55,8 +55,8 @@ template T1, QuantityOf T2, QuantityOf< QuantityOf T4, QuantityOf T5> void print_line(const std::tuple& t) { - MP_UNITS_STD_FMT::println("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |", std::get<0>(t), std::get<1>(t), - std::get<2>(t), std::get<3>(t), std::get<4>(t)); + std::cout << MP_UNITS_STD_FMT::format("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |\n", std::get<0>(t), + std::get<1>(t), std::get<2>(t), std::get<3>(t), std::get<4>(t)); } // prints quantities in semi-SI units @@ -65,9 +65,9 @@ template T1, QuantityOf T2, QuantityOf< QuantityOf T4, QuantityOf T5> void print_line_si(const std::tuple& t) { - MP_UNITS_STD_FMT::println("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |", std::get<0>(t).in(eV), - std::get<1>(t).in(one / cm), std::get<2>(t).in(THz), std::get<3>(t).in(K), - std::get<4>(t).in(um)); + std::cout << MP_UNITS_STD_FMT::format("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |\n", std::get<0>(t).in(eV), + std::get<1>(t).in(one / cm), std::get<2>(t).in(THz), std::get<3>(t).in(K), + std::get<4>(t).in(um)); } int main() @@ -92,16 +92,16 @@ int main() const auto t5 = std::make_tuple(isq::energy(h * c / q5), isq::wavenumber(1 / q5), isq::frequency(c / q5), isq::thermodynamic_temperature(h * c / (q5 * kb)), q5); - MP_UNITS_STD_FMT::println("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |", "Energy", "Wavenumber", "Frequency", - "Temperature", "Wavelength"); - MP_UNITS_STD_FMT::println("| {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} |", ""); + std::cout << MP_UNITS_STD_FMT::format("| {:<15} | {:<15} | {:<15} | {:<15} | {:<15} |\n", "Energy", "Wavenumber", + "Frequency", "Temperature", "Wavelength"); + std::cout << MP_UNITS_STD_FMT::format("| {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} |\n", ""); print_line(t1); print_line(t2); print_line(t3); print_line(t4); print_line(t5); - MP_UNITS_STD_FMT::println("| {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} |", ""); + std::cout << MP_UNITS_STD_FMT::format("| {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} | {0:-^15} |\n", ""); print_line_si(t1); print_line_si(t2); print_line_si(t3); diff --git a/example/unmanned_aerial_vehicle.cpp b/example/unmanned_aerial_vehicle.cpp index 05bf9309..13f24840 100644 --- a/example/unmanned_aerial_vehicle.cpp +++ b/example/unmanned_aerial_vehicle.cpp @@ -79,13 +79,13 @@ std::basic_ostream& operator<<(std::basic_ostream& return os << a - a.absolute_point_origin << " HAE(" << to_text(a.absolute_point_origin.egm) << ")"; } -template +template requires(is_hae(QP::absolute_point_origin)) -struct MP_UNITS_STD_FMT::formatter : formatter { +struct MP_UNITS_STD_FMT::formatter : formatter { template - auto format(const QP& a, FormatContext& ctx) + auto format(const QP& a, FormatContext& ctx) const -> decltype(ctx.out()) { - formatter::format(a - a.absolute_point_origin, ctx); + formatter::format(a - a.absolute_point_origin, ctx); return MP_UNITS_STD_FMT::format_to(ctx.out(), " HAE({})", to_text(QP::absolute_point_origin.egm)); } }; @@ -123,12 +123,12 @@ std::basic_ostream& operator<<(std::basic_ostream& return os << a.quantity_from(height_above_launch) << " HAL"; } -template<> -struct MP_UNITS_STD_FMT::formatter : formatter { +template +struct MP_UNITS_STD_FMT::formatter : formatter { template - auto format(const hal_altitude& a, FormatContext& ctx) + auto format(const hal_altitude& a, FormatContext& ctx) const -> decltype(ctx.out()) { - formatter::format(a.quantity_from(height_above_launch), ctx); + formatter::format(a.quantity_from(height_above_launch), ctx); return MP_UNITS_STD_FMT::format_to(ctx.out(), " HAL"); } }; @@ -170,6 +170,6 @@ int main() }; waypoint wpt = {"EPPR", {54.24772_N, 18.6745_E}, mean_sea_level + 16. * ft}; - std::cout << MP_UNITS_STD_FMT::format("{}: {} {}, {:%.2Q %q}, {:%.2Q %q}\n", wpt.name, wpt.pos.lat, wpt.pos.lon, + std::cout << MP_UNITS_STD_FMT::format("{}: {} {}, {:{%N:.2} %U}, {:{%N:.2} %U}\n", wpt.name, wpt.pos.lat, wpt.pos.lon, wpt.msl_alt, to_hae(wpt.msl_alt, wpt.pos)); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1665b352..d831a7f0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,7 +31,7 @@ message(STATUS "${projectPrefix}BUILD_CXX_MODULES: ${${projectPrefix}BUILD_CXX_M option(${projectPrefix}AS_SYSTEM_HEADERS "Exports library as system headers" OFF) message(STATUS "${projectPrefix}AS_SYSTEM_HEADERS: ${${projectPrefix}AS_SYSTEM_HEADERS}") -option(${projectPrefix}USE_FMTLIB "Enables usage of fmtlib instead of the 'std::format' facilities" ON) +option(${projectPrefix}USE_FMTLIB "Enables usage of fmtlib instead of the 'std::format' facilities" OFF) message(STATUS "${projectPrefix}USE_FMTLIB: ${${projectPrefix}USE_FMTLIB}") list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") diff --git a/src/core/include/mp-units/bits/external/hacks.h b/src/core/include/mp-units/bits/external/hacks.h index b453aac6..7e3c13c6 100644 --- a/src/core/include/mp-units/bits/external/hacks.h +++ b/src/core/include/mp-units/bits/external/hacks.h @@ -22,6 +22,8 @@ #pragma once +#include + #if __clang__ #define MP_UNITS_COMP_CLANG __clang_major__ #elif __GNUC__ @@ -114,8 +116,8 @@ MP_UNITS_DIAGNOSTIC_POP #define MP_UNITS_STD_FMT fmt #define MP_UNITS_FMT_LOCALE(loc) (loc).template get() -#define MP_UNITS_FMT_TO_ARG_ID(arg) static_cast(arg) -#define MP_UNITS_FMT_FROM_ARG_ID(arg) static_cast(arg) +#define MP_UNITS_FMT_TO_ARG_ID(arg) (arg) +#define MP_UNITS_FMT_FROM_ARG_ID(arg) (arg) // This re-uses code from fmt; #if FMT_EXCEPTIONS @@ -133,7 +135,7 @@ MP_UNITS_DIAGNOSTIC_POP #else -#ifndef __cpp_lib_format +#if !defined __cpp_lib_format && !defined MP_UNITS_COMP_CLANG #error "std::formatting facility not supported" #endif @@ -141,8 +143,8 @@ MP_UNITS_DIAGNOSTIC_POP #define MP_UNITS_STD_FMT std #define MP_UNITS_FMT_LOCALE(loc) loc -#define MP_UNITS_FMT_TO_ARG_ID(arg) arg -#define MP_UNITS_FMT_FROM_ARG_ID(arg) arg +#define MP_UNITS_FMT_TO_ARG_ID(arg) static_cast(arg) +#define MP_UNITS_FMT_FROM_ARG_ID(arg) static_cast(arg) #define MP_UNITS_THROW(arg) throw arg #endif diff --git a/src/core/include/mp-units/bits/fmt.h b/src/core/include/mp-units/bits/fmt.h index 1b56e7ef..626a3bc8 100644 --- a/src/core/include/mp-units/bits/fmt.h +++ b/src/core/include/mp-units/bits/fmt.h @@ -35,15 +35,48 @@ #include #include -// most of the below code is based on/copied from libfmt +// most of the below code is based on/copied from fmtlib namespace mp_units::detail { -struct auto_id {}; +enum class fmt_align { none, left, right, center, numeric }; +enum class fmt_arg_id_kind { + none, +#if MP_UNITS_USE_FMTLIB + name, +#endif + index +}; -enum class fmt_align { none, left, right, center }; -enum class fmt_sign { none, minus, plus, space }; -enum class arg_id_kind { none, index, name }; +template +struct fmt_arg_ref { + fmt_arg_id_kind kind = fmt_arg_id_kind::none; + union value { + int index = 0; +#if MP_UNITS_USE_FMTLIB + std::basic_string_view name; +#endif + + constexpr value() {} + constexpr value(int idx) : index(idx) {} +#if MP_UNITS_USE_FMTLIB + constexpr value(std::basic_string_view n) : name(n) {} +#endif + } val{}; + + fmt_arg_ref() = default; + constexpr explicit fmt_arg_ref(int index) : kind(fmt_arg_id_kind::index), val(index) {} +#if MP_UNITS_USE_FMTLIB + constexpr explicit fmt_arg_ref(std::basic_string_view name) : kind(fmt_arg_id_kind::name), val(name) {} +#endif + + [[nodiscard]] constexpr fmt_arg_ref& operator=(int idx) + { + kind = fmt_arg_id_kind::index; + val.index = idx; + return *this; + } +}; template struct fill_t { @@ -58,7 +91,7 @@ public: { auto size = str.size(); if (size > max_size) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid fill")); - for (size_t i = 0; i < size; ++i) data_[i] = str[i]; + for (size_t i = 0; i < size && i < max_size; ++i) data_[i] = str[i]; size_ = static_cast(size); return *this; } @@ -74,12 +107,6 @@ template inline constexpr bool is_integer = std::is_integral::value && !std::is_same::value && !std::is_same::value && !std::is_same::value; -template -[[nodiscard]] constexpr bool is_ascii_letter(Char c) -{ - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); -} - // Converts a character to ASCII. Returns a number > 127 on conversion failure. template [[nodiscard]] constexpr Char to_ascii(Char value) @@ -89,232 +116,191 @@ template template requires std::is_enum_v -[[nodiscard]] constexpr auto to_ascii(Char value) -> std::underlying_type_t +[[nodiscard]] constexpr std::underlying_type_t to_ascii(Char value) { return value; } +// Casts a nonnegative integer to unsigned. +template +[[nodiscard]] constexpr std::make_unsigned_t to_unsigned(Int value) +{ + gsl_Expects(std::is_unsigned_v || value >= 0); + return static_cast>(value); +} + struct width_checker { template [[nodiscard]] constexpr unsigned long long operator()(T value) const { if constexpr (is_integer) { - if constexpr (std::numeric_limits::is_signed) { + if constexpr (std::numeric_limits::is_signed) if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative width")); - } return static_cast(value); } MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("width is not integer")); - return 0; // should never happen + return 0; } }; -struct precision_checker { - template - [[nodiscard]] constexpr unsigned long long operator()(T value) const - { - if constexpr (is_integer) { - if constexpr (std::numeric_limits::is_signed) { - if (value < 0) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("negative precision")); - } - return static_cast(value); - } - MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("precision is not integer")); - return 0; // should never happen - } -}; - -// Format specifiers for built-in and string types. -template -struct basic_format_specs { - int width = 0; - int precision = -1; - char type = '\0'; - fmt_align align : 4 = fmt_align::none; - fmt_sign sign : 3 = fmt_sign::none; - bool alt : 1 = false; // Alternate form ('#'). - bool localized : 1 = false; - fill_t fill; -}; - -// Format specifiers with width and precision resolved at formatting rather -// than parsing time to allow re-using the same parsed specifiers with -// different sets of arguments (precompilation of format strings). -template -struct dynamic_format_specs : basic_format_specs { - int dynamic_width_index = -1; - int dynamic_precision_index = -1; -}; - -[[nodiscard]] constexpr int verify_dynamic_arg_index_in_range(size_t idx) +template +[[nodiscard]] constexpr int get_dynamic_spec(FormatArg arg) { - if (idx > static_cast(std::numeric_limits::max())) { - MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("Dynamic width or precision index too large.")); - } - return static_cast(idx); -} - -template -[[nodiscard]] constexpr int on_dynamic_arg(size_t arg_id, MP_UNITS_STD_FMT::basic_format_parse_context& context) -{ - context.check_arg_id(MP_UNITS_FMT_TO_ARG_ID(arg_id)); - return verify_dynamic_arg_index_in_range(arg_id); -} - -template -[[nodiscard]] constexpr int on_dynamic_arg(auto_id, MP_UNITS_STD_FMT::basic_format_parse_context& context) -{ - return verify_dynamic_arg_index_in_range(MP_UNITS_FMT_FROM_ARG_ID(context.next_arg_id())); -} - -template -[[nodiscard]] constexpr int get_dynamic_spec(int index, FormatContext& ctx) -{ - const unsigned long long value = - MP_UNITS_STD_FMT::visit_format_arg(Handler{}, ctx.arg(MP_UNITS_FMT_TO_ARG_ID(static_cast(index)))); - if (value > static_cast(std::numeric_limits::max())) { + const unsigned long long value = MP_UNITS_STD_FMT::visit_format_arg(Handler{}, arg); + if (value > ::mp_units::detail::to_unsigned(std::numeric_limits::max())) { MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big")); } return static_cast(value); } +template +[[nodiscard]] constexpr auto get_arg(Context& ctx, ID id) -> decltype(ctx.arg(id)) +{ + auto arg = ctx.arg(MP_UNITS_FMT_TO_ARG_ID(id)); + if (!arg) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("argument not found")); + return arg; +} + +template +constexpr void handle_dynamic_spec(int& value, fmt_arg_ref ref, Context& ctx) +{ + switch (ref.kind) { + case fmt_arg_id_kind::none: + break; + case fmt_arg_id_kind::index: + value = ::mp_units::detail::get_dynamic_spec(get_arg(ctx, ref.val.index)); + break; +#if MP_UNITS_USE_FMTLIB + case fmt_arg_id_kind::name: + value = ::mp_units::detail::get_dynamic_spec(get_arg(ctx, ref.val.name)); + break; +#endif + } +} + // Parses the range [begin, end) as an unsigned integer. This function assumes // that the range is non-empty and the first character is a digit. -template S> -[[nodiscard]] constexpr It parse_nonnegative_int(It begin, S end, size_t& value) +template +[[nodiscard]] constexpr int parse_nonnegative_int(const Char*& begin, const Char* end, int error_value) { gsl_Expects(begin != end && '0' <= *begin && *begin <= '9'); - constexpr auto max_int = static_cast(std::numeric_limits::max()); - constexpr auto big_int = max_int / 10u; - value = 0; - + unsigned value = 0, prev = 0; + auto p = begin; do { - if (value > big_int) { - value = max_int + 1; - break; - } - value = value * 10 + static_cast(*begin - '0'); - ++begin; - } while (begin != end && '0' <= *begin && *begin <= '9'); - - if (value > max_int) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("Number is too big")); - - return begin; + prev = value; + value = value * 10 + unsigned(*p - '0'); + ++p; + } while (p != end && '0' <= *p && *p <= '9'); + auto num_digits = p - begin; + begin = p; + if (num_digits <= std::numeric_limits::digits10) return static_cast(value); + // Check for overflow. + const unsigned max = ::mp_units::detail::to_unsigned((std::numeric_limits::max)()); + return num_digits == std::numeric_limits::digits10 + 1 && prev * 10ull + unsigned(p[-1] - '0') <= max + ? static_cast(value) + : error_value; } -template S> -[[nodiscard]] constexpr It parse_nonnegative_int(It begin, S end, int& value) +template +[[nodiscard]] constexpr bool is_name_start(Char c) { - size_t val_unsigned = 0; - begin = parse_nonnegative_int(begin, end, val_unsigned); - // Never invalid because parse_nonnegative_integer throws an error for values that don't fit in signed integers - value = static_cast(val_unsigned); - return begin; + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; } -template S, typename IDHandler> -[[nodiscard]] constexpr It do_parse_arg_id(It begin, S end, IDHandler&& handler) +template +[[nodiscard]] constexpr const Char* do_parse_arg_id(const Char* begin, const Char* end, Handler&& handler) { - gsl_Expects(begin != end); - auto c = *begin; + Char c = *begin; if (c >= '0' && c <= '9') { - size_t index = 0; + int index = 0; + constexpr int max = (std::numeric_limits::max)(); if (c != '0') - begin = parse_nonnegative_int(begin, end, index); + index = ::mp_units::detail::parse_nonnegative_int(begin, end, max); else ++begin; if (begin == end || (*begin != '}' && *begin != ':')) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string")); else - handler(index); + handler.on_index(index); return begin; } - MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string")); - return begin; // should never happen + if (c == '%') return begin; // mp-units extension + if (!::mp_units::detail::is_name_start(c)) { + MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string")); + return begin; + } + auto it = begin; + do { + ++it; + } while (it != end && (::mp_units::detail::is_name_start(*it) || ('0' <= *it && *it <= '9'))); +#if MP_UNITS_USE_FMTLIB + handler.on_name({begin, ::mp_units::detail::to_unsigned(it - begin)}); +#else + MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("named arguments are not supported in the C++ standard facilities")); +#endif + + return it; } -template S, typename IDHandler> -[[nodiscard]] constexpr It parse_arg_id(It begin, S end, IDHandler&& handler) -{ - auto c = *begin; - if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); - handler(); - return begin; -} - -template S, typename Handler> -[[nodiscard]] constexpr It parse_sign(It begin, S end, Handler&& handler) +template +[[nodiscard]] constexpr const Char* parse_arg_id(const Char* begin, const Char* end, Handler&& handler) { gsl_Expects(begin != end); - switch (to_ascii(*begin)) { - case '+': - handler.on_sign(fmt_sign::plus); - ++begin; - break; - case '-': - handler.on_sign(fmt_sign::minus); - ++begin; - break; - case ' ': - handler.on_sign(fmt_sign::space); - ++begin; - break; - default: - break; - } + Char c = *begin; + if (c != '}' && c != ':') return ::mp_units::detail::do_parse_arg_id(begin, end, handler); + handler.on_auto(); return begin; } -template S, typename Handler> -[[nodiscard]] constexpr It parse_width(It begin, S end, Handler&& handler) -{ - struct width_adapter { - Handler& handler; - constexpr void operator()() { handler.on_dynamic_width(auto_id{}); } - constexpr void operator()(size_t id) { handler.on_dynamic_width(id); } - }; +template +struct dynamic_spec_id_handler { + MP_UNITS_STD_FMT::basic_format_parse_context& ctx; + fmt_arg_ref& ref; + constexpr void on_auto() + { + int id = MP_UNITS_FMT_FROM_ARG_ID(ctx.next_arg_id()); + ref = fmt_arg_ref(id); +#if MP_UNITS_USE_FMTLIB || __cpp_lib_format >= 202305L + ctx.check_dynamic_spec(id); +#endif + } + constexpr void on_index(int id) + { + ref = fmt_arg_ref(id); + ctx.check_arg_id(MP_UNITS_FMT_TO_ARG_ID(id)); +#if MP_UNITS_USE_FMTLIB || __cpp_lib_format >= 202305L + ctx.check_dynamic_spec(id); +#endif + } +#if MP_UNITS_USE_FMTLIB + constexpr void on_name([[maybe_unused]] std::basic_string_view id) + { + ref = fmt_arg_ref(id); + ctx.check_arg_id(id); + } +#endif +}; + +template +[[nodiscard]] constexpr const Char* parse_dynamic_spec(const Char* begin, const Char* end, int& value, + fmt_arg_ref& ref, + MP_UNITS_STD_FMT::basic_format_parse_context& ctx) +{ gsl_Expects(begin != end); if ('0' <= *begin && *begin <= '9') { - int width = 0; - begin = parse_nonnegative_int(begin, end, width); - if (width != -1) - handler.on_width(width); + int val = ::mp_units::detail::parse_nonnegative_int(begin, end, -1); + if (val != -1) + value = val; else MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big")); } else if (*begin == '{') { ++begin; - if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler}); - if (begin == end || *begin != '}') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string")); - ++begin; - } - return begin; -} - -template S, typename Handler> -[[nodiscard]] constexpr It parse_precision(It begin, S end, Handler&& handler) -{ - struct precision_adapter { - Handler& handler; - constexpr void operator()() { handler.on_dynamic_precision(auto_id{}); } - constexpr void operator()(size_t id) { handler.on_dynamic_precision(id); } - }; - - ++begin; - auto c = begin != end ? *begin : std::iter_value_t(); - if ('0' <= c && c <= '9') { - auto precision = 0; - begin = parse_nonnegative_int(begin, end, precision); - if (precision != -1) - handler.on_precision(precision); - else - MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("number is too big")); - } else if (c == '{') { - ++begin; - if (begin != end) begin = parse_arg_id(begin, end, precision_adapter{handler}); - if (begin == end || *begin++ != '}') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string")); - } else { - MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("missing precision specifier")); + if (*begin == '%') return begin - 1; // mp-units extension + auto handler = dynamic_spec_id_handler{ctx, ref}; + if (begin != end) begin = ::mp_units::detail::parse_arg_id(begin, end, handler); + if (begin != end && *begin == '}') return ++begin; + MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid format string")); } return begin; } @@ -334,13 +320,14 @@ constexpr int code_point_length(It begin) } // Parses fill and alignment. -template S, typename Handler> -[[nodiscard]] constexpr It parse_align(It begin, S end, Handler&& handler) +template +[[nodiscard]] constexpr const Char* parse_align(const Char* begin, const Char* end, Specs& specs, + fmt_align default_align = fmt_align::none) { gsl_Expects(begin != end); auto align = fmt_align::none; auto p = begin + code_point_length(begin); - if (p >= end) p = begin; + if (end - p <= 0) p = begin; for (;;) { switch (to_ascii(*p)) { case '<': @@ -352,120 +339,29 @@ template S, typename Handler> case '^': align = fmt_align::center; break; - default: - break; } if (align != fmt_align::none) { if (p != begin) { auto c = *begin; - if (c == '{') MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid fill character '{'")); - handler.on_fill(std::basic_string_view>(&*begin, static_cast(p - begin))); + if (c == '}') return begin; + if (c == '{') { + MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid fill character '{'")); + return begin; + } + specs.fill = {begin, to_unsigned(p - begin)}; begin = p + 1; - } else + } else { ++begin; - handler.on_align(align); + } break; } else if (p == begin) { break; } p = begin; } + if (align == fmt_align::none) align = default_align; // mp-units extension + specs.align = align; return begin; } -// Parses standard format specifiers and sends notifications about parsed -// components to handler. -template S, typename SpecHandler> -[[nodiscard]] constexpr It parse_format_specs(It begin, S end, SpecHandler&& handler) -{ - if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) && *begin != 'L') { - handler.on_type(*begin++); - return begin; - } - - if (begin == end) return begin; - - begin = ::mp_units::detail::parse_align(begin, end, handler); - if (begin == end) return begin; - - // Parse sign. - begin = ::mp_units::detail::parse_sign(begin, end, handler); - if (begin == end) return begin; - - if (*begin == '#') { - handler.on_hash(); - if (++begin == end) return begin; - } - - // Parse zero flag. - if (*begin == '0') { - handler.on_zero(); - if (++begin == end) return begin; - } - - begin = ::mp_units::detail::parse_width(begin, end, handler); - if (begin == end) return begin; - - // Parse precision. - if (*begin == '.') { - begin = ::mp_units::detail::parse_precision(begin, end, handler); - if (begin == end) return begin; - } - - if (*begin == 'L') { - handler.on_localized(); - ++begin; - } - - // Parse type. - if (begin != end && *begin != '}') handler.on_type(*begin++); - return begin; -} - -// A format specifier handler that sets fields in basic_format_specs. -template -class specs_setter { -protected: - basic_format_specs& specs_; -public: - constexpr explicit specs_setter(basic_format_specs& specs) : specs_(specs) {} - constexpr void on_align(fmt_align align) { specs_.align = align; } - constexpr void on_fill(std::basic_string_view fill) { specs_.fill = fill; } - constexpr void on_sign(fmt_sign s) { specs_.sign = s; } - constexpr void on_hash() { specs_.alt = true; } - constexpr void on_localized() { specs_.localized = true; } - constexpr void on_zero() { specs_.fill[0] = Char('0'); } - constexpr void on_width(int width) { specs_.width = width; } - constexpr void on_precision(int precision) { specs_.precision = precision; } - constexpr void on_type(Char type) { specs_.type = static_cast(type); } -}; - -// Format spec handler that saves references to arguments representing dynamic -// width and precision to be resolved at formatting time. -template -class dynamic_specs_handler : public specs_setter { -public: - using char_type = MP_UNITS_TYPENAME ParseContext::char_type; - - constexpr dynamic_specs_handler(dynamic_format_specs& specs, ParseContext& ctx) : - specs_setter(specs), specs_(specs), context_(ctx) - { - } - - template - constexpr void on_dynamic_width(T t) - { - specs_.dynamic_width_index = on_dynamic_arg(t, context_); - } - - template - constexpr void on_dynamic_precision(T t) - { - specs_.dynamic_precision_index = on_dynamic_arg(t, context_); - } -private: - dynamic_format_specs& specs_; - ParseContext& context_; -}; - } // namespace mp_units::detail diff --git a/src/core/include/mp-units/format.h b/src/core/include/mp-units/format.h index eafba9b2..58655021 100644 --- a/src/core/include/mp-units/format.h +++ b/src/core/include/mp-units/format.h @@ -29,185 +29,68 @@ #include #include -// Grammar -// -// quantity-format-spec ::= [fill-and-align] [width] [quantity-specs] -// quantity-specs ::= conversion-spec -// quantity-specs conversion-spec -// quantity-specs literal-char -// literal-char ::= any character other than '{' or '}' -// conversion-spec ::= '%' type -// type ::= [rep-modifier] 'Q' -// [unit-modifier] 'q' -// rep-modifier ::= [sign] [#] [precision] [L] [rep-type] -// rep-type ::= one of -// a A b B d e E f F g G o x X -// unit-modifier ::= [text-encoding] [unit-symbol-solidus] [unit-symbol-separator] -// [text-encoding] [unit-symbol-separator] [unit-symbol-solidus] -// [unit-symbol-solidus] [text-encoding] [unit-symbol-separator] -// [unit-symbol-solidus] [unit-symbol-separator] [text-encoding] -// [unit-symbol-separator] [text-encoding] [unit-symbol-solidus] -// [unit-symbol-separator] [unit-symbol-solidus] [text-encoding] -// text-encoding ::= one of -// U A -// unit-symbol-solidus ::= one of -// o a n -// unit-symbol-separator ::= one of -// s d - - -// TODO Should the below be allowed? Is it even possible to implement with `format()` being const? -// std::cout << std::format("{:%Q %q %Q %q}\n", 123s); namespace mp_units::detail { -// Holds specs about the whole object -template -struct quantity_global_format_specs { - fill_t fill; - fmt_align align = fmt_align::none; +template +struct fill_align_width_format_specs { + fill_t fill; + fmt_align align : 4 = fmt_align::none; int width = 0; - int dynamic_width_index = -1; + fmt_arg_ref width_ref; }; -// Holds specs about the representation (%[specs]Q) -struct quantity_rep_format_specs { - fmt_sign sign = fmt_sign::none; - int precision = -1; - int dynamic_precision_index = -1; - char type = '\0'; - bool alt = false; - bool localized = false; -}; - -// Holds specs about the unit (%[specs]q) -struct quantity_unit_format_specs : unit_symbol_formatting {}; - -template -struct quantity_format_specs { - quantity_global_format_specs global; - quantity_rep_format_specs rep; - quantity_unit_format_specs unit; -}; - -// Parse a `units-rep-modifier` -template S, typename Handler> -constexpr It parse_units_rep(It begin, S end, Handler&& handler, bool treat_as_floating_point) +template +[[nodiscard]] constexpr const Char* at_most_one_of(const Char* begin, const Char* end, std::string_view modifiers) { - // parse sign - begin = parse_sign(begin, end, handler); - if (begin == end) return begin; - - // parse # - if (*begin == '#') { - handler.on_hash(); - if (++begin == end) return begin; - } - - // parse precision if a floating point - if (*begin == '.') { - if (treat_as_floating_point) { - begin = parse_precision(begin, end, handler); - } else - throw MP_UNITS_STD_FMT::format_error("precision not allowed for integral quantity representation"); - if (begin == end) return begin; - } - - // parse L to enable the locale-specific form - if (*begin == 'L') { - handler.on_localized(); - ++begin; - } - - if (begin != end && *begin != '}' && *begin != '%') { - handler.on_type(*begin++); - } - return begin; + auto it = find_first_of(begin, end, modifiers.begin(), modifiers.end()); + if (it != end && find_first_of(it + 1, end, modifiers.begin(), modifiers.end()) != end) + throw MP_UNITS_STD_FMT::format_error("only one of '" + std::string(modifiers) + + "' unit modifiers may be used in the format spec"); + return it; } -// parse units-specs -template S, typename Handler> -constexpr It parse_units_format(It begin, S end, Handler&& handler) +template +[[nodiscard]] constexpr const Char* parse_fill_align_width(MP_UNITS_STD_FMT::basic_format_parse_context& ctx, + const Char* begin, const Char* end, Specs& specs, + fmt_align default_align = fmt_align::none) { - auto ptr = begin; - while (ptr != end) { - auto c = *ptr; - if (c == '}') break; - if (c != '%') { - ++ptr; - continue; - } - if (begin != ptr) handler.on_text(begin, ptr); - begin = ++ptr; // consume '%' - if (ptr == end) throw MP_UNITS_STD_FMT::format_error("invalid format"); - c = *ptr++; + auto it = begin; + if (it == end || *it == '}') return it; - constexpr auto units_types = std::string_view{"Qq"}; - const auto new_end = find_first_of(begin, end, units_types.begin(), units_types.end()); - if (new_end == end) throw MP_UNITS_STD_FMT::format_error("invalid format"); - if (*new_end == 'Q') { - handler.on_quantity_value(begin, new_end); // Edit `on_quantity_value` to add rep modifiers - } else { - handler.on_quantity_unit(begin, new_end); // Edit `on_quantity_unit` to add an unit modifier - } - ptr = new_end + 1; - begin = ptr; - } - if (begin != ptr) handler.on_text(begin, ptr); - return ptr; + it = mp_units::detail::parse_align(it, end, specs, default_align); + if (it == end) return it; + + return mp_units::detail::parse_dynamic_spec(it, end, specs.width, specs.width_ref, ctx); } -// build the 'representation' as requested in the format string, applying only units-rep-modifiers -template -[[nodiscard]] OutputIt format_units_quantity_value(OutputIt out, const Rep& val, - const quantity_rep_format_specs& rep_specs, const Locale& loc) +template +[[nodiscard]] constexpr const Char* parse_subentity_replacement_field(const Char* begin, const Char* end, + Handler&& handler) { - std::basic_string buffer; - auto to_buffer = std::back_inserter(buffer); - - MP_UNITS_STD_FMT::format_to(to_buffer, "{{:"); - switch (rep_specs.sign) { - case fmt_sign::none: - break; - case fmt_sign::plus: - MP_UNITS_STD_FMT::format_to(to_buffer, "+"); - break; - case fmt_sign::minus: - MP_UNITS_STD_FMT::format_to(to_buffer, "-"); - break; - case fmt_sign::space: - MP_UNITS_STD_FMT::format_to(to_buffer, " "); - break; + if (end - begin++ < 4) + return MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` too short")), end; + if (*begin++ != '%') + MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` should start with '%'")); + if (*begin == '}') + MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` should have an identifier")); + auto it = begin; + for (; it != end; ++it) { + if (*it == '{' || *it == '%') + MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("invalid `subentity-replacement-field` format")); + if (*it == '}' || *it == ':') break; } - - if (rep_specs.alt) { - MP_UNITS_STD_FMT::format_to(to_buffer, "#"); - } - auto type = rep_specs.type; - if (auto precision = rep_specs.precision; precision >= 0) { - MP_UNITS_STD_FMT::format_to(to_buffer, ".{}{}", precision, type == '\0' ? 'f' : type); - } else if constexpr (treat_as_floating_point) { - MP_UNITS_STD_FMT::format_to(to_buffer, "{}", type == '\0' ? 'g' : type); - } else { - if (type != '\0') { - MP_UNITS_STD_FMT::format_to(to_buffer, "{}", type); - } - } - if (rep_specs.localized) { - MP_UNITS_STD_FMT::format_to(to_buffer, "L"); - } - - MP_UNITS_STD_FMT::format_to(to_buffer, "}}"); - if (rep_specs.localized) { - return MP_UNITS_STD_FMT::vformat_to(out, MP_UNITS_FMT_LOCALE(loc), buffer, MP_UNITS_STD_FMT::make_format_args(val)); - } - return MP_UNITS_STD_FMT::vformat_to(out, buffer, MP_UNITS_STD_FMT::make_format_args(val)); + if (it == end) MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` too short")); + std::string_view id{begin, it}; + if (*it == ':') ++it; + it = handler.on_replacement_field(id, it); + if (it == end || *it != '}') + MP_UNITS_THROW(MP_UNITS_STD_FMT::format_error("`subentity-replacement-field` should end with '}'")); + return ++it; } -// Creates a global format string -// e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "{:*^10}" -template -OutputIt format_global_buffer(OutputIt out, const quantity_global_format_specs& specs) +template +OutputIt format_global_buffer(OutputIt out, const fill_align_width_format_specs& specs) { MP_UNITS_STD_FMT::format_to(out, "{{:"); if (specs.fill.size() != 1 || specs.fill[0] != ' ') { @@ -230,239 +113,346 @@ OutputIt format_global_buffer(OutputIt out, const quantity_global_format_specs -struct quantity_formatter { - OutputIt out; - Rep val; - const quantity_format_specs& specs; - Locale loc; - - explicit quantity_formatter(OutputIt o, const quantity& q, const quantity_format_specs& fspecs, - Locale lc) : - out(o), val(q.numerical_value_ref_in(q.unit)), specs(fspecs), loc(std::move(lc)) - { - } - - template S> - void on_text(It begin, S end) - { - std::copy(begin, end, out); - } - - template S> - void on_quantity_value([[maybe_unused]] It, [[maybe_unused]] S) - { - out = format_units_quantity_value(out, val, specs.rep, loc); - } - - template S> - void on_quantity_unit(It, S) - { - out = unit_symbol_to(out, get_unit(Reference), specs.unit); - } -}; - -template S> -[[nodiscard]] constexpr It at_most_one_of(It begin, S end, std::string_view modifiers) -{ - auto it = find_first_of(begin, end, modifiers.begin(), modifiers.end()); - if (it != end && find_first_of(it + 1, end, modifiers.begin(), modifiers.end()) != end) - throw MP_UNITS_STD_FMT::format_error("only one of '" + std::string(modifiers) + - "' unit modifiers may be used in the format spec"); - return it; -} - } // namespace mp_units::detail -template -struct MP_UNITS_STD_FMT::formatter, CharT> { -private: - using quantity = mp_units::quantity; - using iterator = MP_UNITS_TYPENAME MP_UNITS_STD_FMT::basic_format_parse_context::iterator; +// +// Grammar +// +// dimension-format-spec ::= [fill-and-align] [width] [dimension-spec] +// dimension-spec ::= [text-encoding] +// text-encoding ::= 'U' | 'A' +// - bool quantity_value = false; - bool quantity_unit = false; - mp_units::detail::quantity_format_specs specs; - std::basic_string_view format_str; +// template +// struct dimension_format_specs : fill_align_width_format_specs, dimension_symbol_formatting {}; - struct spec_handler { - formatter& f; - MP_UNITS_STD_FMT::basic_format_parse_context& context; - constexpr void on_fill(std::basic_string_view fill) { f.specs.global.fill = fill; } - constexpr void on_align(mp_units::detail::fmt_align align) { f.specs.global.align = align; } - constexpr void on_width(int width) { f.specs.global.width = width; } - constexpr void on_sign(mp_units::detail::fmt_sign sign) { f.specs.rep.sign = sign; } - constexpr void on_hash() { f.specs.rep.alt = true; } - constexpr void on_precision(int precision) { f.specs.rep.precision = precision; } - constexpr void on_localized() { f.specs.rep.localized = true; } +// +// Grammar +// +// unit-format-spec ::= [fill-and-align] [width] [unit-spec] +// unit-spec ::= [text-encoding] [unit-symbol-solidus] [unit-symbol-separator] [L] +// [text-encoding] [unit-symbol-separator] [unit-symbol-solidus] [L] +// [unit-symbol-solidus] [text-encoding] [unit-symbol-separator] [L] +// [unit-symbol-solidus] [unit-symbol-separator] [text-encoding] [L] +// [unit-symbol-separator] [text-encoding] [unit-symbol-solidus] [L] +// [unit-symbol-separator] [unit-symbol-solidus] [text-encoding] [L] +// unit-symbol-solidus ::= '1' | 'a' | 'n'1 +// unit-symbol-separator ::= 's' | 'd' +// +template +class MP_UNITS_STD_FMT::formatter { + struct format_specs : mp_units::detail::fill_align_width_format_specs, mp_units::unit_symbol_formatting {}; - constexpr void on_type(char type) + std::basic_string_view fill_align_width_format_str_; + std::basic_string_view modifiers_format_str_; + format_specs specs_{}; + + struct format_checker { + using enum mp_units::text_encoding; + + mp_units::text_encoding encoding = unicode; + + constexpr void on_text_encoding(Char val) { encoding = (val == 'U') ? unicode : ascii; } + constexpr void on_unit_symbol_solidus(Char) const {} + constexpr void on_unit_symbol_separator(Char val) const { - constexpr auto valid_rep_types = std::string_view{"aAbBdeEfFgGoxX"}; - if (valid_rep_types.find(type) != std::string_view::npos) { - f.specs.rep.type = type; - } else { - throw MP_UNITS_STD_FMT::format_error("invalid quantity type specifier"); - } - } - - template - constexpr void on_dynamic_width(T t) - { - f.specs.global.dynamic_width_index = mp_units::detail::on_dynamic_arg(t, context); - } - - template - constexpr void on_dynamic_precision(T t) - { - f.specs.rep.dynamic_precision_index = mp_units::detail::on_dynamic_arg(t, context); - } - - template S> - constexpr void on_text(It, S) - { - } - - template S> - constexpr void on_quantity_value(It begin, S end) - { - if (begin != end) mp_units::detail::parse_units_rep(begin, end, *this, mp_units::treat_as_floating_point); - f.quantity_value = true; - } - - template S> - constexpr void on_quantity_unit(It begin, S end) - { - if (begin == end) return; - - constexpr auto valid_modifiers = std::string_view{"UAoansd"}; - for (auto it = begin; it != end; ++it) { - if (valid_modifiers.find(*it) == std::string_view::npos) - throw MP_UNITS_STD_FMT::format_error("invalid unit modifier specified"); - } - - if (auto it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) { - if (*it == 'U') - f.specs.unit.encoding = mp_units::text_encoding::unicode; - else - f.specs.unit.encoding = mp_units::text_encoding::ascii; - } - - if (auto it = mp_units::detail::at_most_one_of(begin, end, "oan"); it != end) { - if (*it == 'o') - f.specs.unit.solidus = mp_units::unit_symbol_solidus::one_denominator; - else if (*it == 'a') - f.specs.unit.solidus = mp_units::unit_symbol_solidus::always; - else - f.specs.unit.solidus = mp_units::unit_symbol_solidus::never; - } - - if (auto it = mp_units::detail::at_most_one_of(begin, end, "sd"); it != end) { - if (*it == 's') - f.specs.unit.separator = mp_units::unit_symbol_separator::space; - else { - if (f.specs.unit.encoding == mp_units::text_encoding::ascii) - throw MP_UNITS_STD_FMT::format_error("half_high_dot unit separator allowed only for Unicode encoding"); - f.specs.unit.separator = mp_units::unit_symbol_separator::half_high_dot; - } - } - - f.quantity_unit = true; + if (val == 'd' && encoding == ascii) + throw MP_UNITS_STD_FMT::format_error("half_high_dot unit separator allowed only for Unicode encoding"); } }; - [[nodiscard]] constexpr std::pair do_parse( - MP_UNITS_STD_FMT::basic_format_parse_context& ctx) - { - auto begin = ctx.begin(); - auto end = ctx.end(); + struct unit_formatter { + format_specs specs; - if (begin == end || *begin == '}') return {begin, begin}; + using enum mp_units::text_encoding; + using enum mp_units::unit_symbol_solidus; + using enum mp_units::unit_symbol_separator; - // handler to assign parsed data to formatter data members - spec_handler handler{*this, ctx}; - - // parse alignment - begin = mp_units::detail::parse_align(begin, end, handler); - if (begin == end) return {begin, begin}; - - // parse width - begin = mp_units::detail::parse_width(begin, end, handler); - if (begin == end) return {begin, begin}; - - // parse units-specific specification - end = mp_units::detail::parse_units_format(begin, end, handler); - - if (specs.global.align == mp_units::detail::fmt_align::none && (!quantity_unit || quantity_value)) - // quantity values should behave like numbers (by default aligned to right) - specs.global.align = mp_units::detail::fmt_align::right; - - return {begin, end}; - } - - template - OutputIt format_quantity_content(OutputIt out, const quantity& q, FormatContext& ctx) - { - auto begin = format_str.begin(); - auto end = format_str.end(); - - if (begin == end || *begin == '}') { - // default format should print value followed by the unit separated with 1 space - out = mp_units::detail::format_units_quantity_value(out, q.numerical_value_ref_in(q.unit), specs.rep, - ctx.locale()); - if constexpr (mp_units::detail::has_unit_symbol(get_unit(Reference))) { - if constexpr (mp_units::space_before_unit_symbol) *out++ = CharT(' '); - out = unit_symbol_to(out, get_unit(Reference)); + constexpr void on_text_encoding(Char val) { specs.encoding = (val == 'U') ? unicode : ascii; } + constexpr void on_unit_symbol_solidus(Char val) + { + switch (val) { + case '1': + specs.solidus = one_denominator; + break; + case 'a': + specs.solidus = always; + break; + case 'n': + specs.solidus = never; + break; } - } else { - // user provided format - mp_units::detail::quantity_formatter f(out, q, specs, ctx.locale()); - mp_units::detail::parse_units_format(begin, end, f); } - return out; + constexpr void on_unit_symbol_separator(Char val) { specs.separator = (val == 's') ? space : half_high_dot; } + }; + + template + constexpr const Char* parse_unit_specs(const Char* begin, const Char* end, Handler&& handler) const + { + auto it = begin; + if (it == end || *it == '}') return begin; + + constexpr auto valid_modifiers = std::string_view{"UA1ansd"}; + for (; it != end && *it != '}'; ++it) { + if (valid_modifiers.find(*it) == std::string_view::npos) + throw MP_UNITS_STD_FMT::format_error("invalid unit modifier specified"); + } + end = it; + + if (it = mp_units::detail::at_most_one_of(begin, end, "UA"); it != end) handler.on_text_encoding(*it); + if (it = mp_units::detail::at_most_one_of(begin, end, "1an"); it != end) handler.on_unit_symbol_solidus(*it); + if (it = mp_units::detail::at_most_one_of(begin, end, "sd"); it != end) handler.on_unit_symbol_separator(*it); + return end; } public: - [[nodiscard]] constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context& ctx) + constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context& ctx) -> decltype(ctx.begin()) { - auto range = do_parse(ctx); - if (range.first != range.second) - format_str = std::basic_string_view(&*range.first, static_cast(range.second - range.first)); - return range.second; + const auto begin = ctx.begin(); + auto end = ctx.end(); + + auto it = parse_fill_align_width(ctx, begin, end, specs_); + fill_align_width_format_str_ = {begin, it}; + if (it == end) return it; + + format_checker checker; + end = parse_unit_specs(it, end, checker); + modifiers_format_str_ = {it, end}; + return end; } template - auto format(const quantity& q, FormatContext& ctx) + auto format(const U& u, FormatContext& ctx) const -> decltype(ctx.out()) { - // process dynamic width and precision - if (specs.global.dynamic_width_index >= 0) - specs.global.width = - mp_units::detail::get_dynamic_spec(specs.global.dynamic_width_index, ctx); - if (specs.rep.dynamic_precision_index >= 0) - specs.rep.precision = - mp_units::detail::get_dynamic_spec(specs.rep.dynamic_precision_index, ctx); + unit_formatter f{specs_}; + mp_units::detail::handle_dynamic_spec(f.specs.width, f.specs.width_ref, ctx); - if (specs.global.width == 0) { + parse_unit_specs(modifiers_format_str_.begin(), modifiers_format_str_.end(), f); + + if (f.specs.width == 0) { // Avoid extra copying if width is not specified - return format_quantity_content(ctx.out(), q, ctx); + return mp_units::unit_symbol_to(ctx.out(), u, f.specs); } else { - // In `quantity_buffer` we will have the representation and the unit formatted according to their - // specification, ignoring global specifiers - // e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "1.2_m" - std::basic_string quantity_buffer; + std::basic_string unit_buffer; + mp_units::unit_symbol_to(std::back_inserter(unit_buffer), u, f.specs); - // deal with quantity content - format_quantity_content(std::back_inserter(quantity_buffer), q, ctx); - - // In `global_format_buffer` we will create a global format string - // e.g. "{:*^10%.1Q_%q}, 1.23_q_m" => "{:*^10}" - std::basic_string global_format_buffer; - mp_units::detail::format_global_buffer(std::back_inserter(global_format_buffer), specs.global); - - // Format the `quantity buffer` using specs from `global_format_buffer` - // In the example, equivalent to MP_UNITS_STD_FMT::format("{:*^10}", "1.2_m") + std::basic_string global_format_buffer = "{:" + std::basic_string{fill_align_width_format_str_} + "}"; return MP_UNITS_STD_FMT::vformat_to(ctx.out(), global_format_buffer, + MP_UNITS_STD_FMT::make_format_args(unit_buffer)); + } + } +}; + + +// +// Grammar +// +// quantity-format-spec ::= [fill-and-align] [width] [quantity-specs] +// quantity-specs ::= conversion-spec +// quantity-specs conversion-spec +// quantity-specs literal-char +// literal-char ::= +// conversion-spec ::= placement-spec +// subentity-replacement-field +// placement-spec ::= '%' placement-type +// placement-type ::= 'N' | 'U' | 'D' | '?' | '%' +// subentity-replacement-field ::= '{' '%' subentity-id [format-specifier] '}' +// subentity-id ::= literal-char +// subentity-id literal-char +// format-specifier ::= ':' format-spec +// format-spec ::= +// +template +class MP_UNITS_STD_FMT::formatter, Char> { + static constexpr auto unit = get_unit(Reference); + static constexpr auto dimension = get_quantity_spec(Reference).dimension; + + using quantity_t = mp_units::quantity; + using unit_t = std::remove_const_t; + using dimension_t = std::remove_const_t; + + using format_specs = mp_units::detail::fill_align_width_format_specs; + + std::basic_string_view modifiers_format_str_; + std::vector format_str_lengths_; + format_specs specs_{}; + + struct format_checker { + MP_UNITS_STD_FMT::basic_format_parse_context& ctx; + std::vector& format_str_lengths; + + constexpr void on_number(std::basic_string_view) const {} + constexpr void on_maybe_space() const {} + constexpr void on_unit(std::basic_string_view) const {} + constexpr void on_dimension(std::basic_string_view) const {} + constexpr void on_text(const Char*, const Char*) const {} + + constexpr const Char* on_replacement_field(std::basic_string_view id, const Char* begin) + { + if (id == "N") + return on_replacement_field(begin); + else if (id == "U") + return on_replacement_field(begin); + else if (id == "D") { + return begin; + // on_replacement_field(begin); + } else + throw MP_UNITS_STD_FMT::format_error("unknown replacement field '" + std::string(id) + "'"); + } + + private: + template + constexpr const Char* on_replacement_field(const Char* begin) const + { + MP_UNITS_STD_FMT::formatter sf; + ctx.advance_to(begin); + auto ptr = sf.parse(ctx); + if (*ptr != '}') throw MP_UNITS_STD_FMT::format_error("unmatched '}' in format string"); + format_str_lengths.push_back(mp_units::detail::to_unsigned(ptr - begin)); + return ptr; + } + }; + + template + struct quantity_formatter { + OutputIt out; + const quantity_t& q; + std::vector::const_iterator format_str_lengths_it; + std::locale locale; + + void on_number(std::basic_string_view format_str) + { + out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str, + MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit))); + } + void on_maybe_space() + { + if constexpr (mp_units::space_before_unit_symbol) *out++ = ' '; + } + void on_unit(std::basic_string_view format_str) + { + out = MP_UNITS_STD_FMT::vformat_to(out, locale, format_str, MP_UNITS_STD_FMT::make_format_args(q.unit)); + } + void on_dimension(std::basic_string_view) {} + void on_text(const Char* begin, const Char* end) const { std::copy(begin, end, out); } + + constexpr const Char* on_replacement_field(std::basic_string_view id, const Char* begin) + { + auto format_str = [&] { return "{:" + std::string(begin, *format_str_lengths_it + 1); }; + if (id == "N") + on_number(format_str()); + else if (id == "U") + on_unit(format_str()); + else if (id == "D") + on_dimension(format_str()); + else + throw MP_UNITS_STD_FMT::format_error("unknown replacement field '" + std::string(id) + "'"); + return begin + *format_str_lengths_it++; + } + }; + template + quantity_formatter(OutputIt, Args...) -> quantity_formatter; + + template + constexpr const Char* parse_quantity_specs(const Char* begin, const Char* end, Handler&& handler) const + { + if (begin == end || *begin == '}') return begin; + if (*begin != '%' && *begin != '{') + throw MP_UNITS_STD_FMT::format_error( + "`quantity-specs` should start with a `conversion-spec` ('%' or '{' characters expected)})"); + auto ptr = begin; + while (ptr != end) { + auto c = *ptr; + if (c == '}') break; + if (c == '{') { + if (begin != ptr) handler.on_text(begin, ptr); + begin = ptr = mp_units::detail::parse_subentity_replacement_field(ptr, end, handler); + continue; + } + if (c != '%') { + ++ptr; + continue; + } + if (begin != ptr) handler.on_text(begin, ptr); + ++ptr; // consume '%' + if (ptr == end) throw MP_UNITS_STD_FMT::format_error("invalid `placement-spec` format"); + + c = *ptr++; + switch (c) { + case 'N': + handler.on_number("{}"); + break; + case 'U': + handler.on_unit("{}"); + break; + case 'D': + handler.on_dimension("{}"); + break; + case '?': + handler.on_maybe_space(); + break; + case '%': + handler.on_text(ptr - 1, ptr); + break; + default: + throw MP_UNITS_STD_FMT::format_error(std::string("unknown `placement-spec` token '") + c + "'"); + } + begin = ptr; + } + if (begin != ptr) handler.on_text(begin, ptr); + return ptr; + } + + template + OutputIt format_quantity(OutputIt out, const quantity_t& q, FormatContext& ctx) const + { + std::locale locale = MP_UNITS_FMT_LOCALE(ctx.locale()); + if (modifiers_format_str_.empty()) { + // default format should print value followed by the unit separated with 1 space + out = MP_UNITS_STD_FMT::vformat_to(out, locale, "{}", + MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit))); + if constexpr (mp_units::space_before_unit_symbol) *out++ = ' '; + return MP_UNITS_STD_FMT::vformat_to(out, locale, "{}", MP_UNITS_STD_FMT::make_format_args(q.unit)); + } else { + // user provided format + quantity_formatter f{out, q, format_str_lengths_.cbegin(), locale}; + parse_quantity_specs(modifiers_format_str_.begin(), modifiers_format_str_.end(), f); + return f.out; + } + } + +public: + constexpr auto parse(MP_UNITS_STD_FMT::basic_format_parse_context& ctx) -> decltype(ctx.begin()) + { + const auto begin = ctx.begin(); + auto end = ctx.end(); + + auto it = parse_fill_align_width(ctx, begin, end, specs_, mp_units::detail::fmt_align::right); + if (it == end) return it; + + format_checker checker{ctx, format_str_lengths_}; + end = parse_quantity_specs(it, end, checker); + modifiers_format_str_ = {it, end}; + return end; + } + + template + auto format(const quantity_t& q, FormatContext& ctx) const -> decltype(ctx.out()) + { + auto specs = specs_; + mp_units::detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); + + if (specs.width == 0) { + // Avoid extra copying if width is not specified + return format_quantity(ctx.out(), q, ctx); + } else { + std::basic_string quantity_buffer; + format_quantity(std::back_inserter(quantity_buffer), q, ctx); + + std::basic_string fill_align_width_format_str; + mp_units::detail::format_global_buffer(std::back_inserter(fill_align_width_format_str), specs); + return MP_UNITS_STD_FMT::vformat_to(ctx.out(), fill_align_width_format_str, MP_UNITS_STD_FMT::make_format_args(quantity_buffer)); } } diff --git a/src/core/include/mp-units/ostream.h b/src/core/include/mp-units/ostream.h index c648a810..3f462fb1 100644 --- a/src/core/include/mp-units/ostream.h +++ b/src/core/include/mp-units/ostream.h @@ -31,6 +31,12 @@ namespace mp_units { namespace detail { +template +void to_stream(std::basic_ostream& os, U u) +{ + unit_symbol_to(std::ostream_iterator(os), u); +} + template void to_stream(std::basic_ostream& os, const quantity& q) { @@ -39,14 +45,29 @@ void to_stream(std::basic_ostream& os, const quantity& q) os << +q.numerical_value_ref_in(q.unit); else os << q.numerical_value_ref_in(q.unit); - if constexpr (has_unit_symbol(get_unit(R))) { - if constexpr (space_before_unit_symbol) os << " "; - unit_symbol_to(std::ostream_iterator(os), get_unit(R)); - } + if constexpr (space_before_unit_symbol) os << " "; + to_stream(os, get_unit(R)); } } // namespace detail +template +std::basic_ostream& operator<<(std::basic_ostream& os, U u) +{ + if (os.width()) { + // std::setw() applies to the whole uni output so it has to be first put into std::string + std::basic_ostringstream oss; + oss.flags(os.flags()); + oss.imbue(os.getloc()); + oss.precision(os.precision()); + detail::to_stream(oss, u); + return os << std::move(oss).str(); + } + + detail::to_stream(os, u); + return os; +} + template std::basic_ostream& operator<<(std::basic_ostream& os, const quantity& q) requires requires { os << q.numerical_value_ref_in(q.unit); } diff --git a/src/core/include/mp-units/unit.h b/src/core/include/mp-units/unit.h index 7c210cd2..e586ec87 100644 --- a/src/core/include/mp-units/unit.h +++ b/src/core/include/mp-units/unit.h @@ -653,6 +653,23 @@ template } +/** + * @brief Puts a space ' ' sign before a unit symbol + * + * Quantities of some units (e.g. degree, arcminute, arcsecond) should not be printed with the + * space between a number and a unit. For those a partial specialization with the value `false` should + * be provided. + */ +template +inline constexpr bool space_before_unit_symbol = true; + +template<> +inline constexpr bool space_before_unit_symbol = false; +template<> +inline constexpr bool space_before_unit_symbol = false; +template<> +inline constexpr bool space_before_unit_symbol = false; + // get_unit_symbol enum class text_encoding : std::int8_t { @@ -713,12 +730,6 @@ constexpr Out print_separator(Out out, unit_symbol_formatting fmt) return out; } -template -[[nodiscard]] consteval bool has_unit_symbol(U) -{ - return requires { U::symbol; } || !std::derived_from>; -} - template Out, Unit U> requires requires { U::symbol; } constexpr Out unit_symbol_impl(Out out, U, unit_symbol_formatting fmt, bool negative_power) @@ -741,12 +752,8 @@ constexpr Out unit_symbol_impl(Out out, const scaled_unit& u, unit_symbol_ constexpr auto mag_txt = magnitude_text(); out = copy(mag_txt, fmt.encoding, out); - if constexpr (has_unit_symbol(scaled_unit::reference_unit)) { - *out++ = ' '; - return unit_symbol_impl(out, u.reference_unit, fmt, negative_power); - } else { - return out; - } + if constexpr (space_before_unit_symbol::reference_unit>) *out++ = ' '; + return unit_symbol_impl(out, u.reference_unit, fmt, negative_power); } } @@ -828,21 +835,6 @@ constexpr Out unit_symbol_impl(Out out, const derived_unit&, unit_symbo } // namespace detail -/** - * @brief Puts a space ' ' sign before a unit symbol - * - * Quantities of some units (e.g. degree, arcminute, arcsecond) should not be printed with the - * space between a number and a unit. For those a partial specialization with the value `false` should - * be provided. - */ -template -inline constexpr bool space_before_unit_symbol = true; - -template<> -inline constexpr bool space_before_unit_symbol = false; -template<> -inline constexpr bool space_before_unit_symbol = false; - template Out, Unit U> constexpr Out unit_symbol_to(Out out, U u, unit_symbol_formatting fmt = unit_symbol_formatting{}) { diff --git a/test/runtime/fmt_test.cpp b/test/runtime/fmt_test.cpp index b1d131ec..260b1a83 100644 --- a/test/runtime/fmt_test.cpp +++ b/test/runtime/fmt_test.cpp @@ -33,6 +33,7 @@ import mp_units; #include #include #include +#include #include #include #include @@ -60,7 +61,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } + SECTION("fmt with format {:%N%?%U} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); + } } SECTION("floating-point representation") @@ -72,7 +76,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } + SECTION("fmt with format {:%N%?%U} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); + } } } @@ -85,7 +92,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } + SECTION("fmt with format {:%N%?%U} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); } } SECTION("quantity with a derived unit") @@ -101,9 +108,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") + SECTION("fmt with format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); } } @@ -116,9 +123,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") + SECTION("fmt with format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); } } @@ -131,9 +138,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") + SECTION("fmt with format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); } } } @@ -149,9 +156,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") + SECTION("fmt with format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); } } @@ -164,9 +171,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") + SECTION("fmt with format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); } } @@ -179,9 +186,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") + SECTION("fmt with format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); } } @@ -194,9 +201,9 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") + SECTION("fmt with format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); } } } @@ -213,7 +220,7 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "2 "); } + SECTION("fmt with format {:%N%?%U} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == "2"); } } SECTION("one with ratio.exp != 0") @@ -225,19 +232,27 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "2 km/m"); } + SECTION("fmt with format {:%N%?%U} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == "2 km/m"); + } } SECTION("percents") { - const auto q = value_cast(15. * isq::length[m] / (100. * isq::length[m])); + constexpr auto q = value_cast(15. * isq::length[m] / (100. * isq::length[m])); os << q; + static_assert(!space_before_unit_symbol); + SECTION("iostream") { CHECK(os.str() == "15%"); } SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "15 %"); } + SECTION("fmt with format {:%N%?%U} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); + } } SECTION("radians") @@ -249,7 +264,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } + SECTION("fmt with format {:%N%?%U} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); + } } } @@ -264,7 +282,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "42 °"); } + SECTION("fmt with format {:%N%?%U} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); + } } SECTION("arcminute") @@ -276,7 +297,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "42 ′"); } + SECTION("fmt with format {:%N%?%U} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); + } } SECTION("arcsecond") @@ -288,7 +312,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == "42 ″"); } + SECTION("fmt with format {:%N%?%U} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); + } } } @@ -303,7 +330,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } + SECTION("fmt with format {:%N%?%U} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); + } } SECTION("signed negative") @@ -315,7 +345,10 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } + SECTION("fmt with format {:%N%?%U} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); + } } SECTION("unsigned") @@ -327,20 +360,23 @@ TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]") SECTION("fmt with default format {} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{}", q) == os.str()); } - SECTION("fmt with format {:%Q %q} on a quantity") { CHECK(MP_UNITS_STD_FMT::format("{:%Q %q}", q) == os.str()); } + SECTION("fmt with format {:%N%?%U} on a quantity") + { + CHECK(MP_UNITS_STD_FMT::format("{:%N%?%U}", q) == os.str()); + } } } } -TEST_CASE("format string with only %Q should print quantity value only", "[text][fmt]") +TEST_CASE("quantity format string with only %N should print quantity value only", "[text][fmt]") { SECTION("integral representation") { - SECTION("positive value") { CHECK(MP_UNITS_STD_FMT::format("{:%Q}", 123 * isq::speed[km / h]) == "123"); } + SECTION("positive value") { CHECK(MP_UNITS_STD_FMT::format("{:%N}", 123 * isq::speed[km / h]) == "123"); } SECTION("negative value") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q}", 5 * isq::length[m] - 10 * isq::length[m]) == "-5"); + CHECK(MP_UNITS_STD_FMT::format("{:%N}", 5 * isq::length[m] - 10 * isq::length[m]) == "-5"); } } @@ -348,185 +384,186 @@ TEST_CASE("format string with only %Q should print quantity value only", "[text] { SECTION("positive value") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q}", 221. * isq::length[km] / (2 * isq::time[h])) == "110.5"); + CHECK(MP_UNITS_STD_FMT::format("{:%N}", 221. * isq::length[km] / (2 * isq::time[h])) == "110.5"); } SECTION("negative value") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q}", 3.14 * isq::length[m] - 10 * isq::length[m]) == "-6.86"); + CHECK(MP_UNITS_STD_FMT::format("{:%N}", 3.14 * isq::length[m] - 10 * isq::length[m]) == "-6.859999999999999"); } SECTION("nan") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q}", std::numeric_limits::quiet_NaN() * isq::length[m]) == "nan"); + CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits::quiet_NaN() * isq::length[m]) == "nan"); } SECTION("inf") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q}", std::numeric_limits::infinity() * isq::length[m]) == "inf"); + CHECK(MP_UNITS_STD_FMT::format("{:%N}", std::numeric_limits::infinity() * isq::length[m]) == "inf"); } SECTION("-inf") { - CHECK(MP_UNITS_STD_FMT::format("{:%Q}", -std::numeric_limits::infinity() * isq::length[m]) == "-inf"); + CHECK(MP_UNITS_STD_FMT::format("{:%N}", -std::numeric_limits::infinity() * isq::length[m]) == "-inf"); } } } -TEST_CASE("format string with only %q should print quantity unit symbol only", "[text][fmt]") +TEST_CASE("quantity format string with only %U should print quantity unit symbol only", "[text][fmt]") { - SECTION("Text encoding") + CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::speed[km / h]) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::resistance[si::kilo]) == "kΩ"); + CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::time[us]) == "µs"); + CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * isq::acceleration[m / s2]) == "m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{:%U}", 123 * percent) == "%"); +} + +TEST_CASE("Unit formatting should use proper text encoding") +{ + SECTION("Unicode text output") { - SECTION("Unicode text output") - { - CHECK(MP_UNITS_STD_FMT::format("{:%Uq}", 123 * isq::speed[km / h]) == "km/h"); - // TODO enable this when resistance is defined - // CHECK(MP_UNITS_STD_FMT::format("{:%Uq}", 123 * isq::resistance[kilo]) == "kΩ"); - CHECK(MP_UNITS_STD_FMT::format("{:%Uq}", 123 * isq::time[us]) == "µs"); - CHECK(MP_UNITS_STD_FMT::format("{:%Uq}", 123 * isq::acceleration[m / s2]) == "m/s²"); - } - - SECTION("Unicode text output is used by default") - { - CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::speed[km / h]) == "km/h"); - // CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::resistance[kilo]) == "kΩ"); - CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::time[us]) == "µs"); - CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::acceleration[m / s2]) == "m/s²"); - } - - SECTION("ASCII text output") - { - CHECK(MP_UNITS_STD_FMT::format("{:%Aq}", 123 * isq::speed[km / h]) == "km/h"); - // CHECK(MP_UNITS_STD_FMT::format("{:%Aq}", 123 * isq::resistance[kilo]) == "kohm"); - CHECK(MP_UNITS_STD_FMT::format("{:%Aq}", 123 * isq::time[us]) == "us"); - CHECK(MP_UNITS_STD_FMT::format("{:%Aq}", 123 * isq::acceleration[m / s2]) == "m/s^2"); - } + CHECK(MP_UNITS_STD_FMT::format("{:U}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{:U}", si::kilo) == "kΩ"); + CHECK(MP_UNITS_STD_FMT::format("{:U}", us) == "µs"); + CHECK(MP_UNITS_STD_FMT::format("{:U}", m / s2) == "m/s²"); } - SECTION("Solidus") + SECTION("Unicode text output is used by default") { - SECTION("Solidus for only one element in denominator") - { - CHECK(MP_UNITS_STD_FMT::format("{:%oq}", 123 * isq::speed[km / h]) == "km/h"); - CHECK(MP_UNITS_STD_FMT::format("{:%oq}", 123 * isq::acceleration[m / s2]) == "m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{:%oq}", 123 * isq::pressure[kg / m / s2]) == "kg m⁻¹ s⁻²"); - } - - SECTION("Solidus for only one element in denominator is used by default") - { - CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::speed[km / h]) == "km/h"); - CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::acceleration[m / s2]) == "m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::pressure[kg / m / s2]) == "kg m⁻¹ s⁻²"); - } - - SECTION("Always use solidus") - { - CHECK(MP_UNITS_STD_FMT::format("{:%aq}", 123 * isq::speed[km / h]) == "km/h"); - CHECK(MP_UNITS_STD_FMT::format("{:%aq}", 123 * isq::acceleration[m / s2]) == "m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{:%aq}", 123 * isq::pressure[kg / m / s2]) == "kg/(m s²)"); - } - - SECTION("Never use solidus") - { - CHECK(MP_UNITS_STD_FMT::format("{:%nq}", 123 * isq::speed[km / h]) == "km h⁻¹"); - CHECK(MP_UNITS_STD_FMT::format("{:%nq}", 123 * isq::acceleration[m / s2]) == "m s⁻²"); - CHECK(MP_UNITS_STD_FMT::format("{:%nq}", 123 * isq::pressure[kg / m / s2]) == "kg m⁻¹ s⁻²"); - } + CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{}", si::kilo) == "kΩ"); + CHECK(MP_UNITS_STD_FMT::format("{}", us) == "µs"); + CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²"); } - SECTION("Separator") + SECTION("ASCII text output") { - SECTION("Space") - { - CHECK(MP_UNITS_STD_FMT::format("{:%sq}", 123 * isq::force[kg * m / s2]) == "kg m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{:%sq}", 123 * isq::pressure[kg / m / s2]) == "kg m⁻¹ s⁻²"); - CHECK(MP_UNITS_STD_FMT::format("{:%asq}", 123 * isq::pressure[kg / m / s2]) == "kg/(m s²)"); - } + CHECK(MP_UNITS_STD_FMT::format("{:A}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{:A}", si::kilo) == "kohm"); + CHECK(MP_UNITS_STD_FMT::format("{:A}", us) == "us"); + CHECK(MP_UNITS_STD_FMT::format("{:A}", m / s2) == "m/s^2"); + } +} - SECTION("Space is used by default") - { - CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::force[kg * m / s2]) == "kg m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{:%q}", 123 * isq::pressure[kg / m / s2]) == "kg m⁻¹ s⁻²"); - CHECK(MP_UNITS_STD_FMT::format("{:%aq}", 123 * isq::pressure[kg / m / s2]) == "kg/(m s²)"); - } +TEST_CASE("Unit formatting should print solidus according to specs") +{ + SECTION("Solidus for only one element in denominator") + { + CHECK(MP_UNITS_STD_FMT::format("{:1}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{:1}", m / s2) == "m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{:1}", kg / m / s2) == "kg m⁻¹ s⁻²"); + } - SECTION("Dot") - { - CHECK(MP_UNITS_STD_FMT::format("{:%dq}", 123 * isq::force[kg * m / s2]) == "kg⋅m/s²"); - CHECK(MP_UNITS_STD_FMT::format("{:%dq}", 123 * isq::pressure[kg / m / s2]) == "kg⋅m⁻¹⋅s⁻²"); - CHECK(MP_UNITS_STD_FMT::format("{:%adq}", 123 * isq::pressure[kg / m / s2]) == "kg/(m⋅s²)"); - } + SECTION("Solidus for only one element in denominator is used by default") + { + CHECK(MP_UNITS_STD_FMT::format("{}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{}", m / s2) == "m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²"); + } + + SECTION("Always use solidus") + { + CHECK(MP_UNITS_STD_FMT::format("{:a}", km / h) == "km/h"); + CHECK(MP_UNITS_STD_FMT::format("{:a}", m / s2) == "m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)"); + } + + SECTION("Never use solidus") + { + CHECK(MP_UNITS_STD_FMT::format("{:n}", km / h) == "km h⁻¹"); + CHECK(MP_UNITS_STD_FMT::format("{:n}", m / s2) == "m s⁻²"); + CHECK(MP_UNITS_STD_FMT::format("{:n}", kg / m / s2) == "kg m⁻¹ s⁻²"); + } +} + +TEST_CASE("Unit formatting should user proper separator") +{ + SECTION("Space") + { + CHECK(MP_UNITS_STD_FMT::format("{:s}", kg * m / s2) == "kg m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{:s}", kg / m / s2) == "kg m⁻¹ s⁻²"); + CHECK(MP_UNITS_STD_FMT::format("{:sa}", kg / m / s2) == "kg/(m s²)"); + } + + SECTION("Space is used by default") + { + CHECK(MP_UNITS_STD_FMT::format("{}", kg * m / s2) == "kg m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{}", kg / m / s2) == "kg m⁻¹ s⁻²"); + CHECK(MP_UNITS_STD_FMT::format("{:a}", kg / m / s2) == "kg/(m s²)"); + } + + SECTION("Dot") + { + CHECK(MP_UNITS_STD_FMT::format("{:d}", kg * m / s2) == "kg⋅m/s²"); + CHECK(MP_UNITS_STD_FMT::format("{:d}", kg / m / s2) == "kg⋅m⁻¹⋅s⁻²"); + CHECK(MP_UNITS_STD_FMT::format("{:ad}", kg / m / s2) == "kg/(m⋅s²)"); } } TEST_CASE("unknown unit modifiers should throw", "[text][fmt][exception]") { - auto q = 1 * isq::length[m]; - SECTION("only the invalid modifier") { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%xq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:x}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); } SECTION("invalid modifier in the front") { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%xUdaq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:xUda}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); } SECTION("invalid modifier in the end") { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%Udaxq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udax}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); } SECTION("invalid modifier in the middle") { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%Udxaq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:Udxa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("invalid unit modifier specified")); } } TEST_CASE("repeated unit modifiers should throw", "[text][fmt][exception]") { - auto q = 1 * isq::length[m]; - SECTION("text encoding") { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%UdaUq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:UdaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dUaUq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dUUaq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); } SECTION("solidus") { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%aUdaq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:aUda}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%daUaq}", MP_UNITS_STD_FMT::make_format_args(q)), + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:daUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%daaUq}", MP_UNITS_STD_FMT::make_format_args(q)), + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:daaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); } SECTION("separator") { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dUadq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUad}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dadUq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dadU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%addUq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:addU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); } @@ -534,43 +571,41 @@ TEST_CASE("repeated unit modifiers should throw", "[text][fmt][exception]") TEST_CASE("more then one modifier of the same kind should throw", "[text][fmt][exception]") { - auto q = 1 * isq::length[m]; - SECTION("text encoding") { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%UdaAq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:UdaA}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dAaUq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dAaU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dAUaq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dAUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'UA' unit modifiers may be used in the format spec")); } SECTION("solidus") { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%aUdnq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:aUdn}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dnUaq}", MP_UNITS_STD_FMT::make_format_args(q)), + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dnUa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%daoUq}", MP_UNITS_STD_FMT::make_format_args(q)), + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:da1U}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("only one of 'oan' unit modifiers may be used in the format spec")); + Catch::Matchers::Message("only one of '1an' unit modifiers may be used in the format spec")); } SECTION("separator") { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dUasq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dUas}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%sadUq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:sadU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%adsUq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:adsU}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("only one of 'sd' unit modifiers may be used in the format spec")); } @@ -578,23 +613,21 @@ TEST_CASE("more then one modifier of the same kind should throw", "[text][fmt][e TEST_CASE("half_high_dot separator requested for ASCII encoding should throw", "[text][fmt][exception]") { - auto q = 1 * isq::length[m]; - - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%dAaq}", MP_UNITS_STD_FMT::make_format_args(q)), + REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:dAa}", MP_UNITS_STD_FMT::make_format_args(m)), MP_UNITS_STD_FMT::format_error, Catch::Matchers::Message("half_high_dot unit separator allowed only for Unicode encoding")); } -TEST_CASE("%q and %Q can be put anywhere in a format string", "[text][fmt]") +TEST_CASE("%U and %N can be put anywhere in a format string", "[text][fmt]") { - SECTION("no space") { CHECK(MP_UNITS_STD_FMT::format("{:%Q%q}", 123 * isq::speed[km / h]) == "123km/h"); } + SECTION("no space") { CHECK(MP_UNITS_STD_FMT::format("{:%N%U}", 123 * isq::speed[km / h]) == "123km/h"); } - SECTION("separator") { CHECK(MP_UNITS_STD_FMT::format("{:%Q###%q}", 123 * isq::speed[km / h]) == "123###km/h"); } + SECTION("separator") { CHECK(MP_UNITS_STD_FMT::format("{:%N###%U}", 123 * isq::speed[km / h]) == "123###km/h"); } - SECTION("opposite order") { CHECK(MP_UNITS_STD_FMT::format("{:%q %Q}", 123 * isq::speed[km / h]) == "km/h 123"); } + SECTION("opposite order") { CHECK(MP_UNITS_STD_FMT::format("{:%U %N}", 123 * isq::speed[km / h]) == "km/h 123"); } } -TEST_CASE("fill and align specification", "[text][fmt][ostream]") +TEST_CASE("quantity fill and align specification", "[text][fmt][ostream]") { SECTION("ostream") { @@ -649,40 +682,40 @@ TEST_CASE("fill and align specification", "[text][fmt][ostream]") CHECK(MP_UNITS_STD_FMT::format("|{:*^10}|", 123 * isq::length[m]) == "|**123 m***|"); } - SECTION("full format {:%Q %q} on a quantity") + SECTION("full format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("|{:0%Q%q}|", 123 * isq::length[m]) == "|123m|"); - CHECK(MP_UNITS_STD_FMT::format("|{:10%Q%q}|", 123 * isq::length[m]) == "| 123m|"); - CHECK(MP_UNITS_STD_FMT::format("|{:<10%Q%q}|", 123 * isq::length[m]) == "|123m |"); - CHECK(MP_UNITS_STD_FMT::format("|{:>10%Q%q}|", 123 * isq::length[m]) == "| 123m|"); - CHECK(MP_UNITS_STD_FMT::format("|{:^10%Q%q}|", 123 * isq::length[m]) == "| 123m |"); - CHECK(MP_UNITS_STD_FMT::format("|{:*<10%Q%q}|", 123 * isq::length[m]) == "|123m******|"); - CHECK(MP_UNITS_STD_FMT::format("|{:*>10%Q%q}|", 123 * isq::length[m]) == "|******123m|"); - CHECK(MP_UNITS_STD_FMT::format("|{:*^10%Q%q}|", 123 * isq::length[m]) == "|***123m***|"); + CHECK(MP_UNITS_STD_FMT::format("|{:0%N%?%U}|", 123 * isq::length[m]) == "|123 m|"); + CHECK(MP_UNITS_STD_FMT::format("|{:10%N%?%U}|", 123 * isq::length[m]) == "| 123 m|"); + CHECK(MP_UNITS_STD_FMT::format("|{:<10%N%?%U}|", 123 * isq::length[m]) == "|123 m |"); + CHECK(MP_UNITS_STD_FMT::format("|{:>10%N%?%U}|", 123 * isq::length[m]) == "| 123 m|"); + CHECK(MP_UNITS_STD_FMT::format("|{:^10%N%?%U}|", 123 * isq::length[m]) == "| 123 m |"); + CHECK(MP_UNITS_STD_FMT::format("|{:*<10%N%?%U}|", 123 * isq::length[m]) == "|123 m*****|"); + CHECK(MP_UNITS_STD_FMT::format("|{:*>10%N%?%U}|", 123 * isq::length[m]) == "|*****123 m|"); + CHECK(MP_UNITS_STD_FMT::format("|{:*^10%N%?%U}|", 123 * isq::length[m]) == "|**123 m***|"); } - SECTION("value only format {:%Q} on a quantity") + SECTION("value only format {:%N} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("|{:0%Q}|", 123 * isq::length[m]) == "|123|"); - CHECK(MP_UNITS_STD_FMT::format("|{:10%Q}|", 123 * isq::length[m]) == "| 123|"); - CHECK(MP_UNITS_STD_FMT::format("|{:<10%Q}|", 123 * isq::length[m]) == "|123 |"); - CHECK(MP_UNITS_STD_FMT::format("|{:>10%Q}|", 123 * isq::length[m]) == "| 123|"); - CHECK(MP_UNITS_STD_FMT::format("|{:^10%Q}|", 123 * isq::length[m]) == "| 123 |"); - CHECK(MP_UNITS_STD_FMT::format("|{:*<10%Q}|", 123 * isq::length[m]) == "|123*******|"); - CHECK(MP_UNITS_STD_FMT::format("|{:*>10%Q}|", 123 * isq::length[m]) == "|*******123|"); - CHECK(MP_UNITS_STD_FMT::format("|{:*^10%Q}|", 123 * isq::length[m]) == "|***123****|"); + CHECK(MP_UNITS_STD_FMT::format("|{:0%N}|", 123 * isq::length[m]) == "|123|"); + CHECK(MP_UNITS_STD_FMT::format("|{:10%N}|", 123 * isq::length[m]) == "| 123|"); + CHECK(MP_UNITS_STD_FMT::format("|{:<10%N}|", 123 * isq::length[m]) == "|123 |"); + CHECK(MP_UNITS_STD_FMT::format("|{:>10%N}|", 123 * isq::length[m]) == "| 123|"); + CHECK(MP_UNITS_STD_FMT::format("|{:^10%N}|", 123 * isq::length[m]) == "| 123 |"); + CHECK(MP_UNITS_STD_FMT::format("|{:*<10%N}|", 123 * isq::length[m]) == "|123*******|"); + CHECK(MP_UNITS_STD_FMT::format("|{:*>10%N}|", 123 * isq::length[m]) == "|*******123|"); + CHECK(MP_UNITS_STD_FMT::format("|{:*^10%N}|", 123 * isq::length[m]) == "|***123****|"); } - SECTION("symbol only format {:%q} on a quantity") + SECTION("symbol only format {:%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("|{:0%q}|", 123 * isq::length[m]) == "|m|"); - CHECK(MP_UNITS_STD_FMT::format("|{:10%q}|", 123 * isq::length[m]) == "| m|"); - CHECK(MP_UNITS_STD_FMT::format("|{:<10%q}|", 123 * isq::length[m]) == "|m |"); - CHECK(MP_UNITS_STD_FMT::format("|{:>10%q}|", 123 * isq::length[m]) == "| m|"); - CHECK(MP_UNITS_STD_FMT::format("|{:^10%q}|", 123 * isq::length[m]) == "| m |"); - CHECK(MP_UNITS_STD_FMT::format("|{:*<10%q}|", 123 * isq::length[m]) == "|m*********|"); - CHECK(MP_UNITS_STD_FMT::format("|{:*>10%q}|", 123 * isq::length[m]) == "|*********m|"); - CHECK(MP_UNITS_STD_FMT::format("|{:*^10%q}|", 123 * isq::length[m]) == "|****m*****|"); + CHECK(MP_UNITS_STD_FMT::format("|{:0%U}|", 123 * isq::length[m]) == "|m|"); + CHECK(MP_UNITS_STD_FMT::format("|{:10%U}|", 123 * isq::length[m]) == "| m|"); + CHECK(MP_UNITS_STD_FMT::format("|{:<10%U}|", 123 * isq::length[m]) == "|m |"); + CHECK(MP_UNITS_STD_FMT::format("|{:>10%U}|", 123 * isq::length[m]) == "| m|"); + CHECK(MP_UNITS_STD_FMT::format("|{:^10%U}|", 123 * isq::length[m]) == "| m |"); + CHECK(MP_UNITS_STD_FMT::format("|{:*<10%U}|", 123 * isq::length[m]) == "|m*********|"); + CHECK(MP_UNITS_STD_FMT::format("|{:*>10%U}|", 123 * isq::length[m]) == "|*********m|"); + CHECK(MP_UNITS_STD_FMT::format("|{:*^10%U}|", 123 * isq::length[m]) == "|****m*****|"); } } @@ -691,142 +724,139 @@ TEST_CASE("sign specification", "[text][fmt]") auto inf = std::numeric_limits::infinity() * si::metre; auto nan = std::numeric_limits::quiet_NaN() * si::metre; - SECTION("full format {:%Q %q} on a quantity") + SECTION("full format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{0:%Q%q},{0:%+Q%q},{0:%-Q%q},{0:% Q%q}", 1 * isq::length[m]) == "1m,+1m,1m, 1m"); - CHECK(MP_UNITS_STD_FMT::format("{0:%Q%q},{0:%+Q%q},{0:%-Q%q},{0:% Q%q}", -1 * isq::length[m]) == "-1m,-1m,-1m,-1m"); - CHECK(MP_UNITS_STD_FMT::format("{0:%Q%q},{0:%+Q%q},{0:%-Q%q},{0:% Q%q}", inf) == "infm,+infm,infm, infm"); - CHECK(MP_UNITS_STD_FMT::format("{0:%Q%q},{0:%+Q%q},{0:%-Q%q},{0:% Q%q}", nan) == "nanm,+nanm,nanm, nanm"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", 1 * isq::length[m]) == + "1m,+1m,1m, 1m"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", -1 * isq::length[m]) == + "-1m,-1m,-1m,-1m"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", inf) == "infm,+infm,infm, infm"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N%U},{0:{%N:+}%U},{0:{%N:-}%U},{0:{%N: }%U}", nan) == "nanm,+nanm,nanm, nanm"); } - SECTION("value only format {:%Q} on a quantity") + SECTION("value only format {:%N} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{0:%Q},{0:%+Q},{0:%-Q},{0:% Q}", 1 * isq::length[m]) == "1,+1,1, 1"); - CHECK(MP_UNITS_STD_FMT::format("{0:%Q},{0:%+Q},{0:%-Q},{0:% Q}", -1 * isq::length[m]) == "-1,-1,-1,-1"); - CHECK(MP_UNITS_STD_FMT::format("{0:%Q},{0:%+Q},{0:%-Q},{0:% Q}", inf) == "inf,+inf,inf, inf"); - CHECK(MP_UNITS_STD_FMT::format("{0:%Q},{0:%+Q},{0:%-Q},{0:% Q}", nan) == "nan,+nan,nan, nan"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", 1 * isq::length[m]) == "1,+1,1, 1"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", -1 * isq::length[m]) == "-1,-1,-1,-1"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", inf) == "inf,+inf,inf, inf"); + CHECK(MP_UNITS_STD_FMT::format("{0:%N},{0:{%N:+}},{0:{%N:-}},{0:{%N: }}", nan) == "nan,+nan,nan, nan"); } } TEST_CASE("precision specification", "[text][fmt]") { - SECTION("full format {:%Q %q} on a quantity") + SECTION("full format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%.0Q %q}", 1.2345 * isq::length[m]) == "1 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.1Q %q}", 1.2345 * isq::length[m]) == "1.2 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.2Q %q}", 1.2345 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3Q %q}", 1.2345 * isq::length[m]) == "1.234 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.4Q %q}", 1.2345 * isq::length[m]) == "1.2345 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.5Q %q}", 1.2345 * isq::length[m]) == "1.23450 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.10Q %q}", 1.2345 * isq::length[m]) == "1.2345000000 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.0f}%?%U}", 1.2345 * isq::length[m]) == "1 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.1f}%?%U}", 1.2345 * isq::length[m]) == "1.2 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.2f}%?%U}", 1.2345 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3f}%?%U}", 1.2345 * isq::length[m]) == "1.234 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.4f}%?%U}", 1.2345 * isq::length[m]) == "1.2345 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.5f}%?%U}", 1.2345 * isq::length[m]) == "1.23450 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.10f}%?%U}", 1.2345 * isq::length[m]) == "1.2345000000 m"); } - SECTION("value only format {:%Q} on a quantity") + SECTION("value only format {:%N} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%.0Q}", 1.2345 * isq::length[m]) == "1"); - CHECK(MP_UNITS_STD_FMT::format("{:%.1Q}", 1.2345 * isq::length[m]) == "1.2"); - CHECK(MP_UNITS_STD_FMT::format("{:%.2Q}", 1.2345 * isq::length[m]) == "1.23"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3Q}", 1.2345 * isq::length[m]) == "1.234"); - CHECK(MP_UNITS_STD_FMT::format("{:%.4Q}", 1.2345 * isq::length[m]) == "1.2345"); - CHECK(MP_UNITS_STD_FMT::format("{:%.5Q}", 1.2345 * isq::length[m]) == "1.23450"); - CHECK(MP_UNITS_STD_FMT::format("{:%.10Q}", 1.2345 * isq::length[m]) == "1.2345000000"); - } -} - -TEST_CASE("precision specification for integral representation should throw", "[text][fmt][exception]") -{ - auto q = 1 * isq::length[m]; - - SECTION("full format {:%Q %q} on a quantity") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%.1Q %q}", MP_UNITS_STD_FMT::make_format_args(q)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("precision not allowed for integral quantity representation")); - } - - SECTION("value only format {:%Q} on a quantity") - { - REQUIRE_THROWS_MATCHES(MP_UNITS_STD_FMT::vformat("{:%.1Q}", MP_UNITS_STD_FMT::make_format_args(q)), - MP_UNITS_STD_FMT::format_error, - Catch::Matchers::Message("precision not allowed for integral quantity representation")); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.0f}}", 1.2345 * isq::length[m]) == "1"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.1f}}", 1.2345 * isq::length[m]) == "1.2"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.2f}}", 1.2345 * isq::length[m]) == "1.23"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3f}}", 1.2345 * isq::length[m]) == "1.234"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.4f}}", 1.2345 * isq::length[m]) == "1.2345"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.5f}}", 1.2345 * isq::length[m]) == "1.23450"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.10f}}", 1.2345 * isq::length[m]) == "1.2345000000"); } } TEST_CASE("type specification", "[text][fmt]") { - SECTION("full format {:%Q %q} on a quantity") + SECTION("full format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%bQ %q}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%BQ %q}", 42 * isq::length[m]) == "101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%dQ %q}", 42 * isq::length[m]) == "42 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%oQ %q}", 42 * isq::length[m]) == "52 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%xQ %q}", 42 * isq::length[m]) == "2a m"); - CHECK(MP_UNITS_STD_FMT::format("{:%XQ %q}", 42 * isq::length[m]) == "2A m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:b}%?%U}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:B}%?%U}", 42 * isq::length[m]) == "101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:d}%?%U}", 42 * isq::length[m]) == "42 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:o}%?%U}", 42 * isq::length[m]) == "52 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:x}%?%U}", 42 * isq::length[m]) == "2a m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:X}%?%U}", 42 * isq::length[m]) == "2A m"); - CHECK(MP_UNITS_STD_FMT::format("{:%aQ %q}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3aQ %q}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%AQ %q}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3AQ %q}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%eQ %q}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3eQ %q}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%EQ %q}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3EQ %q}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%gQ %q}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%gQ %q}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3gQ %q}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3gQ %q}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%GQ %q}", 1.2345678 * isq::length[m]) == "1.23457 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%GQ %q}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3GQ %q}", 1.2345678 * isq::length[m]) == "1.23 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3GQ %q}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); +#if MP_UNITS_USE_FMTLIB + CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}%?%U}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}%?%U}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}%?%U}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}%?%U}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0 m"); +#else + CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}%?%U}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}%?%U}", 1.2345678 * isq::length[m]) == "1.3c1p+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}%?%U}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}%?%U}", 1.2345678 * isq::length[m]) == "1.3C1P+0 m"); +#endif + CHECK(MP_UNITS_STD_FMT::format("{:{%N:e}%?%U}", 1.2345678 * isq::length[m]) == "1.234568e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3e}%?%U}", 1.2345678 * isq::length[m]) == "1.235e+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:E}%?%U}", 1.2345678 * isq::length[m]) == "1.234568E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3E}%?%U}", 1.2345678 * isq::length[m]) == "1.235E+00 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}%?%U}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23457e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}%?%U}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23e+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}%?%U}", 1.2345678 * isq::length[m]) == "1.23457 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23457E+08 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}%?%U}", 1.2345678 * isq::length[m]) == "1.23 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}%?%U}", 1.2345678e8 * isq::length[m]) == "1.23E+08 m"); } - SECTION("value only format {:%Q} on a quantity") + SECTION("value only format {:%N} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%bQ}", 42 * isq::length[m]) == "101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%BQ}", 42 * isq::length[m]) == "101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%dQ}", 42 * isq::length[m]) == "42"); - CHECK(MP_UNITS_STD_FMT::format("{:%oQ}", 42 * isq::length[m]) == "52"); - CHECK(MP_UNITS_STD_FMT::format("{:%xQ}", 42 * isq::length[m]) == "2a"); - CHECK(MP_UNITS_STD_FMT::format("{:%XQ}", 42 * isq::length[m]) == "2A"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:b}}", 42 * isq::length[m]) == "101010"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:B}}", 42 * isq::length[m]) == "101010"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:d}}", 42 * isq::length[m]) == "42"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:o}}", 42 * isq::length[m]) == "52"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:x}}", 42 * isq::length[m]) == "2a"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:X}}", 42 * isq::length[m]) == "2A"); - CHECK(MP_UNITS_STD_FMT::format("{:%aQ}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3aQ}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%AQ}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3AQ}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0"); - CHECK(MP_UNITS_STD_FMT::format("{:%eQ}", 1.2345678 * isq::length[m]) == "1.234568e+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3eQ}", 1.2345678 * isq::length[m]) == "1.235e+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%EQ}", 1.2345678 * isq::length[m]) == "1.234568E+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3EQ}", 1.2345678 * isq::length[m]) == "1.235E+00"); - CHECK(MP_UNITS_STD_FMT::format("{:%gQ}", 1.2345678 * isq::length[m]) == "1.23457"); - CHECK(MP_UNITS_STD_FMT::format("{:%gQ}", 1.2345678e8 * isq::length[m]) == "1.23457e+08"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3gQ}", 1.2345678 * isq::length[m]) == "1.23"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3gQ}", 1.2345678e8 * isq::length[m]) == "1.23e+08"); - CHECK(MP_UNITS_STD_FMT::format("{:%GQ}", 1.2345678 * isq::length[m]) == "1.23457"); - CHECK(MP_UNITS_STD_FMT::format("{:%GQ}", 1.2345678e8 * isq::length[m]) == "1.23457E+08"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3GQ}", 1.2345678 * isq::length[m]) == "1.23"); - CHECK(MP_UNITS_STD_FMT::format("{:%.3GQ}", 1.2345678e8 * isq::length[m]) == "1.23E+08"); +#if MP_UNITS_USE_FMTLIB + CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}}", 1.2345678 * isq::length[m]) == "0x1.3c0ca2a5b1d5dp+0"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}}", 1.2345678 * isq::length[m]) == "0x1.3c1p+0"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}}", 1.2345678 * isq::length[m]) == "0X1.3C0CA2A5B1D5DP+0"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}}", 1.2345678 * isq::length[m]) == "0X1.3C1P+0"); +#else + CHECK(MP_UNITS_STD_FMT::format("{:{%N:a}}", 1.2345678 * isq::length[m]) == "1.3c0ca2a5b1d5dp+0"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3a}}", 1.2345678 * isq::length[m]) == "1.3c1p+0"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:A}}", 1.2345678 * isq::length[m]) == "1.3C0CA2A5B1D5DP+0"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3A}}", 1.2345678 * isq::length[m]) == "1.3C1P+0"); +#endif + CHECK(MP_UNITS_STD_FMT::format("{:{%N:e}}", 1.2345678 * isq::length[m]) == "1.234568e+00"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3e}}", 1.2345678 * isq::length[m]) == "1.235e+00"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:E}}", 1.2345678 * isq::length[m]) == "1.234568E+00"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3E}}", 1.2345678 * isq::length[m]) == "1.235E+00"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}}", 1.2345678 * isq::length[m]) == "1.23457"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:g}}", 1.2345678e8 * isq::length[m]) == "1.23457e+08"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}}", 1.2345678 * isq::length[m]) == "1.23"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3g}}", 1.2345678e8 * isq::length[m]) == "1.23e+08"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}}", 1.2345678 * isq::length[m]) == "1.23457"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:G}}", 1.2345678e8 * isq::length[m]) == "1.23457E+08"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}}", 1.2345678 * isq::length[m]) == "1.23"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:.3G}}", 1.2345678e8 * isq::length[m]) == "1.23E+08"); } } TEST_CASE("different base types with the # specifier", "[text][fmt]") { - SECTION("full format {:%Q %q} on a quantity") + SECTION("full format {:%N%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%#bQ %q}", 42 * isq::length[m]) == "0b101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%#BQ %q}", 42 * isq::length[m]) == "0B101010 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%#oQ %q}", 42 * isq::length[m]) == "052 m"); - CHECK(MP_UNITS_STD_FMT::format("{:%#xQ %q}", 42 * isq::length[m]) == "0x2a m"); - CHECK(MP_UNITS_STD_FMT::format("{:%#XQ %q}", 42 * isq::length[m]) == "0X2A m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:#b}%?%U}", 42 * isq::length[m]) == "0b101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:#B}%?%U}", 42 * isq::length[m]) == "0B101010 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:#o}%?%U}", 42 * isq::length[m]) == "052 m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:#x}%?%U}", 42 * isq::length[m]) == "0x2a m"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:#X}%?%U}", 42 * isq::length[m]) == "0X2A m"); } - SECTION("value only format {:%Q} on a quantity") + SECTION("value only format {:%N} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format("{:%#bQ}", 42 * isq::length[m]) == "0b101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%#BQ}", 42 * isq::length[m]) == "0B101010"); - CHECK(MP_UNITS_STD_FMT::format("{:%#oQ}", 42 * isq::length[m]) == "052"); - CHECK(MP_UNITS_STD_FMT::format("{:%#xQ}", 42 * isq::length[m]) == "0x2a"); - CHECK(MP_UNITS_STD_FMT::format("{:%#XQ}", 42 * isq::length[m]) == "0X2A"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:#b}}", 42 * isq::length[m]) == "0b101010"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:#B}}", 42 * isq::length[m]) == "0B101010"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:#o}}", 42 * isq::length[m]) == "052"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:#x}}", 42 * isq::length[m]) == "0x2a"); + CHECK(MP_UNITS_STD_FMT::format("{:{%N:#X}}", 42 * isq::length[m]) == "0X2A"); } } @@ -845,10 +875,10 @@ TEST_CASE("localization with the 'L' specifier", "[text][fmt][localization]") std::locale grp2{std::locale::classic(), new group2}; std::locale grp3{std::locale::classic(), new group3}; - SECTION("full format {:%LQ %q} on a quantity") + SECTION("full format {:{%N:L}%?%U} on a quantity") { - CHECK(MP_UNITS_STD_FMT::format(grp2, "{:%LQ %q}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); - CHECK(MP_UNITS_STD_FMT::format(grp3, "{:%LQ %q}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); + CHECK(MP_UNITS_STD_FMT::format(grp2, "{:{%N:L}%?%U}", 299'792'458 * isq::speed[m / s]) == "2_99_79_24_58 m/s"); + CHECK(MP_UNITS_STD_FMT::format(grp3, "{:{%N:L}%?%U}", 299'792'458 * isq::speed[m / s]) == "299'792'458 m/s"); } } diff --git a/test_package/conanfile.py b/test_package/conanfile.py index 4234d8cd..5ed38a31 100644 --- a/test_package/conanfile.py +++ b/test_package/conanfile.py @@ -25,7 +25,6 @@ import os from conan import ConanFile from conan.tools.build import can_run from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout -from conan.tools.scm import Version class TestPackageConan(ConanFile):