mirror of
https://github.com/build-cpp/cmkr
synced 2026-06-08 13:22:55 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 39b4522bc5 | |||
| da4ec26df0 | |||
| 31f77ab328 | |||
| 6d41c92e37 | |||
| 411eb03db4 | |||
| 9f80ce4429 | |||
| 8650f53174 | |||
| 6cb1009c0e | |||
| 26acf23989 | |||
| 15886fc340 | |||
| d5e34b0bcc | |||
| 916ae11cae | |||
| eaa361611a | |||
| 2300612ba9 | |||
| 25f015bc83 | |||
| fc674b4c6b | |||
| 433dbb0500 | |||
| 4772a2b683 | |||
| fb4931df1f | |||
| e96cd30217 | |||
| 2db8d1ee1d | |||
| 645737afd4 | |||
| 98c2e962c0 | |||
| 438feccf0f | |||
| 80cc29b8e1 | |||
| 1a62034a8a | |||
| c12472cd8f | |||
| 228553d43a | |||
| ffe08ae60c |
Generated
+1
-1
@@ -22,7 +22,7 @@ project(cmkr
|
||||
LANGUAGES
|
||||
CXX
|
||||
VERSION
|
||||
0.2.45
|
||||
0.2.46
|
||||
DESCRIPTION
|
||||
"CMakeLists generator from TOML"
|
||||
)
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ cmkr-include = false
|
||||
|
||||
[project]
|
||||
name = "cmkr"
|
||||
version = "0.2.45"
|
||||
version = "0.2.46"
|
||||
description = "CMakeLists generator from TOML"
|
||||
languages = ["CXX"]
|
||||
include-after = [
|
||||
|
||||
+8
-1
@@ -2,7 +2,7 @@ include_guard()
|
||||
|
||||
# Change these defaults to point to your infrastructure if desired
|
||||
set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
|
||||
set(CMKR_TAG "v0.2.45" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
|
||||
set(CMKR_TAG "v0.2.46" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
|
||||
set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE)
|
||||
|
||||
# To bootstrap/generate a cmkr project: cmake -P cmkr.cmake
|
||||
@@ -61,6 +61,13 @@ endif()
|
||||
if(DEFINED ENV{CMKR_CACHE})
|
||||
set(CMKR_DIRECTORY_PREFIX "$ENV{CMKR_CACHE}")
|
||||
string(REPLACE "\\" "/" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}")
|
||||
if(CMKR_DIRECTORY_PREFIX MATCHES "^~")
|
||||
if(WIN32)
|
||||
string(REGEX REPLACE "^~" "$ENV{USERPROFILE}" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}")
|
||||
elseif(UNIX)
|
||||
string(REGEX REPLACE "^~" "$ENV{HOME}" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}")
|
||||
endif()
|
||||
endif()
|
||||
if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$")
|
||||
set(CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}/")
|
||||
endif()
|
||||
|
||||
+78
-1
@@ -199,6 +199,8 @@ tag = "v0.1"
|
||||
shallow = false # shallow clone (--depth 1)
|
||||
system = false
|
||||
subdir = "" # folder containing CMakeLists.txt
|
||||
# Set cache variables before FetchContent_MakeAvailable
|
||||
options = { BUILD_TESTS = false, BUILD_EXAMPLES = false }
|
||||
|
||||
# Include a CMake project from a URL
|
||||
[fetch-content.urlcontent]
|
||||
@@ -213,10 +215,19 @@ sha1 = "502a4e25b8b209889c99c7fa0732102682c2e4ff"
|
||||
condition = "mycondition"
|
||||
svn = "https://svn-host.com/url"
|
||||
rev = "svn_rev"
|
||||
|
||||
# For many options, you can also use a separate table:
|
||||
# [fetch-content.bigproject.options]
|
||||
# BUILD_TESTS = false
|
||||
# BUILD_EXAMPLES = false
|
||||
# SOME_STRING_OPTION = "value"
|
||||
# SOME_LIST_OPTION = ["item1", "item2"] # becomes "item1;item2"
|
||||
```
|
||||
|
||||
Table keys that match CMake variable names (`[A-Z_]+`) will be passed to the [`FetchContent_Declare`](https://cmake.org/cmake/help/latest/module/FetchContent.html#command:fetchcontent_declare) command.
|
||||
|
||||
The `options` field allows setting CACHE variables before `FetchContent_MakeAvailable` is called. This is useful for configuring options on the fetched dependency (e.g., disabling tests). Boolean values become `ON`/`OFF`, arrays become semicolon-separated strings.
|
||||
|
||||
## Targets
|
||||
|
||||
```toml
|
||||
@@ -246,6 +257,24 @@ link-options = [""] # linker flags
|
||||
private-link-options = [""]
|
||||
precompile-headers = [""] # precompiled headers
|
||||
private-precompile-headers = [""]
|
||||
dependencies = [""] # add_dependencies(target ...)
|
||||
|
||||
# Keys below are for type = "custom" (add_custom_target)
|
||||
all = false
|
||||
command = ["${CMAKE_COMMAND}", "-E", "echo", "Hello"] # one command line
|
||||
commands = [ # multiple command lines
|
||||
["${CMAKE_COMMAND}", "-E", "echo", "First"],
|
||||
["${CMAKE_COMMAND}", "-E", "echo", "Second"],
|
||||
]
|
||||
depends = ["generated.txt"] # add_custom_target(... DEPENDS ...)
|
||||
byproducts = ["generated.txt"]
|
||||
working-directory = "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
comment = "Run custom target"
|
||||
job-pool = "pool"
|
||||
job-server-aware = true
|
||||
verbatim = true
|
||||
uses-terminal = false # incompatible with job-pool when true
|
||||
command-expand-lists = true
|
||||
|
||||
cmake-before = """
|
||||
message(STATUS "CMake injected before the target")
|
||||
@@ -263,6 +292,51 @@ CXX_STANDARD_REQUIRED = true
|
||||
FOLDER = "MyFolder"
|
||||
```
|
||||
|
||||
### Custom commands
|
||||
|
||||
Use `[[target.<name>.custom-command]]` to map directly to [`add_custom_command`](https://cmake.org/cmake/help/latest/command/add_custom_command.html). A custom command can use either the `OUTPUT` form or the `TARGET` form:
|
||||
|
||||
```toml
|
||||
# OUTPUT form (add_custom_command(OUTPUT ...))
|
||||
[[target.mytarget.custom-command]]
|
||||
condition = "mycondition"
|
||||
outputs = ["generated.cpp"] # relative paths are interpreted from ${CMAKE_CURRENT_BINARY_DIR}
|
||||
command = [
|
||||
"${CMAKE_COMMAND}",
|
||||
"-DOUTPUT=${CMAKE_CURRENT_BINARY_DIR}/generated.cpp",
|
||||
"-P",
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/generate.cmake",
|
||||
]
|
||||
depends = ["cmake/generate.cmake"]
|
||||
byproducts = ["generated.hpp"]
|
||||
main-dependency = "cmake/generate.cmake"
|
||||
implicit-depends = [["CXX", "src/input.idl"]]
|
||||
working-directory = "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
comment = "Generate source"
|
||||
depfile = "${CMAKE_CURRENT_BINARY_DIR}/generated.d"
|
||||
job-pool = "pool" # incompatible with uses-terminal when uses-terminal = true
|
||||
job-server-aware = true
|
||||
verbatim = true
|
||||
append = false
|
||||
uses-terminal = false
|
||||
codegen = false
|
||||
command-expand-lists = false
|
||||
depends-explicit-only = false
|
||||
|
||||
# TARGET form (add_custom_command(TARGET ...))
|
||||
[[target.mytarget.custom-command]]
|
||||
build-event = "post-build" # pre-build, pre-link, post-build
|
||||
command = ["${CMAKE_COMMAND}", "-E", "touch", "${CMAKE_CURRENT_BINARY_DIR}/post-build.stamp"]
|
||||
byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/post-build.stamp"]
|
||||
working-directory = "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
comment = "Post-build step"
|
||||
verbatim = true
|
||||
command-expand-lists = false
|
||||
uses-terminal = false
|
||||
```
|
||||
|
||||
For each custom command entry, exactly one of `outputs` or `build-event` is required. Relative `outputs`/`byproducts` paths follow CMake and are interpreted from the current binary directory. `build-event` is only valid for non-`interface` targets, and `pre-link` is not supported for `type = "custom"`. In both `add_custom_command(OUTPUT ...)` and `add_custom_target(...)`, `job-pool` cannot be combined with `uses-terminal = true`. When `append = true` is used with the `OUTPUT` form, at least one `command`/`commands` entry or `depends` must also be provided.
|
||||
|
||||
A table mapping the cmkr features to the relevant CMake construct and the relevant documentation pages:
|
||||
|
||||
| cmkr | CMake construct | Description |
|
||||
@@ -279,6 +353,9 @@ A table mapping the cmkr features to the relevant CMake construct and the releva
|
||||
| `link-libraries` | [`target_link_libraries`](https://cmake.org/cmake/help/latest/command/target_link_libraries.html) | Adds library dependencies. Use `::mylib` to make sure a target exists. |
|
||||
| `link-options` | [`target_link_options`](https://cmake.org/cmake/help/latest/command/target_link_options.html) | Adds linker flags. |
|
||||
| `precompile-headers` | [`target_precompile_headers`](https://cmake.org/cmake/help/latest/command/target_precompile_headers.html) | Specifies precompiled headers. |
|
||||
| `dependencies` | [`add_dependencies`](https://cmake.org/cmake/help/latest/command/add_dependencies.html) | Adds target-level dependencies. |
|
||||
| `type = "custom"` + custom target keys | [`add_custom_target`](https://cmake.org/cmake/help/latest/command/add_custom_target.html) | Creates a custom target with command/dependency options. |
|
||||
| `[[target.<name>.custom-command]]` | [`add_custom_command`](https://cmake.org/cmake/help/latest/command/add_custom_command.html) | Supports both `OUTPUT` and `TARGET` forms. |
|
||||
| `properties` | [`set_target_properties`](https://cmake.org/cmake/help/latest/command/set_target_properties.html) | See [properties on targets](https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#properties-on-targets) for more information. |
|
||||
|
||||
The default [visibility](/basics) is as follows:
|
||||
@@ -342,4 +419,4 @@ files = ["content/my.png"]
|
||||
dirs = ["include"]
|
||||
configs = ["Release", "Debug"]
|
||||
optional = false
|
||||
```
|
||||
```
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
# Automatically generated from tests/custom-command/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: Tests add_custom_command and add_custom_target support
|
||||
permalink: /examples/custom-command
|
||||
parent: Examples
|
||||
nav_order: 12
|
||||
---
|
||||
|
||||
# Tests add_custom_command and add_custom_target support
|
||||
|
||||
This test demonstrates add_custom_command and add_custom_target support in cmkr.
|
||||
|
||||
```toml
|
||||
#
|
||||
# There are two forms of add_custom_command in CMake:
|
||||
# 1. Output form: generates files that can be consumed by other targets
|
||||
# 2. Target form: runs commands at build time for a specific target (pre-build, pre-link, post-build)
|
||||
|
||||
[project]
|
||||
name = "custom-command"
|
||||
description = "Tests add_custom_command and add_custom_target support"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Simple executable demonstrating post-build events
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[target.hello]
|
||||
type = "executable"
|
||||
sources = ["src/hello.cpp"]
|
||||
|
||||
# This post-build command runs after the 'hello' executable is built.
|
||||
# build-event can be: "pre-build", "pre-link", or "post-build"
|
||||
[[target.hello.custom-command]]
|
||||
build-event = "post-build"
|
||||
command = ["${CMAKE_COMMAND}", "-E", "echo", "Built executable: $<TARGET_FILE_NAME:hello>"]
|
||||
comment = "Print the built executable name"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Executable with code generation (output form custom command)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[target.custom-command]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
include-directories = ["${CMAKE_CURRENT_BINARY_DIR}/generated"]
|
||||
|
||||
# Output form: This custom command generates source files before building.
|
||||
# Relative outputs follow CMake and are interpreted from the binary directory.
|
||||
# The outputs are automatically added as sources to the target.
|
||||
[[target.custom-command.custom-command]]
|
||||
outputs = ["generated/generated.cpp"]
|
||||
byproducts = ["generated/generated.hpp"]
|
||||
depends = ["cmake/generate_source.cmake"]
|
||||
command = [
|
||||
"${CMAKE_COMMAND}",
|
||||
"-DOUTPUT_CPP=${CMAKE_CURRENT_BINARY_DIR}/generated/generated.cpp",
|
||||
"-DOUTPUT_HPP=${CMAKE_CURRENT_BINARY_DIR}/generated/generated.hpp",
|
||||
"-P",
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/generate_source.cmake",
|
||||
]
|
||||
comment = "Generate source files"
|
||||
verbatim = true
|
||||
|
||||
# Target form: This post-build command runs after 'custom-command' is built.
|
||||
[[target.custom-command.custom-command]]
|
||||
build-event = "post-build"
|
||||
command = [
|
||||
"${CMAKE_COMMAND}",
|
||||
"-E",
|
||||
"touch",
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/custom-command-post-build.stamp",
|
||||
]
|
||||
byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/custom-command-post-build.stamp"]
|
||||
comment = "Create a post-build stamp file"
|
||||
verbatim = true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Custom target (add_custom_target)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# A custom target runs commands independently of any executable/library.
|
||||
# Setting 'all = true' makes it run as part of the default build.
|
||||
[target.custom-codegen]
|
||||
type = "custom"
|
||||
all = true
|
||||
command = [
|
||||
"${CMAKE_COMMAND}",
|
||||
"-E",
|
||||
"touch",
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/custom-target.stamp",
|
||||
]
|
||||
byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/custom-target.stamp"]
|
||||
comment = "Run custom target"
|
||||
verbatim = true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Template-based custom target
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Custom target options should also work when the target type comes from a template.
|
||||
[template.generated-custom-target]
|
||||
type = "custom"
|
||||
all = true
|
||||
verbatim = true
|
||||
|
||||
[target.custom-codegen-template]
|
||||
type = "generated-custom-target"
|
||||
command = [
|
||||
"${CMAKE_COMMAND}",
|
||||
"-DINPUT=${CMAKE_CURRENT_BINARY_DIR}/template-custom-target.stamp",
|
||||
"-P",
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_file_exists.cmake",
|
||||
]
|
||||
comment = "Verify the generated stamp exists"
|
||||
|
||||
# The output-form custom command should automatically become a dependency of the
|
||||
# custom target, so the verification command sees the stamp file.
|
||||
[[target.custom-codegen-template.custom-command]]
|
||||
outputs = ["template-custom-target.stamp"]
|
||||
command = [
|
||||
"${CMAKE_COMMAND}",
|
||||
"-E",
|
||||
"touch",
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/template-custom-target.stamp",
|
||||
]
|
||||
comment = "Create a stamp file for the template-based custom target"
|
||||
verbatim = true
|
||||
|
||||
# A target-level all = false should override the template's all = true.
|
||||
# If this target is incorrectly included in the default build, the build fails.
|
||||
[target.custom-codegen-template-disabled]
|
||||
type = "generated-custom-target"
|
||||
all = false
|
||||
command = ["${CMAKE_COMMAND}", "-E", "false"]
|
||||
comment = "This target must not run as part of the default build"
|
||||
```
|
||||
|
||||
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/custom-command/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/custom-command/cmake.toml).</sub></sup>
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
# Automatically generated from tests/generator-executable/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: Tests using an executable to generate sources for another target
|
||||
permalink: /examples/generator-executable
|
||||
parent: Examples
|
||||
nav_order: 13
|
||||
---
|
||||
|
||||
# Tests using an executable to generate sources for another target
|
||||
|
||||
This test demonstrates a common pattern: using an executable to generate sources
|
||||
|
||||
for another target. The generator runs at build time and produces code that is
|
||||
|
||||
consumed by the main executable.
|
||||
|
||||
```toml
|
||||
#
|
||||
# CMake handles the dependency automatically: the generator executable is built
|
||||
# first, then it runs to produce the generated sources, and finally the main
|
||||
# executable is built using those sources.
|
||||
|
||||
[project]
|
||||
name = "generator-executable"
|
||||
description = "Tests using an executable to generate sources for another target"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Generator executable: produces code at build time
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[target.generate_numbers]
|
||||
type = "executable"
|
||||
sources = ["src/generate_numbers.cpp"]
|
||||
|
||||
# Post-build: confirm the generator was built
|
||||
[[target.generate_numbers.custom-command]]
|
||||
build-event = "post-build"
|
||||
command = ["${CMAKE_COMMAND}", "-E", "echo", "Generator built successfully: $<TARGET_FILE_NAME:generate_numbers>"]
|
||||
comment = "Confirm generator executable was built"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Main executable: uses generated sources
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[target.main]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
include-directories = ["${CMAKE_CURRENT_BINARY_DIR}/generated"]
|
||||
|
||||
# Create the generated directory before the generator runs.
|
||||
cmake-before = """
|
||||
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
"""
|
||||
|
||||
# Output form custom command: runs the generate_numbers executable to generate sources.
|
||||
# The outputs are automatically added as sources to this target.
|
||||
# The DEPENDS on "generate_numbers" ensures the generator is built before this runs.
|
||||
[[target.main.custom-command]]
|
||||
outputs = ["${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"]
|
||||
depends = ["generate_numbers"]
|
||||
command = ["$<TARGET_FILE:generate_numbers>", "${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"]
|
||||
comment = "Run generate_numbers to generate numbers.cpp"
|
||||
|
||||
# Post-build: run the executable to verify it works
|
||||
[[target.main.custom-command]]
|
||||
build-event = "post-build"
|
||||
command = ["$<TARGET_FILE:main>"]
|
||||
comment = "Run the main executable to verify generated code works"
|
||||
```
|
||||
|
||||
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/generator-executable/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/generator-executable/cmake.toml).</sub></sup>
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
# Automatically generated from tests/objective-c/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: Objective-C
|
||||
permalink: /examples/objective-c
|
||||
parent: Examples
|
||||
nav_order: 11
|
||||
---
|
||||
|
||||
# Objective-C
|
||||
|
||||
Add Objective-C sources on Apple platforms:
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "objective-c"
|
||||
description = "Objective-C"
|
||||
languages = ["C"]
|
||||
apple.languages = ["OBJC"]
|
||||
|
||||
[target.hello]
|
||||
type = "executable"
|
||||
sources = ["src/main.c"]
|
||||
apple.sources = ["src/apple.m"]
|
||||
apple.link-libraries = ["$<LINK_LIBRARY:FRAMEWORK,Foundation>"]
|
||||
```
|
||||
|
||||
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/objective-c/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/objective-c/cmake.toml).</sub></sup>
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
# Automatically generated from tests/protobuf/cmake.toml - DO NOT EDIT
|
||||
layout: default
|
||||
title: Demonstrates protobuf integration with cmkr custom commands
|
||||
permalink: /examples/protobuf
|
||||
parent: Examples
|
||||
nav_order: 14
|
||||
---
|
||||
|
||||
# Demonstrates protobuf integration with cmkr custom commands
|
||||
|
||||
This test demonstrates using protobuf with cmkr for code generation.
|
||||
|
||||
```toml
|
||||
#
|
||||
# The example:
|
||||
# 1. Fetches protobuf from GitHub using FetchContent
|
||||
# 2. Uses a custom command to generate C++ sources from a .proto file
|
||||
# 3. Links an executable against the generated sources and protobuf library
|
||||
# 4. Serializes and deserializes a simple message
|
||||
#
|
||||
# Note: protobuf v21.x is used because newer versions (v22+) require abseil-cpp
|
||||
# as a dependency, which significantly complicates the build.
|
||||
|
||||
[cmake]
|
||||
version = "3.18...3.31"
|
||||
|
||||
[project]
|
||||
name = "protobuf-example"
|
||||
description = "Demonstrates protobuf integration with cmkr custom commands"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Fetch protobuf from GitHub
|
||||
# Using v21.12 (last version before abseil-cpp became required)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[fetch-content.protobuf]
|
||||
git = "https://github.com/protocolbuffers/protobuf"
|
||||
tag = "v21.12"
|
||||
options = {
|
||||
protobuf_BUILD_TESTS = false,
|
||||
protobuf_BUILD_EXAMPLES = false,
|
||||
protobuf_BUILD_LIBPROTOC = false,
|
||||
protobuf_BUILD_SHARED_LIBS = false,
|
||||
protobuf_MSVC_STATIC_RUNTIME = false,
|
||||
SKIP_INSTALL_ALL = true,
|
||||
protobuf_WITH_ZLIB = false,
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Main executable using protobuf
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[target.protobuf_example]
|
||||
type = "executable"
|
||||
sources = [
|
||||
"src/main.cpp",
|
||||
]
|
||||
include-directories = [
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated",
|
||||
]
|
||||
link-libraries = ["protobuf::libprotobuf"]
|
||||
compile-features = ["cxx_std_17"]
|
||||
|
||||
# Create the generated directory before running protoc
|
||||
cmake-before = """
|
||||
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
"""
|
||||
|
||||
# Custom command to generate C++ sources from .proto file
|
||||
# This uses protoc from the fetched protobuf repository
|
||||
[[target.protobuf_example.custom-command]]
|
||||
outputs = [
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated/addressbook.pb.h",
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated/addressbook.pb.cc",
|
||||
]
|
||||
depends = [
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/proto/addressbook.proto",
|
||||
"protobuf::protoc", # Ensure protoc is built first
|
||||
]
|
||||
command = [
|
||||
"$<TARGET_FILE:protobuf::protoc>",
|
||||
"--cpp_out=${CMAKE_CURRENT_BINARY_DIR}/generated",
|
||||
"-I${CMAKE_CURRENT_SOURCE_DIR}/proto",
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/proto/addressbook.proto",
|
||||
]
|
||||
comment = "Generate C++ sources from addressbook.proto"
|
||||
verbatim = true
|
||||
|
||||
# Post-build: run the executable to verify it works
|
||||
[[target.protobuf_example.custom-command]]
|
||||
build-event = "post-build"
|
||||
command = ["$<TARGET_FILE:protobuf_example>"]
|
||||
comment = "Run protobuf_example to verify serialization/deserialization works"
|
||||
```
|
||||
|
||||
|
||||
|
||||
<sup><sub>This page was automatically generated from [tests/protobuf/cmake.toml](https://github.com/build-cpp/cmkr/tree/main/tests/protobuf/cmake.toml).</sub></sup>
|
||||
@@ -20,11 +20,11 @@ description = "Target templates"
|
||||
type = "executable"
|
||||
sources = ["src/templates.cpp"]
|
||||
compile-definitions = ["IS_APP"]
|
||||
|
||||
# Unlike interface targets you can also inherit properties
|
||||
[template.app.properties]
|
||||
CXX_STANDARD = "11"
|
||||
CXX_STANDARD_REQUIRED = true
|
||||
properties = {
|
||||
CXX_STANDARD = "11",
|
||||
CXX_STANDARD_REQUIRED = true,
|
||||
}
|
||||
|
||||
[target.app-a]
|
||||
type = "app"
|
||||
|
||||
+109
-1
@@ -74,6 +74,104 @@ enum TargetType {
|
||||
extern const char *targetTypeNames[target_last];
|
||||
|
||||
struct Target {
|
||||
struct CustomTarget {
|
||||
bool has_all = false;
|
||||
bool all = false;
|
||||
|
||||
bool has_command = false;
|
||||
bool has_commands = false;
|
||||
std::vector<std::vector<std::string>> commands;
|
||||
std::vector<std::string> depends;
|
||||
std::vector<std::string> byproducts;
|
||||
|
||||
bool has_working_directory = false;
|
||||
std::string working_directory;
|
||||
|
||||
bool has_comment = false;
|
||||
std::string comment;
|
||||
|
||||
bool has_job_pool = false;
|
||||
std::string job_pool;
|
||||
|
||||
bool has_job_server_aware = false;
|
||||
bool job_server_aware = false;
|
||||
|
||||
bool has_verbatim = false;
|
||||
bool verbatim = false;
|
||||
|
||||
bool has_uses_terminal = false;
|
||||
bool uses_terminal = false;
|
||||
|
||||
bool has_command_expand_lists = false;
|
||||
bool command_expand_lists = false;
|
||||
|
||||
bool empty() const {
|
||||
return !has_all && !has_command && !has_commands && commands.empty() && depends.empty() && byproducts.empty() && !has_working_directory &&
|
||||
!has_comment && !has_job_pool && !has_job_server_aware && !has_verbatim && !has_uses_terminal && !has_command_expand_lists;
|
||||
}
|
||||
};
|
||||
|
||||
struct CustomCommand {
|
||||
struct ImplicitDependency {
|
||||
std::string language;
|
||||
std::string file;
|
||||
};
|
||||
|
||||
std::string condition;
|
||||
std::vector<std::vector<std::string>> commands;
|
||||
|
||||
std::vector<std::string> outputs;
|
||||
std::string build_event;
|
||||
|
||||
bool has_append = false;
|
||||
bool append = false;
|
||||
|
||||
bool has_main_dependency = false;
|
||||
std::string main_dependency;
|
||||
|
||||
std::vector<std::string> depends;
|
||||
std::vector<std::string> byproducts;
|
||||
std::vector<ImplicitDependency> implicit_depends;
|
||||
|
||||
bool has_working_directory = false;
|
||||
std::string working_directory;
|
||||
|
||||
bool has_comment = false;
|
||||
std::string comment;
|
||||
|
||||
bool has_depfile = false;
|
||||
std::string depfile;
|
||||
|
||||
bool has_job_pool = false;
|
||||
std::string job_pool;
|
||||
|
||||
bool has_job_server_aware = false;
|
||||
bool job_server_aware = false;
|
||||
|
||||
bool has_verbatim = false;
|
||||
bool verbatim = false;
|
||||
|
||||
bool has_uses_terminal = false;
|
||||
bool uses_terminal = false;
|
||||
|
||||
bool has_codegen = false;
|
||||
bool codegen = false;
|
||||
|
||||
bool has_command_expand_lists = false;
|
||||
bool command_expand_lists = false;
|
||||
|
||||
bool has_depends_explicit_only = false;
|
||||
bool depends_explicit_only = false;
|
||||
|
||||
bool is_output_form() const {
|
||||
return !outputs.empty();
|
||||
}
|
||||
|
||||
bool is_target_form() const {
|
||||
return !build_event.empty();
|
||||
}
|
||||
};
|
||||
|
||||
std::string name;
|
||||
TargetType type = target_last;
|
||||
std::string type_name;
|
||||
@@ -105,6 +203,11 @@ struct Target {
|
||||
ConditionVector precompile_headers;
|
||||
ConditionVector private_precompile_headers;
|
||||
|
||||
ConditionVector dependencies;
|
||||
|
||||
CustomTarget custom_target;
|
||||
std::vector<CustomCommand> custom_commands;
|
||||
|
||||
std::string condition;
|
||||
std::string alias;
|
||||
Condition<tsl::ordered_map<std::string, std::string>> properties;
|
||||
@@ -152,10 +255,15 @@ struct Subdir {
|
||||
ConditionVector include_after;
|
||||
};
|
||||
|
||||
struct ContentOption {
|
||||
mpark::variant<bool, std::string> value;
|
||||
};
|
||||
|
||||
struct Content {
|
||||
std::string name;
|
||||
std::string condition;
|
||||
tsl::ordered_map<std::string, std::string> arguments;
|
||||
tsl::ordered_map<std::string, ContentOption> options;
|
||||
|
||||
Condition<std::string> cmake_before;
|
||||
Condition<std::string> cmake_after;
|
||||
@@ -192,7 +300,7 @@ struct Project {
|
||||
std::string project_name;
|
||||
std::string project_version;
|
||||
std::string project_description;
|
||||
std::vector<std::string> project_languages;
|
||||
ConditionVector project_languages;
|
||||
bool project_allow_unknown_languages = false;
|
||||
MsvcRuntimeType project_msvc_runtime = msvc_last;
|
||||
Condition<std::string> cmake_before;
|
||||
|
||||
+433
-73
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "project_parser.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
@@ -54,6 +55,18 @@ static std::string format(const char *format, const tsl::ordered_map<std::string
|
||||
return s;
|
||||
}
|
||||
|
||||
static std::string quoted(const std::string &str) {
|
||||
return "\"" + str + "\"";
|
||||
}
|
||||
|
||||
static std::string space_error_check(const std::string &str) {
|
||||
auto pos = str.find_last_of(' ');
|
||||
if (pos != std::string::npos)
|
||||
return quoted(str) + " <------ !space(s)";
|
||||
else
|
||||
return quoted(str);
|
||||
}
|
||||
|
||||
static std::vector<fs::path> expand_cmake_path(const fs::path &source_path, const fs::path &toml_dir, bool is_root_project) {
|
||||
auto is_subdir = [](fs::path p, const fs::path &root) {
|
||||
while (true) {
|
||||
@@ -243,7 +256,13 @@ void generate_project(const std::string &type) {
|
||||
generate_gitfile(".gitattributes", {"/**/CMakeLists.txt linguist-generated", "/**/cmkr.cmake linguist-vendored"});
|
||||
|
||||
// Generate .gitignore with reasonable defaults for CMake
|
||||
generate_gitfile(".gitignore", {"build*/", "cmake-build*/", "CMakerLists.txt", "CMakeLists.txt.user"});
|
||||
generate_gitfile(".gitignore", {
|
||||
"build*/",
|
||||
"cmake-build*/",
|
||||
"CMakerLists.txt",
|
||||
"CMakeLists.txt.user",
|
||||
"CMakeUserPresets.json",
|
||||
});
|
||||
|
||||
tsl::ordered_map<std::string, std::string> variables = {
|
||||
{"@name", name},
|
||||
@@ -713,16 +732,41 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
|
||||
parser::Project project(parent_project, path, false);
|
||||
|
||||
for (const auto &lang : project.project_languages) {
|
||||
if (known_languages.find(lang) == known_languages.end()) {
|
||||
if (project.project_allow_unknown_languages) {
|
||||
printf("[warning] Unknown language '%s' specified\n", lang.c_str());
|
||||
} else {
|
||||
throw std::runtime_error("Unknown language '" + lang + "' specified");
|
||||
}
|
||||
tsl::ordered_set<std::string> flat_project_languages;
|
||||
for (const auto &itr : project.project_languages) {
|
||||
for (const auto &language : itr.second) {
|
||||
flat_project_languages.insert(language);
|
||||
}
|
||||
}
|
||||
|
||||
// Reference: https://gitlab.kitware.com/cmake/cmake/-/issues/24340#note_1304703
|
||||
flat_project_languages.insert("RC");
|
||||
|
||||
// All acceptable extensions based off our given languages.
|
||||
tsl::ordered_set<std::string> project_extensions;
|
||||
for (const auto &language : flat_project_languages) {
|
||||
auto itr = known_languages.find(language);
|
||||
if (itr == known_languages.end()) {
|
||||
if (project.project_allow_unknown_languages) {
|
||||
printf("[warning] Unknown language '%s' specified\n", language.c_str());
|
||||
} else {
|
||||
throw std::runtime_error("Unknown language '" + language + "' specified");
|
||||
}
|
||||
} else {
|
||||
project_extensions.insert(itr->second.begin(), itr->second.end());
|
||||
}
|
||||
}
|
||||
|
||||
auto contains_language_source = [&project_extensions](const std::vector<std::string> &sources) {
|
||||
for (const auto &source : sources) {
|
||||
auto extension = fs::path(source).extension().string();
|
||||
if (project_extensions.count(extension) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Generator gen(project, path);
|
||||
|
||||
// Helper lambdas for more convenient CMake generation
|
||||
@@ -900,17 +944,29 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
gen.conditional_cmake(project.cmake_before);
|
||||
|
||||
if (!project.project_name.empty()) {
|
||||
auto languages = std::make_pair("LANGUAGES", project.project_languages);
|
||||
auto languages = std::make_pair("LANGUAGES", project.project_languages[""]);
|
||||
auto version = std::make_pair("VERSION", project.project_version);
|
||||
auto description = std::make_pair("DESCRIPTION", project.project_description);
|
||||
cmd("project")(project.project_name, languages, version, description).endl();
|
||||
cmd("project")(project.project_name, languages, version, description);
|
||||
|
||||
for (const auto &language : project.project_languages) {
|
||||
for (const auto &language : project.project_languages[""]) {
|
||||
if (language == "CSharp") {
|
||||
cmd("include")("CSharpUtilities").endl();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gen.handle_condition(project.project_languages, [&cmd](const std::string &condition, const std::vector<std::string> &languages) {
|
||||
if (!condition.empty()) {
|
||||
cmd("enable_language")(languages).endl();
|
||||
for (const auto &language : languages) {
|
||||
if (language == "CSharp") {
|
||||
cmd("include")("CSharpUtilities").endl();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
gen.conditional_includes(project.include_after);
|
||||
@@ -1068,6 +1124,22 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
gen.conditional_includes(content.include_before);
|
||||
gen.conditional_cmake(content.cmake_before);
|
||||
|
||||
// Set cache variables before FetchContent
|
||||
if (!content.options.empty()) {
|
||||
for (const auto &opt : content.options) {
|
||||
std::string value;
|
||||
std::string type;
|
||||
if (opt.second.value.index() == 0) {
|
||||
value = mpark::get<0>(opt.second.value) ? "ON" : "OFF";
|
||||
type = "BOOL";
|
||||
} else {
|
||||
value = mpark::get<1>(opt.second.value);
|
||||
type = "STRING";
|
||||
}
|
||||
cmd("set")(opt.first, value, "CACHE", type, RawArg("\"\""), "FORCE");
|
||||
}
|
||||
}
|
||||
|
||||
std::string version_info;
|
||||
if (content.arguments.contains("GIT_TAG")) {
|
||||
version_info = " (" + content.arguments.at("GIT_TAG") + ")";
|
||||
@@ -1126,34 +1198,6 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
gen.conditional_cmake(subdir.cmake_after);
|
||||
}
|
||||
|
||||
// The implicit default is ["C", "CXX"], so make sure this list isn't
|
||||
// empty or projects without languages explicitly defined will error.
|
||||
auto project_languages = project.project_languages;
|
||||
if (project_languages.empty())
|
||||
project_languages = {"C", "CXX"};
|
||||
|
||||
// Reference: https://gitlab.kitware.com/cmake/cmake/-/issues/24340#note_1304703
|
||||
project_languages.push_back("RC");
|
||||
|
||||
// All acceptable extensions based off our given languages.
|
||||
tsl::ordered_set<std::string> project_extensions;
|
||||
for (const auto &language : project_languages) {
|
||||
auto itr = known_languages.find(language);
|
||||
if (itr != known_languages.end()) {
|
||||
project_extensions.insert(itr->second.begin(), itr->second.end());
|
||||
}
|
||||
}
|
||||
|
||||
auto contains_language_source = [&project_extensions](const std::vector<std::string> &sources) {
|
||||
for (const auto &source : sources) {
|
||||
auto extension = fs::path(source).extension().string();
|
||||
if (project_extensions.count(extension) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!project.targets.empty()) {
|
||||
auto project_root = project.root();
|
||||
for (size_t i = 0; i < project.targets.size(); i++) {
|
||||
@@ -1178,6 +1222,14 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
}
|
||||
}
|
||||
|
||||
auto resolved_target_type = target.type;
|
||||
if (tmplate != nullptr) {
|
||||
if (resolved_target_type != parser::target_template) {
|
||||
throw_target_error("Unreachable code, unexpected target type for template");
|
||||
}
|
||||
resolved_target_type = tmplate->outline.type;
|
||||
}
|
||||
|
||||
ConditionScope cs(gen, target.condition);
|
||||
|
||||
// Detect if there is cmake included before/after the target
|
||||
@@ -1217,6 +1269,103 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
gen.conditional_includes(target.include_before);
|
||||
gen.conditional_cmake(target.cmake_before);
|
||||
|
||||
auto merge_unique_vector = [](std::vector<std::string> &destination, const std::vector<std::string> &source) {
|
||||
for (const auto &item : source) {
|
||||
if (std::find(destination.begin(), destination.end(), item) == destination.end()) {
|
||||
destination.push_back(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto custom_target = target.custom_target;
|
||||
if (tmplate != nullptr) {
|
||||
const auto &custom = tmplate->outline.custom_target;
|
||||
if (!custom_target.has_all && custom.has_all) {
|
||||
custom_target.has_all = true;
|
||||
custom_target.all = custom.all;
|
||||
}
|
||||
if (custom.has_command) {
|
||||
custom_target.has_command = true;
|
||||
}
|
||||
if (custom.has_commands) {
|
||||
custom_target.has_commands = true;
|
||||
}
|
||||
if (!custom.commands.empty()) {
|
||||
custom_target.commands.insert(custom_target.commands.begin(), custom.commands.begin(), custom.commands.end());
|
||||
}
|
||||
merge_unique_vector(custom_target.depends, custom.depends);
|
||||
merge_unique_vector(custom_target.byproducts, custom.byproducts);
|
||||
if (!custom_target.has_working_directory && custom.has_working_directory) {
|
||||
custom_target.has_working_directory = true;
|
||||
custom_target.working_directory = custom.working_directory;
|
||||
}
|
||||
if (!custom_target.has_comment && custom.has_comment) {
|
||||
custom_target.has_comment = true;
|
||||
custom_target.comment = custom.comment;
|
||||
}
|
||||
if (!custom_target.has_job_pool && custom.has_job_pool) {
|
||||
custom_target.has_job_pool = true;
|
||||
custom_target.job_pool = custom.job_pool;
|
||||
}
|
||||
if (!custom_target.has_job_server_aware && custom.has_job_server_aware) {
|
||||
custom_target.has_job_server_aware = true;
|
||||
custom_target.job_server_aware = custom.job_server_aware;
|
||||
}
|
||||
if (!custom_target.has_verbatim && custom.has_verbatim) {
|
||||
custom_target.has_verbatim = true;
|
||||
custom_target.verbatim = custom.verbatim;
|
||||
}
|
||||
if (!custom_target.has_uses_terminal && custom.has_uses_terminal) {
|
||||
custom_target.has_uses_terminal = true;
|
||||
custom_target.uses_terminal = custom.uses_terminal;
|
||||
}
|
||||
if (!custom_target.has_command_expand_lists && custom.has_command_expand_lists) {
|
||||
custom_target.has_command_expand_lists = true;
|
||||
custom_target.command_expand_lists = custom.command_expand_lists;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<parser::Target::CustomCommand> custom_commands;
|
||||
if (tmplate != nullptr) {
|
||||
custom_commands.insert(custom_commands.end(), tmplate->outline.custom_commands.begin(), tmplate->outline.custom_commands.end());
|
||||
}
|
||||
custom_commands.insert(custom_commands.end(), target.custom_commands.begin(), target.custom_commands.end());
|
||||
|
||||
if (resolved_target_type == parser::target_custom && custom_target.has_job_pool && custom_target.has_uses_terminal &&
|
||||
custom_target.uses_terminal) {
|
||||
throw_target_error("job-pool cannot be used with uses-terminal");
|
||||
}
|
||||
|
||||
auto has_cmake_path_reference = [](const std::string &value) {
|
||||
return value.find("${") != std::string::npos || value.find("$ENV{") != std::string::npos ||
|
||||
value.find("$CACHE{") != std::string::npos;
|
||||
};
|
||||
|
||||
auto normalize_generated_output_source = [&has_cmake_path_reference](const std::string &output) {
|
||||
auto starts_with = [](const std::string &value, const std::string &prefix) {
|
||||
return value.rfind(prefix, 0) == 0;
|
||||
};
|
||||
if (fs::path(output).is_absolute()) {
|
||||
return output;
|
||||
}
|
||||
if (starts_with(output, "${CMAKE_CURRENT_BINARY_DIR}") || starts_with(output, "${CMAKE_BINARY_DIR}") ||
|
||||
starts_with(output, "${PROJECT_BINARY_DIR}") || starts_with(output, "${CMAKE_CURRENT_SOURCE_DIR}") ||
|
||||
starts_with(output, "${CMAKE_SOURCE_DIR}") || starts_with(output, "${PROJECT_SOURCE_DIR}") ||
|
||||
starts_with(output, "${CMAKE_CURRENT_LIST_DIR}") || starts_with(output, "$ENV{") || starts_with(output, "$CACHE{") ||
|
||||
has_cmake_path_reference(output)) {
|
||||
return output;
|
||||
}
|
||||
return "${CMAKE_CURRENT_BINARY_DIR}/" + output;
|
||||
};
|
||||
|
||||
parser::Condition<tsl::ordered_set<std::string>> mcustom_target_depends;
|
||||
if (resolved_target_type == parser::target_custom) {
|
||||
auto &unconditional_depends = mcustom_target_depends[""];
|
||||
for (const auto &dep : custom_target.depends) {
|
||||
unconditional_depends.insert(dep);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the sources from the template and the target. The sources
|
||||
// without condition need to be processed first
|
||||
parser::Condition<tsl::ordered_set<std::string>> msources;
|
||||
@@ -1235,8 +1384,21 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
}
|
||||
merge_sources(target.sources);
|
||||
|
||||
for (const auto &custom_command : custom_commands) {
|
||||
if (!custom_command.is_output_form()) {
|
||||
continue;
|
||||
}
|
||||
auto &condition_sources = msources[custom_command.condition];
|
||||
for (const auto &output : custom_command.outputs) {
|
||||
condition_sources.insert(normalize_generated_output_source(output));
|
||||
if (resolved_target_type == parser::target_custom) {
|
||||
mcustom_target_depends[custom_command.condition].insert(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Improve IDE support
|
||||
if (target.type != parser::target_interface) {
|
||||
if (resolved_target_type != parser::target_interface) {
|
||||
msources[""].insert("cmake.toml");
|
||||
}
|
||||
|
||||
@@ -1270,7 +1432,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
}
|
||||
|
||||
// Make sure there are source files for the languages used by the project
|
||||
switch (target.type) {
|
||||
switch (resolved_target_type) {
|
||||
case parser::target_executable:
|
||||
case parser::target_library:
|
||||
case parser::target_shared:
|
||||
@@ -1289,12 +1451,11 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
|
||||
// Make sure relative source files exist
|
||||
for (const auto &source : sources) {
|
||||
auto var_index = source.find("${");
|
||||
if (var_index != std::string::npos)
|
||||
if (has_cmake_path_reference(source))
|
||||
continue;
|
||||
const auto &source_path = fs::path(path) / source;
|
||||
if (!fs::exists(source_path)) {
|
||||
throw_target_error("Source file not found: " + source);
|
||||
throw_target_error("Source file not found: " + space_error_check(source));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1314,18 +1475,12 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
}
|
||||
});
|
||||
|
||||
auto target_type = target.type;
|
||||
|
||||
if (tmplate != nullptr) {
|
||||
if (target_type != parser::target_template) {
|
||||
throw_target_error("Unreachable code, unexpected target type for template");
|
||||
}
|
||||
target_type = tmplate->outline.type;
|
||||
}
|
||||
auto target_type = resolved_target_type;
|
||||
|
||||
std::string add_command;
|
||||
std::string target_type_string;
|
||||
std::string target_scope;
|
||||
bool is_custom_target = false;
|
||||
|
||||
switch (target_type) {
|
||||
case parser::target_executable:
|
||||
@@ -1354,9 +1509,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
target_scope = "INTERFACE";
|
||||
break;
|
||||
case parser::target_custom:
|
||||
// TODO: add proper support, this is hacky
|
||||
add_command = "add_custom_target";
|
||||
target_type_string = "SOURCES";
|
||||
is_custom_target = true;
|
||||
target_scope = "PUBLIC";
|
||||
break;
|
||||
case parser::target_object:
|
||||
@@ -1369,6 +1522,157 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
throw_target_error("Unimplemented enum value");
|
||||
}
|
||||
|
||||
auto has_custom_target_depends = false;
|
||||
auto custom_target_depends_var = target.name + "_DEPENDS";
|
||||
auto custom_target_depends_with_set = true;
|
||||
if (is_custom_target) {
|
||||
for (const auto &itr : mcustom_target_depends) {
|
||||
if (!itr.second.empty()) {
|
||||
has_custom_target_depends = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_custom_target_depends && mcustom_target_depends[""].empty()) {
|
||||
custom_target_depends_with_set = false;
|
||||
cmd("set")(custom_target_depends_var, RawArg("\"\"")).endl();
|
||||
}
|
||||
gen.handle_condition(mcustom_target_depends, [&](const std::string &condition, const tsl::ordered_set<std::string> &depend_set) {
|
||||
std::vector<std::string> depends;
|
||||
depends.reserve(depend_set.size());
|
||||
for (const auto &depend : depend_set) {
|
||||
depends.push_back(depend);
|
||||
}
|
||||
|
||||
if (custom_target_depends_with_set) {
|
||||
if (!condition.empty()) {
|
||||
throw_target_error("Unreachable code, make sure unconditional depends are first");
|
||||
}
|
||||
cmd("set")(custom_target_depends_var, depends);
|
||||
custom_target_depends_with_set = false;
|
||||
} else {
|
||||
cmd("list")("APPEND", custom_target_depends_var, depends);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto append_keyword = [](std::vector<RawArg> &args, const std::string &keyword) {
|
||||
args.emplace_back(keyword);
|
||||
};
|
||||
auto append_value = [](std::vector<RawArg> &args, const std::string &value) {
|
||||
args.emplace_back(Command::quote(value));
|
||||
};
|
||||
auto append_values = [&](std::vector<RawArg> &args, const std::vector<std::string> &values) {
|
||||
for (const auto &value : values) {
|
||||
append_value(args, value);
|
||||
}
|
||||
};
|
||||
auto append_commands = [&](std::vector<RawArg> &args, const std::vector<std::vector<std::string>> &commands) {
|
||||
for (const auto &command_args : commands) {
|
||||
append_keyword(args, "COMMAND");
|
||||
append_values(args, command_args);
|
||||
}
|
||||
};
|
||||
|
||||
auto emit_custom_command = [&](const parser::Target::CustomCommand &custom_command) {
|
||||
std::vector<RawArg> args;
|
||||
if (custom_command.is_output_form()) {
|
||||
append_keyword(args, "OUTPUT");
|
||||
append_values(args, custom_command.outputs);
|
||||
append_commands(args, custom_command.commands);
|
||||
if (custom_command.has_main_dependency) {
|
||||
append_keyword(args, "MAIN_DEPENDENCY");
|
||||
append_value(args, custom_command.main_dependency);
|
||||
}
|
||||
if (!custom_command.depends.empty()) {
|
||||
append_keyword(args, "DEPENDS");
|
||||
append_values(args, custom_command.depends);
|
||||
}
|
||||
if (!custom_command.byproducts.empty()) {
|
||||
append_keyword(args, "BYPRODUCTS");
|
||||
append_values(args, custom_command.byproducts);
|
||||
}
|
||||
if (!custom_command.implicit_depends.empty()) {
|
||||
append_keyword(args, "IMPLICIT_DEPENDS");
|
||||
for (const auto &implicit_dependency : custom_command.implicit_depends) {
|
||||
append_value(args, implicit_dependency.language);
|
||||
append_value(args, implicit_dependency.file);
|
||||
}
|
||||
}
|
||||
if (custom_command.has_working_directory) {
|
||||
append_keyword(args, "WORKING_DIRECTORY");
|
||||
append_value(args, custom_command.working_directory);
|
||||
}
|
||||
if (custom_command.has_comment) {
|
||||
append_keyword(args, "COMMENT");
|
||||
append_value(args, custom_command.comment);
|
||||
}
|
||||
if (custom_command.has_depfile) {
|
||||
append_keyword(args, "DEPFILE");
|
||||
append_value(args, custom_command.depfile);
|
||||
}
|
||||
if (custom_command.has_job_pool) {
|
||||
append_keyword(args, "JOB_POOL");
|
||||
append_value(args, custom_command.job_pool);
|
||||
}
|
||||
if (custom_command.has_job_server_aware) {
|
||||
append_keyword(args, "JOB_SERVER_AWARE");
|
||||
append_value(args, custom_command.job_server_aware ? "ON" : "OFF");
|
||||
}
|
||||
if (custom_command.has_verbatim && custom_command.verbatim) {
|
||||
append_keyword(args, "VERBATIM");
|
||||
}
|
||||
if (custom_command.has_append && custom_command.append) {
|
||||
append_keyword(args, "APPEND");
|
||||
}
|
||||
if (custom_command.has_uses_terminal && custom_command.uses_terminal) {
|
||||
append_keyword(args, "USES_TERMINAL");
|
||||
}
|
||||
if (custom_command.has_codegen && custom_command.codegen) {
|
||||
append_keyword(args, "CODEGEN");
|
||||
}
|
||||
if (custom_command.has_command_expand_lists && custom_command.command_expand_lists) {
|
||||
append_keyword(args, "COMMAND_EXPAND_LISTS");
|
||||
}
|
||||
if (custom_command.has_depends_explicit_only && custom_command.depends_explicit_only) {
|
||||
append_keyword(args, "DEPENDS_EXPLICIT_ONLY");
|
||||
}
|
||||
} else {
|
||||
append_keyword(args, "TARGET");
|
||||
append_value(args, target.name);
|
||||
append_keyword(args, custom_command.build_event);
|
||||
append_commands(args, custom_command.commands);
|
||||
if (!custom_command.byproducts.empty()) {
|
||||
append_keyword(args, "BYPRODUCTS");
|
||||
append_values(args, custom_command.byproducts);
|
||||
}
|
||||
if (custom_command.has_working_directory) {
|
||||
append_keyword(args, "WORKING_DIRECTORY");
|
||||
append_value(args, custom_command.working_directory);
|
||||
}
|
||||
if (custom_command.has_comment) {
|
||||
append_keyword(args, "COMMENT");
|
||||
append_value(args, custom_command.comment);
|
||||
}
|
||||
if (custom_command.has_verbatim && custom_command.verbatim) {
|
||||
append_keyword(args, "VERBATIM");
|
||||
}
|
||||
if (custom_command.has_command_expand_lists && custom_command.command_expand_lists) {
|
||||
append_keyword(args, "COMMAND_EXPAND_LISTS");
|
||||
}
|
||||
if (custom_command.has_uses_terminal && custom_command.uses_terminal) {
|
||||
append_keyword(args, "USES_TERMINAL");
|
||||
}
|
||||
}
|
||||
cmd("add_custom_command")(args).endl();
|
||||
};
|
||||
|
||||
for (const auto &custom_command : custom_commands) {
|
||||
if (custom_command.is_output_form()) {
|
||||
ConditionScope custom_scope(gen, custom_command.condition);
|
||||
emit_custom_command(custom_command);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle custom add commands from templates.
|
||||
if (tmplate != nullptr && !tmplate->add_function.empty()) {
|
||||
add_command = tmplate->add_function;
|
||||
@@ -1382,6 +1686,51 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
"${" + sources_var + "}");
|
||||
}
|
||||
}
|
||||
} else if (is_custom_target) {
|
||||
std::vector<RawArg> args;
|
||||
append_value(args, target.name);
|
||||
if (custom_target.has_all && custom_target.all) {
|
||||
append_keyword(args, "ALL");
|
||||
}
|
||||
append_commands(args, custom_target.commands);
|
||||
if (has_custom_target_depends) {
|
||||
append_keyword(args, "DEPENDS");
|
||||
append_value(args, "${" + custom_target_depends_var + "}");
|
||||
}
|
||||
if (!custom_target.byproducts.empty()) {
|
||||
append_keyword(args, "BYPRODUCTS");
|
||||
append_values(args, custom_target.byproducts);
|
||||
}
|
||||
if (custom_target.has_working_directory) {
|
||||
append_keyword(args, "WORKING_DIRECTORY");
|
||||
append_value(args, custom_target.working_directory);
|
||||
}
|
||||
if (custom_target.has_comment) {
|
||||
append_keyword(args, "COMMENT");
|
||||
append_value(args, custom_target.comment);
|
||||
}
|
||||
if (custom_target.has_job_pool) {
|
||||
append_keyword(args, "JOB_POOL");
|
||||
append_value(args, custom_target.job_pool);
|
||||
}
|
||||
if (custom_target.has_job_server_aware) {
|
||||
append_keyword(args, "JOB_SERVER_AWARE");
|
||||
append_value(args, custom_target.job_server_aware ? "ON" : "OFF");
|
||||
}
|
||||
if (custom_target.has_verbatim && custom_target.verbatim) {
|
||||
append_keyword(args, "VERBATIM");
|
||||
}
|
||||
if (custom_target.has_uses_terminal && custom_target.uses_terminal) {
|
||||
append_keyword(args, "USES_TERMINAL");
|
||||
}
|
||||
if (custom_target.has_command_expand_lists && custom_target.command_expand_lists) {
|
||||
append_keyword(args, "COMMAND_EXPAND_LISTS");
|
||||
}
|
||||
if (has_sources) {
|
||||
append_keyword(args, "SOURCES");
|
||||
append_value(args, "${" + sources_var + "}");
|
||||
}
|
||||
cmd("add_custom_target")(args).endl();
|
||||
} else {
|
||||
cmd(add_command)(target.name, target_type_string).endl();
|
||||
if (has_sources) {
|
||||
@@ -1389,6 +1738,13 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &custom_command : custom_commands) {
|
||||
if (custom_command.is_target_form()) {
|
||||
ConditionScope custom_scope(gen, custom_command.condition);
|
||||
emit_custom_command(custom_command);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: support sources from other directories
|
||||
if (has_sources) {
|
||||
cmd("source_group")("TREE", "${CMAKE_CURRENT_SOURCE_DIR}", "FILES", "${" + sources_var + "}").endl();
|
||||
@@ -1404,7 +1760,7 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
});
|
||||
};
|
||||
|
||||
auto link_libraries = [&](const parser::ConditionVector &cargs, const std::string &scope) {
|
||||
auto link_cmd = [&](const char *command, const parser::ConditionVector &cargs, const std::string &scope) {
|
||||
gen.handle_condition(cargs, [&](const std::string &, const std::vector<std::string> &args) {
|
||||
std::vector<std::string> targs;
|
||||
for (const std::string &arg : args) {
|
||||
@@ -1420,34 +1776,38 @@ void generate_cmake(const char *path, const parser::Project *parent_project) {
|
||||
targs.push_back(arg);
|
||||
}
|
||||
}
|
||||
cmd("target_link_libraries")(target.name, scope, targs);
|
||||
cmd(command)(target.name, scope, targs);
|
||||
});
|
||||
};
|
||||
|
||||
auto gen_target_cmds = [&](const parser::Target &t) {
|
||||
target_cmd("target_compile_definitions", t.compile_definitions, target_scope);
|
||||
target_cmd("target_compile_definitions", t.private_compile_definitions, "PRIVATE");
|
||||
if (!is_custom_target) {
|
||||
target_cmd("target_compile_definitions", t.compile_definitions, target_scope);
|
||||
target_cmd("target_compile_definitions", t.private_compile_definitions, "PRIVATE");
|
||||
|
||||
target_cmd("target_compile_features", t.compile_features, target_scope);
|
||||
target_cmd("target_compile_features", t.private_compile_features, "PRIVATE");
|
||||
target_cmd("target_compile_features", t.compile_features, target_scope);
|
||||
target_cmd("target_compile_features", t.private_compile_features, "PRIVATE");
|
||||
|
||||
target_cmd("target_compile_options", t.compile_options, target_scope);
|
||||
target_cmd("target_compile_options", t.private_compile_options, "PRIVATE");
|
||||
target_cmd("target_compile_options", t.compile_options, target_scope);
|
||||
target_cmd("target_compile_options", t.private_compile_options, "PRIVATE");
|
||||
|
||||
target_cmd("target_include_directories", t.include_directories, target_scope);
|
||||
target_cmd("target_include_directories", t.private_include_directories, "PRIVATE");
|
||||
target_cmd("target_include_directories", t.include_directories, target_scope);
|
||||
target_cmd("target_include_directories", t.private_include_directories, "PRIVATE");
|
||||
|
||||
target_cmd("target_link_directories", t.link_directories, target_scope);
|
||||
target_cmd("target_link_directories", t.private_link_directories, "PRIVATE");
|
||||
target_cmd("target_link_directories", t.link_directories, target_scope);
|
||||
target_cmd("target_link_directories", t.private_link_directories, "PRIVATE");
|
||||
|
||||
link_libraries(t.link_libraries, target_scope);
|
||||
link_libraries(t.private_link_libraries, "PRIVATE");
|
||||
link_cmd("target_link_libraries", t.link_libraries, target_scope);
|
||||
link_cmd("target_link_libraries", t.private_link_libraries, "PRIVATE");
|
||||
|
||||
target_cmd("target_link_options", t.link_options, target_scope);
|
||||
target_cmd("target_link_options", t.private_link_options, "PRIVATE");
|
||||
target_cmd("target_link_options", t.link_options, target_scope);
|
||||
target_cmd("target_link_options", t.private_link_options, "PRIVATE");
|
||||
|
||||
target_cmd("target_precompile_headers", t.precompile_headers, target_scope);
|
||||
target_cmd("target_precompile_headers", t.private_precompile_headers, "PRIVATE");
|
||||
target_cmd("target_precompile_headers", t.precompile_headers, target_scope);
|
||||
target_cmd("target_precompile_headers", t.private_precompile_headers, "PRIVATE");
|
||||
}
|
||||
|
||||
link_cmd("add_dependencies", t.dependencies, "");
|
||||
};
|
||||
|
||||
if (tmplate != nullptr) {
|
||||
|
||||
+389
-4
@@ -1,6 +1,7 @@
|
||||
#include "project_parser.hpp"
|
||||
|
||||
#include "fs.hpp"
|
||||
#include <cctype>
|
||||
#include <deque>
|
||||
#include <stdexcept>
|
||||
#include <toml.hpp>
|
||||
@@ -209,6 +210,105 @@ class TomlCheckerRoot {
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<std::string> parse_string_array(const TomlBasicValue &value, const toml::key &key) {
|
||||
if (!value.is_array()) {
|
||||
throw_key_error("Expected an array", key, value);
|
||||
}
|
||||
std::vector<std::string> values;
|
||||
for (const auto &entry : value.as_array()) {
|
||||
if (!entry.is_string()) {
|
||||
throw_key_error("Expected an array of strings", key, entry);
|
||||
}
|
||||
values.push_back(entry.as_string());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
static std::vector<std::vector<std::string>> parse_command_arguments(const TomlBasicValue &value, const toml::key &key) {
|
||||
if (!value.is_array()) {
|
||||
throw_key_error("Expected an array", key, value);
|
||||
}
|
||||
const auto &array = value.as_array();
|
||||
if (array.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool all_strings = true;
|
||||
bool all_arrays = true;
|
||||
for (const auto &entry : array) {
|
||||
if (!entry.is_string()) {
|
||||
all_strings = false;
|
||||
}
|
||||
if (!entry.is_array()) {
|
||||
all_arrays = false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::string>> commands;
|
||||
if (all_strings) {
|
||||
commands.push_back(parse_string_array(value, key));
|
||||
if (commands[0].empty()) {
|
||||
throw_key_error("Command arrays cannot be empty", key, value);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
if (!all_arrays) {
|
||||
throw_key_error("Expected an array of command arrays", key, value);
|
||||
}
|
||||
|
||||
for (const auto &entry : array) {
|
||||
auto command = parse_string_array(entry, key);
|
||||
if (command.empty()) {
|
||||
throw_key_error("Command arrays cannot be empty", key, entry);
|
||||
}
|
||||
commands.push_back(std::move(command));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
static std::vector<Target::CustomCommand::ImplicitDependency> parse_implicit_dependencies(const TomlBasicValue &value, const toml::key &key) {
|
||||
if (!value.is_array()) {
|
||||
throw_key_error("Expected an array", key, value);
|
||||
}
|
||||
std::vector<Target::CustomCommand::ImplicitDependency> implicit_dependencies;
|
||||
for (const auto &entry : value.as_array()) {
|
||||
auto values = parse_string_array(entry, key);
|
||||
if (values.size() != 2) {
|
||||
throw_key_error("Each implicit dependency must have exactly two strings: [language, file]", key, entry);
|
||||
}
|
||||
Target::CustomCommand::ImplicitDependency implicit_dependency;
|
||||
implicit_dependency.language = std::move(values[0]);
|
||||
implicit_dependency.file = std::move(values[1]);
|
||||
implicit_dependencies.push_back(std::move(implicit_dependency));
|
||||
}
|
||||
return implicit_dependencies;
|
||||
}
|
||||
|
||||
static std::string normalize_build_event(std::string build_event) {
|
||||
for (auto &ch : build_event) {
|
||||
if (ch == '-') {
|
||||
ch = '_';
|
||||
} else {
|
||||
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
|
||||
}
|
||||
}
|
||||
return build_event;
|
||||
}
|
||||
|
||||
static TargetType resolve_target_type(const Target &target, const std::vector<Template> &templates) {
|
||||
if (target.type != target_template) {
|
||||
return target.type;
|
||||
}
|
||||
for (const auto &tmplate : templates) {
|
||||
if (target.type_name == tmplate.outline.name) {
|
||||
return tmplate.outline.type;
|
||||
}
|
||||
}
|
||||
return target.type;
|
||||
}
|
||||
|
||||
Project::Project(const Project *parent, const std::string &path, bool build) : parent(parent) {
|
||||
const auto toml_path = fs::path(path) / "cmake.toml";
|
||||
if (!fs::exists(toml_path)) {
|
||||
@@ -301,6 +401,11 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
|
||||
project.required("name", project_name);
|
||||
project.optional("version", project_version);
|
||||
project.optional("description", project_description);
|
||||
|
||||
// The implicit default is ["C", "CXX"], so make sure this list isn't
|
||||
// empty or projects without languages explicitly defined will error.
|
||||
project_languages[""] = {"C", "CXX"};
|
||||
|
||||
project.optional("languages", project_languages);
|
||||
project.optional("allow-unknown-languages", project_allow_unknown_languages);
|
||||
project.optional("cmake-before", cmake_before);
|
||||
@@ -440,18 +545,19 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
|
||||
options.push_back(o);
|
||||
|
||||
// Add a condition matching the option name
|
||||
conditions.emplace(o.name, o.name);
|
||||
if (!conditions.emplace(o.name, o.name).second) {
|
||||
print_key_warning("Option '" + o.name + "' would create a condition '" + o.name + "' that already exists", o.name, value);
|
||||
}
|
||||
|
||||
// Add an implicit condition for the option
|
||||
auto ncondition = normalize(o.name);
|
||||
if (ncondition.find(nproject_prefix) == 0) {
|
||||
ncondition = ncondition.substr(nproject_prefix.size());
|
||||
}
|
||||
if (!ncondition.empty()) {
|
||||
if (conditions.contains(ncondition)) {
|
||||
if (!ncondition.empty() && ncondition != o.name) {
|
||||
if (!conditions.emplace(ncondition, o.name).second) {
|
||||
print_key_warning("Option '" + o.name + "' would create a condition '" + ncondition + "' that already exists", o.name, value);
|
||||
}
|
||||
conditions.emplace(ncondition, o.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,7 +605,37 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
|
||||
"system", "");
|
||||
}
|
||||
|
||||
// Parse options table - these are CACHE variables set before FetchContent
|
||||
if (c.contains("options")) {
|
||||
c.visit("options");
|
||||
const auto &opts = toml::find(itr.second, "options").as_table();
|
||||
for (const auto &optItr : opts) {
|
||||
ContentOption opt;
|
||||
if (optItr.second.is_boolean()) {
|
||||
opt.value = optItr.second.as_boolean();
|
||||
} else if (optItr.second.is_array()) {
|
||||
// Arrays become semicolon-separated lists
|
||||
std::string list_value;
|
||||
for (const auto &list_val : optItr.second.as_array()) {
|
||||
if (!list_value.empty()) {
|
||||
list_value += ';';
|
||||
}
|
||||
list_value += list_val.as_string();
|
||||
}
|
||||
opt.value = list_value;
|
||||
} else {
|
||||
opt.value = optItr.second.as_string();
|
||||
}
|
||||
content.options.emplace(optItr.first, opt);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &argItr : itr.second.as_table()) {
|
||||
// Skip keys that were already handled (like "options")
|
||||
if (c.visisted(argItr.first)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string value;
|
||||
if (argItr.second.is_array()) {
|
||||
for (const auto &list_val : argItr.second.as_array()) {
|
||||
@@ -690,6 +826,255 @@ Project::Project(const Project *parent, const std::string &path, bool build) : p
|
||||
t.optional("precompile-headers", target.precompile_headers);
|
||||
t.optional("private-precompile-headers", target.private_precompile_headers);
|
||||
|
||||
t.optional("dependencies", target.dependencies);
|
||||
|
||||
const auto resolved_target_type = resolve_target_type(target, templates);
|
||||
|
||||
if (t.contains("all")) {
|
||||
target.custom_target.has_all = true;
|
||||
target.custom_target.all = t.find("all").as_boolean();
|
||||
}
|
||||
|
||||
if (t.contains("command")) {
|
||||
target.custom_target.has_command = true;
|
||||
auto commands = parse_command_arguments(t.find("command"), "command");
|
||||
for (auto &command : commands) {
|
||||
target.custom_target.commands.push_back(std::move(command));
|
||||
}
|
||||
}
|
||||
|
||||
if (t.contains("commands")) {
|
||||
target.custom_target.has_commands = true;
|
||||
auto commands = parse_command_arguments(t.find("commands"), "commands");
|
||||
for (auto &command : commands) {
|
||||
target.custom_target.commands.push_back(std::move(command));
|
||||
}
|
||||
}
|
||||
|
||||
t.optional("depends", target.custom_target.depends);
|
||||
t.optional("byproducts", target.custom_target.byproducts);
|
||||
|
||||
if (t.contains("working-directory")) {
|
||||
target.custom_target.has_working_directory = true;
|
||||
target.custom_target.working_directory = t.find("working-directory").as_string();
|
||||
}
|
||||
|
||||
if (t.contains("comment")) {
|
||||
target.custom_target.has_comment = true;
|
||||
target.custom_target.comment = t.find("comment").as_string();
|
||||
}
|
||||
|
||||
if (t.contains("job-pool")) {
|
||||
target.custom_target.has_job_pool = true;
|
||||
target.custom_target.job_pool = t.find("job-pool").as_string();
|
||||
}
|
||||
|
||||
if (t.contains("job-server-aware")) {
|
||||
target.custom_target.has_job_server_aware = true;
|
||||
target.custom_target.job_server_aware = t.find("job-server-aware").as_boolean();
|
||||
}
|
||||
|
||||
if (t.contains("verbatim")) {
|
||||
target.custom_target.has_verbatim = true;
|
||||
target.custom_target.verbatim = t.find("verbatim").as_boolean();
|
||||
}
|
||||
|
||||
if (t.contains("uses-terminal")) {
|
||||
target.custom_target.has_uses_terminal = true;
|
||||
target.custom_target.uses_terminal = t.find("uses-terminal").as_boolean();
|
||||
}
|
||||
|
||||
if (t.contains("command-expand-lists")) {
|
||||
target.custom_target.has_command_expand_lists = true;
|
||||
target.custom_target.command_expand_lists = t.find("command-expand-lists").as_boolean();
|
||||
}
|
||||
|
||||
if (t.contains("custom-command")) {
|
||||
const auto &custom_commands = t.find("custom-command");
|
||||
if (!custom_commands.is_array()) {
|
||||
throw_key_error("Expected an array of tables", "custom-command", custom_commands);
|
||||
}
|
||||
for (const auto &custom_command_value : custom_commands.as_array()) {
|
||||
if (!custom_command_value.is_table()) {
|
||||
throw_key_error("Expected an array of tables", "custom-command", custom_command_value);
|
||||
}
|
||||
|
||||
auto &custom = checker.create(custom_command_value);
|
||||
Target::CustomCommand custom_command;
|
||||
|
||||
custom.optional("condition", custom_command.condition);
|
||||
|
||||
if (custom.contains("command")) {
|
||||
auto commands = parse_command_arguments(custom.find("command"), "command");
|
||||
for (auto &command : commands) {
|
||||
custom_command.commands.push_back(std::move(command));
|
||||
}
|
||||
}
|
||||
|
||||
if (custom.contains("commands")) {
|
||||
auto commands = parse_command_arguments(custom.find("commands"), "commands");
|
||||
for (auto &command : commands) {
|
||||
custom_command.commands.push_back(std::move(command));
|
||||
}
|
||||
}
|
||||
|
||||
custom.optional("outputs", custom_command.outputs);
|
||||
|
||||
if (custom.contains("build-event")) {
|
||||
custom_command.build_event = normalize_build_event(custom.find("build-event").as_string());
|
||||
}
|
||||
|
||||
if (custom.contains("append")) {
|
||||
custom_command.has_append = true;
|
||||
custom_command.append = custom.find("append").as_boolean();
|
||||
}
|
||||
|
||||
if (custom.contains("main-dependency")) {
|
||||
custom_command.has_main_dependency = true;
|
||||
custom_command.main_dependency = custom.find("main-dependency").as_string();
|
||||
}
|
||||
|
||||
custom.optional("depends", custom_command.depends);
|
||||
custom.optional("byproducts", custom_command.byproducts);
|
||||
|
||||
if (custom.contains("implicit-depends")) {
|
||||
custom_command.implicit_depends = parse_implicit_dependencies(custom.find("implicit-depends"), "implicit-depends");
|
||||
}
|
||||
|
||||
if (custom.contains("working-directory")) {
|
||||
custom_command.has_working_directory = true;
|
||||
custom_command.working_directory = custom.find("working-directory").as_string();
|
||||
}
|
||||
|
||||
if (custom.contains("comment")) {
|
||||
custom_command.has_comment = true;
|
||||
custom_command.comment = custom.find("comment").as_string();
|
||||
}
|
||||
|
||||
if (custom.contains("depfile")) {
|
||||
custom_command.has_depfile = true;
|
||||
custom_command.depfile = custom.find("depfile").as_string();
|
||||
}
|
||||
|
||||
if (custom.contains("job-pool")) {
|
||||
custom_command.has_job_pool = true;
|
||||
custom_command.job_pool = custom.find("job-pool").as_string();
|
||||
}
|
||||
|
||||
if (custom.contains("job-server-aware")) {
|
||||
custom_command.has_job_server_aware = true;
|
||||
custom_command.job_server_aware = custom.find("job-server-aware").as_boolean();
|
||||
}
|
||||
|
||||
if (custom.contains("verbatim")) {
|
||||
custom_command.has_verbatim = true;
|
||||
custom_command.verbatim = custom.find("verbatim").as_boolean();
|
||||
}
|
||||
|
||||
if (custom.contains("uses-terminal")) {
|
||||
custom_command.has_uses_terminal = true;
|
||||
custom_command.uses_terminal = custom.find("uses-terminal").as_boolean();
|
||||
}
|
||||
|
||||
if (custom.contains("codegen")) {
|
||||
custom_command.has_codegen = true;
|
||||
custom_command.codegen = custom.find("codegen").as_boolean();
|
||||
}
|
||||
|
||||
if (custom.contains("command-expand-lists")) {
|
||||
custom_command.has_command_expand_lists = true;
|
||||
custom_command.command_expand_lists = custom.find("command-expand-lists").as_boolean();
|
||||
}
|
||||
|
||||
if (custom.contains("depends-explicit-only")) {
|
||||
custom_command.has_depends_explicit_only = true;
|
||||
custom_command.depends_explicit_only = custom.find("depends-explicit-only").as_boolean();
|
||||
}
|
||||
|
||||
if (custom_command.is_output_form() == custom_command.is_target_form()) {
|
||||
throw_key_error("Specify exactly one of outputs or build-event", "custom-command", custom_command_value);
|
||||
}
|
||||
|
||||
if (custom_command.is_target_form()) {
|
||||
if (custom_command.build_event != "PRE_BUILD" && custom_command.build_event != "PRE_LINK" &&
|
||||
custom_command.build_event != "POST_BUILD") {
|
||||
throw_key_error("build-event must be one of: pre-build, pre-link, post-build", "build-event", custom.find("build-event"));
|
||||
}
|
||||
if (resolved_target_type == target_interface) {
|
||||
throw_key_error("build-event cannot be used with type = \"interface\"", "build-event", custom.find("build-event"));
|
||||
}
|
||||
if (resolved_target_type == target_custom && custom_command.build_event == "PRE_LINK") {
|
||||
throw_key_error("build-event = \"pre-link\" cannot be used with type = \"custom\"", "build-event",
|
||||
custom.find("build-event"));
|
||||
}
|
||||
if (custom_command.has_append || custom_command.has_main_dependency || !custom_command.depends.empty() ||
|
||||
!custom_command.implicit_depends.empty() || custom_command.has_depfile || custom_command.has_job_pool ||
|
||||
custom_command.has_job_server_aware || custom_command.has_codegen || custom_command.has_depends_explicit_only) {
|
||||
throw_key_error("Unsupported option for TARGET custom commands", "custom-command", custom_command_value);
|
||||
}
|
||||
} else {
|
||||
if (custom_command.has_depfile && !custom_command.implicit_depends.empty()) {
|
||||
throw_key_error("depfile cannot be used with implicit-depends", "depfile", custom.find("depfile"));
|
||||
}
|
||||
if (custom_command.has_job_pool && custom_command.has_uses_terminal && custom_command.uses_terminal) {
|
||||
throw_key_error("job-pool cannot be used with uses-terminal", "job-pool", custom.find("job-pool"));
|
||||
}
|
||||
if (custom_command.append &&
|
||||
(custom_command.has_depfile || custom_command.has_job_pool || custom_command.has_job_server_aware ||
|
||||
custom_command.has_codegen || custom_command.has_command_expand_lists || custom_command.has_uses_terminal ||
|
||||
custom_command.has_verbatim || custom_command.has_depends_explicit_only || !custom_command.byproducts.empty())) {
|
||||
throw_key_error("append cannot be used with depfile, job-pool, job-server-aware, codegen, command-expand-lists, "
|
||||
"uses-terminal, verbatim, depends-explicit-only, or byproducts",
|
||||
"append", custom.find("append"));
|
||||
}
|
||||
}
|
||||
|
||||
if (custom_command.is_output_form() && custom_command.append && custom_command.commands.empty() && custom_command.depends.empty()) {
|
||||
throw_key_error("append requires at least one command or depends", "append", custom.find("append"));
|
||||
}
|
||||
|
||||
if (custom_command.commands.empty() &&
|
||||
!(custom_command.is_output_form() && custom_command.append && !custom_command.depends.empty())) {
|
||||
throw_key_error("At least one command is required", "custom-command", custom_command_value);
|
||||
}
|
||||
|
||||
target.custom_commands.push_back(std::move(custom_command));
|
||||
}
|
||||
}
|
||||
|
||||
if (resolved_target_type == target_custom && target.custom_target.has_job_pool && target.custom_target.has_uses_terminal &&
|
||||
target.custom_target.uses_terminal) {
|
||||
throw_key_error("job-pool cannot be used with uses-terminal", "job-pool", t.find("job-pool"));
|
||||
}
|
||||
|
||||
if (resolved_target_type != target_custom && !target.custom_target.empty()) {
|
||||
const char *custom_key = "all";
|
||||
if (target.custom_target.has_command) {
|
||||
custom_key = "command";
|
||||
} else if (target.custom_target.has_commands) {
|
||||
custom_key = "commands";
|
||||
} else if (!target.custom_target.depends.empty()) {
|
||||
custom_key = "depends";
|
||||
} else if (!target.custom_target.byproducts.empty()) {
|
||||
custom_key = "byproducts";
|
||||
} else if (target.custom_target.has_working_directory) {
|
||||
custom_key = "working-directory";
|
||||
} else if (target.custom_target.has_comment) {
|
||||
custom_key = "comment";
|
||||
} else if (target.custom_target.has_job_pool) {
|
||||
custom_key = "job-pool";
|
||||
} else if (target.custom_target.has_job_server_aware) {
|
||||
custom_key = "job-server-aware";
|
||||
} else if (target.custom_target.has_verbatim) {
|
||||
custom_key = "verbatim";
|
||||
} else if (target.custom_target.has_uses_terminal) {
|
||||
custom_key = "uses-terminal";
|
||||
} else if (target.custom_target.has_command_expand_lists) {
|
||||
custom_key = "command-expand-lists";
|
||||
}
|
||||
throw_key_error("Custom target options can only be used with type = \"custom\"", custom_key, t.find(custom_key));
|
||||
}
|
||||
|
||||
Condition<std::string> msvc_runtime;
|
||||
t.optional("msvc-runtime", msvc_runtime);
|
||||
for (const auto &cond_itr : msvc_runtime) {
|
||||
|
||||
Generated
+39
@@ -122,3 +122,42 @@ if(WIN32) # windows
|
||||
)
|
||||
|
||||
endif()
|
||||
add_test(
|
||||
NAME
|
||||
objective-c
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/objective-c"
|
||||
COMMAND
|
||||
"$<TARGET_FILE:cmkr>"
|
||||
build
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
custom-command
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/custom-command"
|
||||
COMMAND
|
||||
"$<TARGET_FILE:cmkr>"
|
||||
build
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
generator-executable
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/generator-executable"
|
||||
COMMAND
|
||||
"$<TARGET_FILE:cmkr>"
|
||||
build
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME
|
||||
protobuf
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_LIST_DIR}/protobuf"
|
||||
COMMAND
|
||||
"$<TARGET_FILE:cmkr>"
|
||||
build
|
||||
)
|
||||
|
||||
@@ -65,3 +65,27 @@ name = "relative-paths"
|
||||
working-directory = "relative-paths"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "objective-c"
|
||||
working-directory = "objective-c"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "custom-command"
|
||||
working-directory = "custom-command"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "generator-executable"
|
||||
working-directory = "generator-executable"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
[[test]]
|
||||
name = "protobuf"
|
||||
working-directory = "protobuf"
|
||||
command = "$<TARGET_FILE:cmkr>"
|
||||
arguments = ["build"]
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# This test demonstrates add_custom_command and add_custom_target support in cmkr.
|
||||
#
|
||||
# There are two forms of add_custom_command in CMake:
|
||||
# 1. Output form: generates files that can be consumed by other targets
|
||||
# 2. Target form: runs commands at build time for a specific target (pre-build, pre-link, post-build)
|
||||
|
||||
[project]
|
||||
name = "custom-command"
|
||||
description = "Tests add_custom_command and add_custom_target support"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Simple executable demonstrating post-build events
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[target.hello]
|
||||
type = "executable"
|
||||
sources = ["src/hello.cpp"]
|
||||
|
||||
# This post-build command runs after the 'hello' executable is built.
|
||||
# build-event can be: "pre-build", "pre-link", or "post-build"
|
||||
[[target.hello.custom-command]]
|
||||
build-event = "post-build"
|
||||
command = ["${CMAKE_COMMAND}", "-E", "echo", "Built executable: $<TARGET_FILE_NAME:hello>"]
|
||||
comment = "Print the built executable name"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Executable with code generation (output form custom command)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[target.custom-command]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
include-directories = ["${CMAKE_CURRENT_BINARY_DIR}/generated"]
|
||||
|
||||
# Output form: This custom command generates source files before building.
|
||||
# Relative outputs follow CMake and are interpreted from the binary directory.
|
||||
# The outputs are automatically added as sources to the target.
|
||||
[[target.custom-command.custom-command]]
|
||||
outputs = ["generated/generated.cpp"]
|
||||
byproducts = ["generated/generated.hpp"]
|
||||
depends = ["cmake/generate_source.cmake"]
|
||||
command = [
|
||||
"${CMAKE_COMMAND}",
|
||||
"-DOUTPUT_CPP=${CMAKE_CURRENT_BINARY_DIR}/generated/generated.cpp",
|
||||
"-DOUTPUT_HPP=${CMAKE_CURRENT_BINARY_DIR}/generated/generated.hpp",
|
||||
"-P",
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/generate_source.cmake",
|
||||
]
|
||||
comment = "Generate source files"
|
||||
verbatim = true
|
||||
|
||||
# Target form: This post-build command runs after 'custom-command' is built.
|
||||
[[target.custom-command.custom-command]]
|
||||
build-event = "post-build"
|
||||
command = [
|
||||
"${CMAKE_COMMAND}",
|
||||
"-E",
|
||||
"touch",
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/custom-command-post-build.stamp",
|
||||
]
|
||||
byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/custom-command-post-build.stamp"]
|
||||
comment = "Create a post-build stamp file"
|
||||
verbatim = true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Custom target (add_custom_target)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# A custom target runs commands independently of any executable/library.
|
||||
# Setting 'all = true' makes it run as part of the default build.
|
||||
[target.custom-codegen]
|
||||
type = "custom"
|
||||
all = true
|
||||
command = [
|
||||
"${CMAKE_COMMAND}",
|
||||
"-E",
|
||||
"touch",
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/custom-target.stamp",
|
||||
]
|
||||
byproducts = ["${CMAKE_CURRENT_BINARY_DIR}/custom-target.stamp"]
|
||||
comment = "Run custom target"
|
||||
verbatim = true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Template-based custom target
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Custom target options should also work when the target type comes from a template.
|
||||
[template.generated-custom-target]
|
||||
type = "custom"
|
||||
all = true
|
||||
verbatim = true
|
||||
|
||||
[target.custom-codegen-template]
|
||||
type = "generated-custom-target"
|
||||
command = [
|
||||
"${CMAKE_COMMAND}",
|
||||
"-DINPUT=${CMAKE_CURRENT_BINARY_DIR}/template-custom-target.stamp",
|
||||
"-P",
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_file_exists.cmake",
|
||||
]
|
||||
comment = "Verify the generated stamp exists"
|
||||
|
||||
# The output-form custom command should automatically become a dependency of the
|
||||
# custom target, so the verification command sees the stamp file.
|
||||
[[target.custom-codegen-template.custom-command]]
|
||||
outputs = ["template-custom-target.stamp"]
|
||||
command = [
|
||||
"${CMAKE_COMMAND}",
|
||||
"-E",
|
||||
"touch",
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/template-custom-target.stamp",
|
||||
]
|
||||
comment = "Create a stamp file for the template-based custom target"
|
||||
verbatim = true
|
||||
|
||||
# A target-level all = false should override the template's all = true.
|
||||
# If this target is incorrectly included in the default build, the build fails.
|
||||
[target.custom-codegen-template-disabled]
|
||||
type = "generated-custom-target"
|
||||
all = false
|
||||
command = ["${CMAKE_COMMAND}", "-E", "false"]
|
||||
comment = "This target must not run as part of the default build"
|
||||
@@ -0,0 +1,13 @@
|
||||
if(NOT DEFINED OUTPUT_CPP)
|
||||
message(FATAL_ERROR "OUTPUT_CPP is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED OUTPUT_HPP)
|
||||
message(FATAL_ERROR "OUTPUT_HPP is required")
|
||||
endif()
|
||||
|
||||
get_filename_component(_output_dir "${OUTPUT_CPP}" DIRECTORY)
|
||||
file(MAKE_DIRECTORY "${_output_dir}")
|
||||
|
||||
file(WRITE "${OUTPUT_HPP}" "#pragma once\n#define GENERATED_VALUE 42\n")
|
||||
file(WRITE "${OUTPUT_CPP}" "#include \"generated.hpp\"\nint generated_value() { return GENERATED_VALUE; }\n")
|
||||
@@ -0,0 +1,9 @@
|
||||
if(NOT DEFINED INPUT)
|
||||
message(FATAL_ERROR "INPUT is required")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${INPUT}")
|
||||
message(FATAL_ERROR "Expected file not found: ${INPUT}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Verified file exists: ${INPUT}")
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Hello, world!" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "generated.hpp"
|
||||
|
||||
int generated_value();
|
||||
|
||||
int main() {
|
||||
return generated_value() == GENERATED_VALUE ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# This test demonstrates a common pattern: using an executable to generate sources
|
||||
# for another target. The generator runs at build time and produces code that is
|
||||
# consumed by the main executable.
|
||||
#
|
||||
# CMake handles the dependency automatically: the generator executable is built
|
||||
# first, then it runs to produce the generated sources, and finally the main
|
||||
# executable is built using those sources.
|
||||
|
||||
[project]
|
||||
name = "generator-executable"
|
||||
description = "Tests using an executable to generate sources for another target"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Generator executable: produces code at build time
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[target.generate_numbers]
|
||||
type = "executable"
|
||||
sources = ["src/generate_numbers.cpp"]
|
||||
|
||||
# Post-build: confirm the generator was built
|
||||
[[target.generate_numbers.custom-command]]
|
||||
build-event = "post-build"
|
||||
command = ["${CMAKE_COMMAND}", "-E", "echo", "Generator built successfully: $<TARGET_FILE_NAME:generate_numbers>"]
|
||||
comment = "Confirm generator executable was built"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Main executable: uses generated sources
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[target.main]
|
||||
type = "executable"
|
||||
sources = ["src/main.cpp"]
|
||||
include-directories = ["${CMAKE_CURRENT_BINARY_DIR}/generated"]
|
||||
|
||||
# Create the generated directory before the generator runs.
|
||||
cmake-before = """
|
||||
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
"""
|
||||
|
||||
# Output form custom command: runs the generate_numbers executable to generate sources.
|
||||
# The outputs are automatically added as sources to this target.
|
||||
# The DEPENDS on "generate_numbers" ensures the generator is built before this runs.
|
||||
[[target.main.custom-command]]
|
||||
outputs = ["${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"]
|
||||
depends = ["generate_numbers"]
|
||||
command = ["$<TARGET_FILE:generate_numbers>", "${CMAKE_CURRENT_BINARY_DIR}/generated/numbers.cpp"]
|
||||
comment = "Run generate_numbers to generate numbers.cpp"
|
||||
|
||||
# Post-build: run the executable to verify it works
|
||||
[[target.main.custom-command]]
|
||||
build-event = "post-build"
|
||||
command = ["$<TARGET_FILE:main>"]
|
||||
comment = "Run the main executable to verify generated code works"
|
||||
@@ -0,0 +1,44 @@
|
||||
// Simple code generator that produces a C++ source file with a function
|
||||
// that returns a vector of numbers.
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc != 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <output_file>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string output_path = argv[1];
|
||||
|
||||
// The example ensures the output directory exists before invoking the generator.
|
||||
|
||||
std::ofstream out(output_path);
|
||||
if (!out) {
|
||||
std::cerr << "Failed to open: " << output_path << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
out << "// Auto-generated by codegen - DO NOT EDIT\n";
|
||||
out << "#include <vector>\n";
|
||||
out << "#include <cstddef>\n";
|
||||
out << "\n";
|
||||
out << "std::vector<int> get_numbers() {\n";
|
||||
out << " return {";
|
||||
for (int i = 1; i <= 10; ++i) {
|
||||
if (i > 1)
|
||||
out << ", ";
|
||||
out << i * i; // 1, 4, 9, 16, 25, 36, 49, 64, 81, 100
|
||||
}
|
||||
out << "};\n";
|
||||
out << "}\n";
|
||||
out << "\n";
|
||||
out << "std::size_t get_numbers_count() {\n";
|
||||
out << " return 10;\n";
|
||||
out << "}\n";
|
||||
|
||||
std::cout << "Generated: " << output_path << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Main executable that uses the generated code
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
// Functions generated by codegen executable
|
||||
std::vector<int> get_numbers();
|
||||
std::size_t get_numbers_count();
|
||||
|
||||
int main() {
|
||||
std::cout << "Numbers from generated code:" << std::endl;
|
||||
|
||||
auto numbers = get_numbers();
|
||||
for (std::size_t i = 0; i < numbers.size(); ++i) {
|
||||
std::cout << " numbers[" << i << "] = " << numbers[i] << std::endl;
|
||||
}
|
||||
|
||||
// Verify the count
|
||||
if (numbers.size() == get_numbers_count()) {
|
||||
std::cout << "Success: Generated " << numbers.size() << " numbers!" << std::endl;
|
||||
return 0;
|
||||
} else {
|
||||
std::cerr << "Error: Count mismatch!" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Add Objective-C sources on Apple platforms:
|
||||
|
||||
[project]
|
||||
name = "objective-c"
|
||||
description = "Objective-C"
|
||||
languages = ["C"]
|
||||
apple.languages = ["OBJC"]
|
||||
|
||||
[target.hello]
|
||||
type = "executable"
|
||||
sources = ["src/main.c"]
|
||||
apple.sources = ["src/apple.m"]
|
||||
apple.link-libraries = ["$<LINK_LIBRARY:FRAMEWORK,Foundation>"]
|
||||
@@ -0,0 +1,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void platform_specific() {
|
||||
NSFileHandle *stdout = [NSFileHandle fileHandleWithStandardOutput];
|
||||
NSString *message = @"Hello from Objective-C!\n";
|
||||
[stdout writeData:[message dataUsingEncoding:NSUTF8StringEncoding] error:nil];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
void platform_specific();
|
||||
#else
|
||||
void platform_specific() {
|
||||
puts("This is not an Apple platform.");
|
||||
}
|
||||
#endif
|
||||
|
||||
int main() {
|
||||
platform_specific();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
*.bin
|
||||
@@ -0,0 +1,81 @@
|
||||
# This test demonstrates using protobuf with cmkr for code generation.
|
||||
#
|
||||
# The example:
|
||||
# 1. Fetches protobuf from GitHub using FetchContent
|
||||
# 2. Uses a custom command to generate C++ sources from a .proto file
|
||||
# 3. Links an executable against the generated sources and protobuf library
|
||||
# 4. Serializes and deserializes a simple message
|
||||
#
|
||||
# Note: protobuf v21.x is used because newer versions (v22+) require abseil-cpp
|
||||
# as a dependency, which significantly complicates the build.
|
||||
|
||||
[cmake]
|
||||
version = "3.18...3.31"
|
||||
|
||||
[project]
|
||||
name = "protobuf-example"
|
||||
description = "Demonstrates protobuf integration with cmkr custom commands"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Fetch protobuf from GitHub
|
||||
# Using v21.12 (last version before abseil-cpp became required)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[fetch-content.protobuf]
|
||||
git = "https://github.com/protocolbuffers/protobuf"
|
||||
tag = "v21.12"
|
||||
options = {
|
||||
protobuf_BUILD_TESTS = false,
|
||||
protobuf_BUILD_EXAMPLES = false,
|
||||
protobuf_BUILD_LIBPROTOC = false,
|
||||
protobuf_BUILD_SHARED_LIBS = false,
|
||||
protobuf_MSVC_STATIC_RUNTIME = false,
|
||||
SKIP_INSTALL_ALL = true,
|
||||
protobuf_WITH_ZLIB = false,
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Main executable using protobuf
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
[target.protobuf_example]
|
||||
type = "executable"
|
||||
sources = [
|
||||
"src/main.cpp",
|
||||
]
|
||||
include-directories = [
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated",
|
||||
]
|
||||
link-libraries = ["protobuf::libprotobuf"]
|
||||
compile-features = ["cxx_std_17"]
|
||||
|
||||
# Create the generated directory before running protoc
|
||||
cmake-before = """
|
||||
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
"""
|
||||
|
||||
# Custom command to generate C++ sources from .proto file
|
||||
# This uses protoc from the fetched protobuf repository
|
||||
[[target.protobuf_example.custom-command]]
|
||||
outputs = [
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated/addressbook.pb.h",
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated/addressbook.pb.cc",
|
||||
]
|
||||
depends = [
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/proto/addressbook.proto",
|
||||
"protobuf::protoc", # Ensure protoc is built first
|
||||
]
|
||||
command = [
|
||||
"$<TARGET_FILE:protobuf::protoc>",
|
||||
"--cpp_out=${CMAKE_CURRENT_BINARY_DIR}/generated",
|
||||
"-I${CMAKE_CURRENT_SOURCE_DIR}/proto",
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/proto/addressbook.proto",
|
||||
]
|
||||
comment = "Generate C++ sources from addressbook.proto"
|
||||
verbatim = true
|
||||
|
||||
# Post-build: run the executable to verify it works
|
||||
[[target.protobuf_example.custom-command]]
|
||||
build-event = "post-build"
|
||||
command = ["$<TARGET_FILE:protobuf_example>"]
|
||||
comment = "Run protobuf_example to verify serialization/deserialization works"
|
||||
@@ -0,0 +1,31 @@
|
||||
// A simple address book example demonstrating protobuf serialization.
|
||||
// Based on the official protobuf tutorial example.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package tutorial;
|
||||
|
||||
// A person with basic contact information
|
||||
message Person {
|
||||
string name = 1;
|
||||
int32 id = 2;
|
||||
string email = 3;
|
||||
|
||||
enum PhoneType {
|
||||
MOBILE = 0;
|
||||
HOME = 1;
|
||||
WORK = 2;
|
||||
}
|
||||
|
||||
message PhoneNumber {
|
||||
string number = 1;
|
||||
PhoneType type = 2;
|
||||
}
|
||||
|
||||
repeated PhoneNumber phones = 4;
|
||||
}
|
||||
|
||||
// An address book containing multiple people
|
||||
message AddressBook {
|
||||
repeated Person people = 1;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Protobuf serialization/deserialization example
|
||||
// This demonstrates using protobuf with cmkr for code generation
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include "addressbook.pb.h"
|
||||
|
||||
// Creates a sample address book with some people
|
||||
void create_address_book(tutorial::AddressBook &book) {
|
||||
// Add first person
|
||||
tutorial::Person *person1 = book.add_people();
|
||||
person1->set_name("Alice Smith");
|
||||
person1->set_id(1);
|
||||
person1->set_email("alice@example.com");
|
||||
|
||||
// Add phone numbers for Alice
|
||||
tutorial::Person::PhoneNumber *phone1 = person1->add_phones();
|
||||
phone1->set_number("555-1234");
|
||||
phone1->set_type(tutorial::Person::HOME);
|
||||
|
||||
tutorial::Person::PhoneNumber *phone2 = person1->add_phones();
|
||||
phone2->set_number("555-5678");
|
||||
phone2->set_type(tutorial::Person::WORK);
|
||||
|
||||
// Add second person
|
||||
tutorial::Person *person2 = book.add_people();
|
||||
person2->set_name("Bob Johnson");
|
||||
person2->set_id(2);
|
||||
person2->set_email("bob@example.com");
|
||||
|
||||
tutorial::Person::PhoneNumber *phone3 = person2->add_phones();
|
||||
phone3->set_number("555-9999");
|
||||
phone3->set_type(tutorial::Person::MOBILE);
|
||||
}
|
||||
|
||||
// Prints the contents of an address book
|
||||
void print_address_book(const tutorial::AddressBook &book) {
|
||||
std::cout << "Address Book (" << book.people_size() << " people):\n";
|
||||
std::cout << "==========================================\n\n";
|
||||
|
||||
for (int i = 0; i < book.people_size(); ++i) {
|
||||
const tutorial::Person &person = book.people(i);
|
||||
|
||||
std::cout << "Person ID: " << person.id() << "\n";
|
||||
std::cout << " Name: " << person.name() << "\n";
|
||||
std::cout << " Email: " << person.email() << "\n";
|
||||
|
||||
for (int j = 0; j < person.phones_size(); ++j) {
|
||||
const tutorial::Person::PhoneNumber &phone = person.phones(j);
|
||||
std::cout << " Phone #" << (j + 1) << ": " << phone.number() << " (";
|
||||
|
||||
switch (phone.type()) {
|
||||
case tutorial::Person::MOBILE:
|
||||
std::cout << "mobile";
|
||||
break;
|
||||
case tutorial::Person::HOME:
|
||||
std::cout << "home";
|
||||
break;
|
||||
case tutorial::Person::WORK:
|
||||
std::cout << "work";
|
||||
break;
|
||||
default:
|
||||
std::cout << "unknown";
|
||||
}
|
||||
std::cout << ")\n";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Verify that the version of the library that we linked against is
|
||||
// compatible with the version of the headers we compiled against.
|
||||
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
||||
|
||||
std::cout << "Protobuf cmkr Example\n";
|
||||
std::cout << "=====================\n\n";
|
||||
|
||||
// Create an address book
|
||||
tutorial::AddressBook original_book;
|
||||
create_address_book(original_book);
|
||||
|
||||
std::cout << "Original Address Book:\n";
|
||||
print_address_book(original_book);
|
||||
|
||||
// Serialize to string
|
||||
std::string serialized;
|
||||
if (!original_book.SerializeToString(&serialized)) {
|
||||
std::cerr << "Error: Failed to serialize address book\n";
|
||||
return 1;
|
||||
}
|
||||
std::cout << "Serialized size: " << serialized.size() << " bytes\n\n";
|
||||
|
||||
// Deserialize into a new object
|
||||
tutorial::AddressBook deserialized_book;
|
||||
if (!deserialized_book.ParseFromString(serialized)) {
|
||||
std::cerr << "Error: Failed to deserialize address book\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Deserialized Address Book:\n";
|
||||
print_address_book(deserialized_book);
|
||||
|
||||
// Write to file
|
||||
const char *filename = "addressbook.bin";
|
||||
{
|
||||
std::ofstream output(filename, std::ios::binary);
|
||||
if (!original_book.SerializeToOstream(&output)) {
|
||||
std::cerr << "Error: Failed to write to file\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::cout << "Written to file: " << filename << "\n";
|
||||
|
||||
// Read back from file
|
||||
tutorial::AddressBook file_book;
|
||||
{
|
||||
std::ifstream input(filename, std::ios::binary);
|
||||
if (!file_book.ParseFromIstream(&input)) {
|
||||
std::cerr << "Error: Failed to read from file\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::cout << "Read back from file successfully!\n\n";
|
||||
|
||||
// Verify all three versions are identical
|
||||
bool all_match = true;
|
||||
if (original_book.SerializeAsString() != deserialized_book.SerializeAsString()) {
|
||||
std::cerr << "Error: Original and deserialized don't match!\n";
|
||||
all_match = false;
|
||||
}
|
||||
if (original_book.SerializeAsString() != file_book.SerializeAsString()) {
|
||||
std::cerr << "Error: Original and file don't match!\n";
|
||||
all_match = false;
|
||||
}
|
||||
|
||||
if (all_match) {
|
||||
std::cout << "SUCCESS: All serialization methods produced identical results!\n";
|
||||
}
|
||||
|
||||
// Clean up any global objects allocated by protobuf
|
||||
google::protobuf::ShutdownProtobufLibrary();
|
||||
|
||||
return all_match ? 0 : 1;
|
||||
}
|
||||
@@ -8,11 +8,11 @@ description = "Target templates"
|
||||
type = "executable"
|
||||
sources = ["src/templates.cpp"]
|
||||
compile-definitions = ["IS_APP"]
|
||||
|
||||
# Unlike interface targets you can also inherit properties
|
||||
[template.app.properties]
|
||||
CXX_STANDARD = "11"
|
||||
CXX_STANDARD_REQUIRED = true
|
||||
properties = {
|
||||
CXX_STANDARD = "11",
|
||||
CXX_STANDARD_REQUIRED = true,
|
||||
}
|
||||
|
||||
[target.app-a]
|
||||
type = "app"
|
||||
|
||||
+4
-3
@@ -9,9 +9,10 @@ add_library(ordered_map INTERFACE)
|
||||
target_include_directories(ordered_map INTERFACE ordered-map-1.0.0/include)
|
||||
target_compile_definitions(ordered_map INTERFACE NOMINMAX)
|
||||
|
||||
# https://github.com/ToruNiina/toml11 (MIT)
|
||||
add_library(toml11 INTERFACE)
|
||||
target_include_directories(toml11 INTERFACE toml11-3.6.0)
|
||||
# https://github.com/ToruNiina/toml11 (MIT)
|
||||
add_library(toml11 INTERFACE)
|
||||
target_include_directories(toml11 INTERFACE toml11-3.8.1)
|
||||
target_compile_definitions(toml11 INTERFACE TOML11_USE_UNRELEASED_TOML_FEATURES)
|
||||
|
||||
# https://github.com/mpark/variant (BSL-1.0)
|
||||
add_library(mpark_variant INTERFACE)
|
||||
|
||||
@@ -25,21 +25,14 @@
|
||||
#ifndef TOML_FOR_MODERN_CPP
|
||||
#define TOML_FOR_MODERN_CPP
|
||||
|
||||
#ifndef __cplusplus
|
||||
# error "__cplusplus is not defined"
|
||||
#endif
|
||||
|
||||
#if __cplusplus < 201103L && _MSC_VER < 1900
|
||||
# error "toml11 requires C++11 or later."
|
||||
#endif
|
||||
|
||||
#define TOML11_VERSION_MAJOR 3
|
||||
#define TOML11_VERSION_MINOR 5
|
||||
#define TOML11_VERSION_PATCH 0
|
||||
#define TOML11_VERSION_MINOR 8
|
||||
#define TOML11_VERSION_PATCH 1
|
||||
|
||||
#include "toml/parser.hpp"
|
||||
#include "toml/literal.hpp"
|
||||
#include "toml/serializer.hpp"
|
||||
#include "toml/get.hpp"
|
||||
#include "toml/macros.hpp"
|
||||
|
||||
#endif// TOML_FOR_MODERN_CPP
|
||||
+45
@@ -17,11 +17,41 @@ namespace color_ansi
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
|
||||
inline int colorize_index()
|
||||
{
|
||||
static const int index = std::ios_base::xalloc();
|
||||
return index;
|
||||
}
|
||||
|
||||
// Control color mode globally
|
||||
class color_mode
|
||||
{
|
||||
public:
|
||||
inline void enable()
|
||||
{
|
||||
should_color_ = true;
|
||||
}
|
||||
inline void disable()
|
||||
{
|
||||
should_color_ = false;
|
||||
}
|
||||
|
||||
inline bool should_color() const
|
||||
{
|
||||
return should_color_;
|
||||
}
|
||||
|
||||
static color_mode& status()
|
||||
{
|
||||
static color_mode status_;
|
||||
return status_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool should_color_ = false;
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
inline std::ostream& colorize(std::ostream& os)
|
||||
@@ -55,6 +85,21 @@ inline std::ostream& cyan (std::ostream& os)
|
||||
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[36m";} return os;}
|
||||
inline std::ostream& white (std::ostream& os)
|
||||
{if(os.iword(detail::colorize_index()) == 1) {os << "\033[37m";} return os;}
|
||||
|
||||
inline void enable()
|
||||
{
|
||||
return detail::color_mode::status().enable();
|
||||
}
|
||||
inline void disable()
|
||||
{
|
||||
return detail::color_mode::status().disable();
|
||||
}
|
||||
|
||||
inline bool should_color()
|
||||
{
|
||||
return detail::color_mode::status().should_color();
|
||||
}
|
||||
|
||||
} // color_ansi
|
||||
|
||||
// ANSI escape sequence is the only and default colorization method currently
|
||||
Vendored
+2
-2
@@ -29,7 +29,7 @@ namespace detail
|
||||
// to output character as an error message.
|
||||
inline std::string show_char(const char c)
|
||||
{
|
||||
// It supress an error that occurs only in Debug mode of MSVC++ on Windows.
|
||||
// It suppresses an error that occurs only in Debug mode of MSVC++ on Windows.
|
||||
// I'm not completely sure but they check the value of char to be in the
|
||||
// range [0, 256) and some of the COMPLETELY VALID utf-8 character sometimes
|
||||
// has negative value (if char has sign). So here it re-interprets c as
|
||||
@@ -154,7 +154,7 @@ struct sequence<Head, Tail...>
|
||||
invoke(location& loc)
|
||||
{
|
||||
const auto first = loc.iter();
|
||||
const auto rslt = Head::invoke(loc);
|
||||
auto rslt = Head::invoke(loc);
|
||||
if(rslt.is_err())
|
||||
{
|
||||
loc.reset(first);
|
||||
+26
-7
@@ -4,11 +4,18 @@
|
||||
#define TOML11_COMMENTS_HPP
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef TOML11_PRESERVE_COMMENTS_BY_DEFAULT
|
||||
# define TOML11_DEFAULT_COMMENT_STRATEGY ::toml::preserve_comments
|
||||
#else
|
||||
# define TOML11_DEFAULT_COMMENT_STRATEGY ::toml::discard_comments
|
||||
#endif
|
||||
|
||||
// This file provides mainly two classes, `preserve_comments` and `discard_comments`.
|
||||
// Those two are a container that have the same interface as `std::vector<std::string>`
|
||||
// but bahaves in the opposite way. `preserve_comments` is just the same as
|
||||
@@ -339,7 +346,7 @@ operator+(const empty_iterator<T, C>& lhs, typename empty_iterator<T, C>::differ
|
||||
//
|
||||
// Why this is chose as the default type is because the last version (2.x.y)
|
||||
// does not contain any comments in a value. To minimize the impact on the
|
||||
// efficiency, this is choosed as a default.
|
||||
// efficiency, this is chosen as a default.
|
||||
//
|
||||
// To reduce the memory footprint, later we can try empty base optimization (EBO).
|
||||
struct discard_comments
|
||||
@@ -418,14 +425,14 @@ struct discard_comments
|
||||
// empty, so accessing through operator[], front/back, data causes address
|
||||
// error.
|
||||
|
||||
reference operator[](const size_type) noexcept {return *data();}
|
||||
const_reference operator[](const size_type) const noexcept {return *data();}
|
||||
reference operator[](const size_type) noexcept {never_call("toml::discard_comment::operator[]");}
|
||||
const_reference operator[](const size_type) const noexcept {never_call("toml::discard_comment::operator[]");}
|
||||
reference at(const size_type) {throw std::out_of_range("toml::discard_comment is always empty.");}
|
||||
const_reference at(const size_type) const {throw std::out_of_range("toml::discard_comment is always empty.");}
|
||||
reference front() noexcept {return *data();}
|
||||
const_reference front() const noexcept {return *data();}
|
||||
reference back() noexcept {return *data();}
|
||||
const_reference back() const noexcept {return *data();}
|
||||
reference front() noexcept {never_call("toml::discard_comment::front");}
|
||||
const_reference front() const noexcept {never_call("toml::discard_comment::front");}
|
||||
reference back() noexcept {never_call("toml::discard_comment::back");}
|
||||
const_reference back() const noexcept {never_call("toml::discard_comment::back");}
|
||||
|
||||
pointer data() noexcept {return nullptr;}
|
||||
const_pointer data() const noexcept {return nullptr;}
|
||||
@@ -443,6 +450,18 @@ struct discard_comments
|
||||
const_reverse_iterator rend() const noexcept {return const_iterator{};}
|
||||
const_reverse_iterator crbegin() const noexcept {return const_iterator{};}
|
||||
const_reverse_iterator crend() const noexcept {return const_iterator{};}
|
||||
|
||||
private:
|
||||
|
||||
[[noreturn]] static void never_call(const char *const this_function)
|
||||
{
|
||||
#ifdef __has_builtin
|
||||
# if __has_builtin(__builtin_unreachable)
|
||||
__builtin_unreachable();
|
||||
# endif
|
||||
#endif
|
||||
throw std::logic_error{this_function};
|
||||
}
|
||||
};
|
||||
|
||||
inline bool operator==(const discard_comments&, const discard_comments&) noexcept {return true;}
|
||||
+32
-32
@@ -21,22 +21,7 @@ namespace toml
|
||||
namespace detail
|
||||
{
|
||||
// TODO: find more sophisticated way to handle this
|
||||
#if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(_POSIX_SOURCE)
|
||||
inline std::tm localtime_s(const std::time_t* src)
|
||||
{
|
||||
std::tm dst;
|
||||
const auto result = ::localtime_r(src, &dst);
|
||||
if (!result) { throw std::runtime_error("localtime_r failed."); }
|
||||
return dst;
|
||||
}
|
||||
inline std::tm gmtime_s(const std::time_t* src)
|
||||
{
|
||||
std::tm dst;
|
||||
const auto result = ::gmtime_r(src, &dst);
|
||||
if (!result) { throw std::runtime_error("gmtime_r failed."); }
|
||||
return dst;
|
||||
}
|
||||
#elif defined(_MSC_VER)
|
||||
#if defined(_MSC_VER)
|
||||
inline std::tm localtime_s(const std::time_t* src)
|
||||
{
|
||||
std::tm dst;
|
||||
@@ -51,6 +36,21 @@ inline std::tm gmtime_s(const std::time_t* src)
|
||||
if (result) { throw std::runtime_error("gmtime_s failed."); }
|
||||
return dst;
|
||||
}
|
||||
#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(_POSIX_SOURCE)
|
||||
inline std::tm localtime_s(const std::time_t* src)
|
||||
{
|
||||
std::tm dst;
|
||||
const auto result = ::localtime_r(src, &dst);
|
||||
if (!result) { throw std::runtime_error("localtime_r failed."); }
|
||||
return dst;
|
||||
}
|
||||
inline std::tm gmtime_s(const std::time_t* src)
|
||||
{
|
||||
std::tm dst;
|
||||
const auto result = ::gmtime_r(src, &dst);
|
||||
if (!result) { throw std::runtime_error("gmtime_r failed."); }
|
||||
return dst;
|
||||
}
|
||||
#else // fallback. not threadsafe
|
||||
inline std::tm localtime_s(const std::time_t* src)
|
||||
{
|
||||
@@ -85,9 +85,9 @@ enum class month_t : std::uint8_t
|
||||
|
||||
struct local_date
|
||||
{
|
||||
std::int16_t year; // A.D. (like, 2018)
|
||||
std::uint8_t month; // [0, 11]
|
||||
std::uint8_t day; // [1, 31]
|
||||
std::int16_t year{}; // A.D. (like, 2018)
|
||||
std::uint8_t month{}; // [0, 11]
|
||||
std::uint8_t day{}; // [1, 31]
|
||||
|
||||
local_date(int y, month_t m, int d)
|
||||
: year (static_cast<std::int16_t>(y)),
|
||||
@@ -181,12 +181,12 @@ operator<<(std::basic_ostream<charT, traits>& os, const local_date& date)
|
||||
|
||||
struct local_time
|
||||
{
|
||||
std::uint8_t hour; // [0, 23]
|
||||
std::uint8_t minute; // [0, 59]
|
||||
std::uint8_t second; // [0, 60]
|
||||
std::uint16_t millisecond; // [0, 999]
|
||||
std::uint16_t microsecond; // [0, 999]
|
||||
std::uint16_t nanosecond; // [0, 999]
|
||||
std::uint8_t hour{}; // [0, 23]
|
||||
std::uint8_t minute{}; // [0, 59]
|
||||
std::uint8_t second{}; // [0, 60]
|
||||
std::uint16_t millisecond{}; // [0, 999]
|
||||
std::uint16_t microsecond{}; // [0, 999]
|
||||
std::uint16_t nanosecond{}; // [0, 999]
|
||||
|
||||
local_time(int h, int m, int s,
|
||||
int ms = 0, int us = 0, int ns = 0)
|
||||
@@ -297,8 +297,8 @@ operator<<(std::basic_ostream<charT, traits>& os, const local_time& time)
|
||||
|
||||
struct time_offset
|
||||
{
|
||||
std::int8_t hour; // [-12, 12]
|
||||
std::int8_t minute; // [-59, 59]
|
||||
std::int8_t hour{}; // [-12, 12]
|
||||
std::int8_t minute{}; // [-59, 59]
|
||||
|
||||
time_offset(int h, int m)
|
||||
: hour (static_cast<std::int8_t>(h)),
|
||||
@@ -364,8 +364,8 @@ operator<<(std::basic_ostream<charT, traits>& os, const time_offset& offset)
|
||||
|
||||
struct local_datetime
|
||||
{
|
||||
local_date date;
|
||||
local_time time;
|
||||
local_date date{};
|
||||
local_time time{};
|
||||
|
||||
local_datetime(local_date d, local_time t): date(d), time(t) {}
|
||||
|
||||
@@ -478,9 +478,9 @@ operator<<(std::basic_ostream<charT, traits>& os, const local_datetime& dt)
|
||||
|
||||
struct offset_datetime
|
||||
{
|
||||
local_date date;
|
||||
local_time time;
|
||||
time_offset offset;
|
||||
local_date date{};
|
||||
local_time time{};
|
||||
time_offset offset{};
|
||||
|
||||
offset_datetime(local_date d, local_time t, time_offset o)
|
||||
: date(d), time(t), offset(o)
|
||||
+19
-1
@@ -2,14 +2,32 @@
|
||||
// Distributed under the MIT License.
|
||||
#ifndef TOML11_EXCEPTION_HPP
|
||||
#define TOML11_EXCEPTION_HPP
|
||||
#include <stdexcept>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "source_location.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
|
||||
struct file_io_error : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
file_io_error(int errnum, const std::string& msg, const std::string& fname)
|
||||
: std::runtime_error(msg + " \"" + fname + "\": errno = " + std::to_string(errnum)),
|
||||
errno_(errnum)
|
||||
{}
|
||||
|
||||
int get_errno() const noexcept {return errno_;}
|
||||
|
||||
private:
|
||||
int errno_;
|
||||
};
|
||||
|
||||
struct exception : public std::exception
|
||||
{
|
||||
public:
|
||||
-1
@@ -2,7 +2,6 @@
|
||||
// Distributed under the MIT License.
|
||||
#ifndef TOML11_FROM_HPP
|
||||
#define TOML11_FROM_HPP
|
||||
#include "traits.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
+101
-13
@@ -140,7 +140,7 @@ get(basic_value<C, M, V>&& v)
|
||||
// ============================================================================
|
||||
// std::string_view
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#if defined(TOML11_USING_STRING_VIEW) && TOML11_USING_STRING_VIEW>0
|
||||
template<typename T, typename C,
|
||||
template<typename ...> class M, template<typename ...> class V>
|
||||
inline detail::enable_if_t<std::is_same<T, std::string_view>::value, std::string_view>
|
||||
@@ -215,6 +215,7 @@ template<typename T, typename C,
|
||||
detail::enable_if_t<detail::conjunction<
|
||||
detail::is_container<T>, // T is a container
|
||||
detail::negation<detail::has_push_back_method<T>>, // w/o push_back(...)
|
||||
detail::negation<detail::has_specialized_from<T>>, // T does not have special conversion
|
||||
detail::negation< // not toml::array
|
||||
detail::is_exact_toml_type<T, basic_value<C, M, V>>>
|
||||
>::value, T>
|
||||
@@ -255,19 +256,37 @@ get(const basic_value<C, M, V>&);
|
||||
|
||||
// toml::from<T>::from_toml(v)
|
||||
template<typename T, typename C,
|
||||
template<typename ...> class M, template<typename ...> class V,
|
||||
std::size_t S = sizeof(::toml::from<T>)>
|
||||
T get(const basic_value<C, M, V>&);
|
||||
template<typename ...> class M, template<typename ...> class V>
|
||||
detail::enable_if_t<detail::has_specialized_from<T>::value, T>
|
||||
get(const basic_value<C, M, V>&);
|
||||
|
||||
// T(const toml::value&) and T is not toml::basic_value
|
||||
template<typename T, typename C,
|
||||
template<typename ...> class M, template<typename ...> class V>
|
||||
detail::enable_if_t<detail::has_specialized_from<T>::value, T>
|
||||
get(basic_value<C, M, V>&);
|
||||
|
||||
// T(const toml::value&) and T is not toml::basic_value,
|
||||
// and it does not have `from<T>` nor `from_toml`.
|
||||
template<typename T, typename C,
|
||||
template<typename ...> class M, template<typename ...> class V>
|
||||
detail::enable_if_t<detail::conjunction<
|
||||
detail::negation<detail::is_basic_value<T>>,
|
||||
std::is_constructible<T, const basic_value<C, M, V>&>
|
||||
std::is_constructible<T, const basic_value<C, M, V>&>,
|
||||
detail::negation<detail::has_from_toml_method<T, C, M, V>>,
|
||||
detail::negation<detail::has_specialized_from<T>>
|
||||
>::value, T>
|
||||
get(const basic_value<C, M, V>&);
|
||||
|
||||
template<typename T, typename C,
|
||||
template<typename ...> class M, template<typename ...> class V>
|
||||
detail::enable_if_t<detail::conjunction<
|
||||
detail::negation<detail::is_basic_value<T>>,
|
||||
std::is_constructible<T, basic_value<C, M, V>&>,
|
||||
detail::negation<detail::has_from_toml_method<T, C, M, V>>,
|
||||
detail::negation<detail::has_specialized_from<T>>
|
||||
>::value, T>
|
||||
get(basic_value<C, M, V>&);
|
||||
|
||||
// ============================================================================
|
||||
// array-like types; most likely STL container, like std::vector, etc.
|
||||
|
||||
@@ -321,6 +340,7 @@ template<typename T, typename C,
|
||||
detail::enable_if_t<detail::conjunction<
|
||||
detail::is_container<T>, // T is a container
|
||||
detail::negation<detail::has_push_back_method<T>>, // w/o push_back
|
||||
detail::negation<detail::has_specialized_from<T>>, // T does not have special conversion
|
||||
detail::negation< // T is not toml::array
|
||||
detail::is_exact_toml_type<T, basic_value<C, M, V>>>
|
||||
>::value, T>
|
||||
@@ -338,8 +358,10 @@ get(const basic_value<C, M, V>& v)
|
||||
{v.location(), "here"}
|
||||
}));
|
||||
}
|
||||
std::transform(ar.cbegin(), ar.cend(), container.begin(),
|
||||
[](const value& x){return ::toml::get<value_type>(x);});
|
||||
for(std::size_t i=0; i<ar.size(); ++i)
|
||||
{
|
||||
container[i] = ::toml::get<value_type>(ar[i]);
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
@@ -438,9 +460,16 @@ get(const basic_value<C, M, V>& v)
|
||||
return ud;
|
||||
}
|
||||
template<typename T, typename C,
|
||||
template<typename ...> class M, template<typename ...> class V,
|
||||
std::size_t>
|
||||
T get(const basic_value<C, M, V>& v)
|
||||
template<typename ...> class M, template<typename ...> class V>
|
||||
detail::enable_if_t<detail::has_specialized_from<T>::value, T>
|
||||
get(const basic_value<C, M, V>& v)
|
||||
{
|
||||
return ::toml::from<T>::from_toml(v);
|
||||
}
|
||||
template<typename T, typename C,
|
||||
template<typename ...> class M, template<typename ...> class V>
|
||||
detail::enable_if_t<detail::has_specialized_from<T>::value, T>
|
||||
get(basic_value<C, M, V>& v)
|
||||
{
|
||||
return ::toml::from<T>::from_toml(v);
|
||||
}
|
||||
@@ -448,14 +477,29 @@ T get(const basic_value<C, M, V>& v)
|
||||
template<typename T, typename C,
|
||||
template<typename ...> class M, template<typename ...> class V>
|
||||
detail::enable_if_t<detail::conjunction<
|
||||
detail::negation<detail::is_basic_value<T>>,
|
||||
std::is_constructible<T, const basic_value<C, M, V>&>
|
||||
detail::negation<detail::is_basic_value<T>>, // T is not a toml::value
|
||||
std::is_constructible<T, const basic_value<C, M, V>&>, // T is constructible from toml::value
|
||||
detail::negation<detail::has_from_toml_method<T, C, M, V>>, // and T does not have T.from_toml(v);
|
||||
detail::negation<detail::has_specialized_from<T>> // and T does not have toml::from<T>{};
|
||||
>::value, T>
|
||||
get(const basic_value<C, M, V>& v)
|
||||
{
|
||||
return T(v);
|
||||
}
|
||||
|
||||
template<typename T, typename C,
|
||||
template<typename ...> class M, template<typename ...> class V>
|
||||
detail::enable_if_t<detail::conjunction<
|
||||
detail::negation<detail::is_basic_value<T>>, // T is not a toml::value
|
||||
std::is_constructible<T, basic_value<C, M, V>&>, // T is constructible from toml::value
|
||||
detail::negation<detail::has_from_toml_method<T, C, M, V>>, // and T does not have T.from_toml(v);
|
||||
detail::negation<detail::has_specialized_from<T>> // and T does not have toml::from<T>{};
|
||||
>::value, T>
|
||||
get(basic_value<C, M, V>& v)
|
||||
{
|
||||
return T(v);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// find
|
||||
|
||||
@@ -1031,6 +1075,50 @@ find_or(const basic_value<C, M, V>& v, const toml::key& ky, T&& opt)
|
||||
return get_or(tab.at(ky), std::forward<T>(opt));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// recursive find-or with type deduction (find_or(value, keys, opt))
|
||||
|
||||
template<typename Value, typename ... Ks,
|
||||
typename detail::enable_if_t<(sizeof...(Ks) > 1), std::nullptr_t> = nullptr>
|
||||
// here we need to add SFINAE in the template parameter to avoid
|
||||
// infinite recursion in type deduction on gcc
|
||||
auto find_or(Value&& v, const toml::key& ky, Ks&& ... keys)
|
||||
-> decltype(find_or(std::forward<Value>(v), ky, detail::last_one(std::forward<Ks>(keys)...)))
|
||||
{
|
||||
if(!v.is_table())
|
||||
{
|
||||
return detail::last_one(std::forward<Ks>(keys)...);
|
||||
}
|
||||
auto&& tab = std::forward<Value>(v).as_table();
|
||||
if(tab.count(ky) == 0)
|
||||
{
|
||||
return detail::last_one(std::forward<Ks>(keys)...);
|
||||
}
|
||||
return find_or(std::forward<decltype(tab)>(tab).at(ky), std::forward<Ks>(keys)...);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// recursive find_or with explicit type specialization, find_or<int>(value, keys...)
|
||||
|
||||
template<typename T, typename Value, typename ... Ks,
|
||||
typename detail::enable_if_t<(sizeof...(Ks) > 1), std::nullptr_t> = nullptr>
|
||||
// here we need to add SFINAE in the template parameter to avoid
|
||||
// infinite recursion in type deduction on gcc
|
||||
auto find_or(Value&& v, const toml::key& ky, Ks&& ... keys)
|
||||
-> decltype(find_or<T>(std::forward<Value>(v), ky, detail::last_one(std::forward<Ks>(keys)...)))
|
||||
{
|
||||
if(!v.is_table())
|
||||
{
|
||||
return detail::last_one(std::forward<Ks>(keys)...);
|
||||
}
|
||||
auto&& tab = std::forward<Value>(v).as_table();
|
||||
if(tab.count(ky) == 0)
|
||||
{
|
||||
return detail::last_one(std::forward<Ks>(keys)...);
|
||||
}
|
||||
return find_or(std::forward<decltype(tab)>(tab).at(ky), std::forward<Ks>(keys)...);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// expect
|
||||
|
||||
-1
@@ -2,7 +2,6 @@
|
||||
// Distributed under the MIT License.
|
||||
#ifndef TOML11_INTO_HPP
|
||||
#define TOML11_INTO_HPP
|
||||
#include "traits.hpp"
|
||||
|
||||
namespace toml
|
||||
{
|
||||
+39
-15
@@ -5,7 +5,6 @@
|
||||
#include <istream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <fstream>
|
||||
|
||||
#include "combinator.hpp"
|
||||
|
||||
@@ -119,8 +118,8 @@ using lex_local_time = lex_partial_time;
|
||||
// ===========================================================================
|
||||
|
||||
using lex_quotation_mark = character<'"'>;
|
||||
using lex_basic_unescaped = exclude<either<in_range<0x00, 0x08>, // 0x09 (tab)
|
||||
in_range<0x0a, 0x1F>, // is allowed
|
||||
using lex_basic_unescaped = exclude<either<in_range<0x00, 0x08>, // 0x09 (tab) is allowed
|
||||
in_range<0x0A, 0x1F>,
|
||||
character<0x22>, character<0x5C>,
|
||||
character<0x7F>>>;
|
||||
|
||||
@@ -133,6 +132,9 @@ using lex_escape_seq_char = either<character<'"'>, character<'\\'>,
|
||||
character<'b'>, character<'f'>,
|
||||
character<'n'>, character<'r'>,
|
||||
character<'t'>,
|
||||
#ifdef TOML11_USE_UNRELEASED_TOML_FEATURES
|
||||
character<'e'>, // ESC (0x1B)
|
||||
#endif
|
||||
lex_escape_unicode_short,
|
||||
lex_escape_unicode_long
|
||||
>;
|
||||
@@ -166,7 +168,7 @@ using lex_basic_string = sequence<lex_quotation_mark,
|
||||
// | ^- expected newline, but got '"'.
|
||||
// ```
|
||||
// As a quick workaround for this problem, `lex_ml_basic_string_delim` was
|
||||
// splitted into two, `lex_ml_basic_string_open` and `lex_ml_basic_string_close`.
|
||||
// split into two, `lex_ml_basic_string_open` and `lex_ml_basic_string_close`.
|
||||
// `lex_ml_basic_string_open` allows only `"""`. `_close` allows 3-5 `"`s.
|
||||
// In parse_ml_basic_string() function, the trailing `"`s will be attached to
|
||||
// the string body.
|
||||
@@ -178,8 +180,8 @@ using lex_ml_basic_string_close = sequence<
|
||||
maybe<lex_quotation_mark>, maybe<lex_quotation_mark>
|
||||
>;
|
||||
|
||||
using lex_ml_basic_unescaped = exclude<either<in_range<0x00, 0x08>, // 0x09
|
||||
in_range<0x0a, 0x1F>, // is tab
|
||||
using lex_ml_basic_unescaped = exclude<either<in_range<0x00, 0x08>, // 0x09 is tab
|
||||
in_range<0x0A, 0x1F>,
|
||||
character<0x5C>, // backslash
|
||||
character<0x7F>, // DEL
|
||||
lex_ml_basic_string_delim>>;
|
||||
@@ -196,8 +198,8 @@ using lex_ml_basic_string = sequence<lex_ml_basic_string_open,
|
||||
lex_ml_basic_body,
|
||||
lex_ml_basic_string_close>;
|
||||
|
||||
using lex_literal_char = exclude<either<in_range<0x00, 0x08>,
|
||||
in_range<0x10, 0x19>, character<0x27>>>;
|
||||
using lex_literal_char = exclude<either<in_range<0x00, 0x08>, in_range<0x0A, 0x1F>,
|
||||
character<0x7F>, character<0x27>>>;
|
||||
using lex_apostrophe = character<'\''>;
|
||||
using lex_literal_string = sequence<lex_apostrophe,
|
||||
repeat<lex_literal_char, unlimited>,
|
||||
@@ -212,7 +214,7 @@ using lex_ml_literal_string_close = sequence<
|
||||
>;
|
||||
|
||||
using lex_ml_literal_char = exclude<either<in_range<0x00, 0x08>,
|
||||
in_range<0x10, 0x1F>,
|
||||
in_range<0x0A, 0x1F>,
|
||||
character<0x7F>,
|
||||
lex_ml_literal_string_delim>>;
|
||||
using lex_ml_literal_body = repeat<either<lex_ml_literal_char, lex_newline>,
|
||||
@@ -225,12 +227,6 @@ using lex_string = either<lex_ml_basic_string, lex_basic_string,
|
||||
lex_ml_literal_string, lex_literal_string>;
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
using lex_comment_start_symbol = character<'#'>;
|
||||
using lex_non_eol = either<character<'\t'>, exclude<in_range<0x00, 0x19>>>;
|
||||
using lex_comment = sequence<lex_comment_start_symbol,
|
||||
repeat<lex_non_eol, unlimited>>;
|
||||
|
||||
using lex_dot_sep = sequence<maybe<lex_ws>, character<'.'>, maybe<lex_ws>>;
|
||||
|
||||
using lex_unquoted_key = repeat<either<lex_alpha, lex_digit,
|
||||
@@ -265,6 +261,34 @@ using lex_array_table = sequence<lex_array_table_open,
|
||||
maybe<lex_ws>,
|
||||
lex_array_table_close>;
|
||||
|
||||
using lex_utf8_1byte = in_range<0x00, 0x7F>;
|
||||
using lex_utf8_2byte = sequence<
|
||||
in_range<'\xC2', '\xDF'>,
|
||||
in_range<'\x80', '\xBF'>
|
||||
>;
|
||||
using lex_utf8_3byte = sequence<either<
|
||||
sequence<character<'\xE0'>, in_range<'\xA0', '\xBF'>>,
|
||||
sequence<in_range<'\xE1', '\xEC'>, in_range<'\x80', '\xBF'>>,
|
||||
sequence<character<'\xED'>, in_range<'\x80', '\x9F'>>,
|
||||
sequence<in_range<'\xEE', '\xEF'>, in_range<'\x80', '\xBF'>>
|
||||
>, in_range<'\x80', '\xBF'>>;
|
||||
using lex_utf8_4byte = sequence<either<
|
||||
sequence<character<'\xF0'>, in_range<'\x90', '\xBF'>>,
|
||||
sequence<in_range<'\xF1', '\xF3'>, in_range<'\x80', '\xBF'>>,
|
||||
sequence<character<'\xF4'>, in_range<'\x80', '\x8F'>>
|
||||
>, in_range<'\x80', '\xBF'>, in_range<'\x80', '\xBF'>>;
|
||||
using lex_utf8_code = either<
|
||||
lex_utf8_1byte,
|
||||
lex_utf8_2byte,
|
||||
lex_utf8_3byte,
|
||||
lex_utf8_4byte
|
||||
>;
|
||||
|
||||
using lex_comment_start_symbol = character<'#'>;
|
||||
using lex_non_eol_ascii = either<character<0x09>, in_range<0x20, 0x7E>>;
|
||||
using lex_comment = sequence<lex_comment_start_symbol, repeat<either<
|
||||
lex_non_eol_ascii, lex_utf8_2byte, lex_utf8_3byte, lex_utf8_4byte>, unlimited>>;
|
||||
|
||||
} // detail
|
||||
} // toml
|
||||
#endif // TOML_LEXER_HPP
|
||||
+11
-5
@@ -12,8 +12,11 @@ inline namespace toml_literals
|
||||
{
|
||||
|
||||
// implementation
|
||||
inline ::toml::value literal_internal_impl(::toml::detail::location loc)
|
||||
inline ::toml::basic_value<TOML11_DEFAULT_COMMENT_STRATEGY, std::unordered_map, std::vector>
|
||||
literal_internal_impl(::toml::detail::location loc)
|
||||
{
|
||||
using value_type = ::toml::basic_value<
|
||||
TOML11_DEFAULT_COMMENT_STRATEGY, std::unordered_map, std::vector>;
|
||||
// if there are some comments or empty lines, skip them.
|
||||
using skip_line = ::toml::detail::repeat<toml::detail::sequence<
|
||||
::toml::detail::maybe<::toml::detail::lex_ws>,
|
||||
@@ -50,7 +53,7 @@ inline ::toml::value literal_internal_impl(::toml::detail::location loc)
|
||||
// If it is neither a table-key or a array-of-table-key, it may be a value.
|
||||
if(!is_table_key && !is_aots_key)
|
||||
{
|
||||
if(auto data = ::toml::detail::parse_value<::toml::value>(loc))
|
||||
if(auto data = ::toml::detail::parse_value<value_type>(loc, 0))
|
||||
{
|
||||
return data.unwrap();
|
||||
}
|
||||
@@ -67,7 +70,7 @@ inline ::toml::value literal_internal_impl(::toml::detail::location loc)
|
||||
// It is a valid toml file.
|
||||
// It should be parsed as if we parse a file with this content.
|
||||
|
||||
if(auto data = ::toml::detail::parse_toml_file<::toml::value>(loc))
|
||||
if(auto data = ::toml::detail::parse_toml_file<value_type>(loc))
|
||||
{
|
||||
return data.unwrap();
|
||||
}
|
||||
@@ -78,11 +81,13 @@ inline ::toml::value literal_internal_impl(::toml::detail::location loc)
|
||||
|
||||
}
|
||||
|
||||
inline ::toml::value operator"" _toml(const char* str, std::size_t len)
|
||||
inline ::toml::basic_value<TOML11_DEFAULT_COMMENT_STRATEGY, std::unordered_map, std::vector>
|
||||
operator"" _toml(const char* str, std::size_t len)
|
||||
{
|
||||
::toml::detail::location loc(
|
||||
std::string("TOML literal encoded in a C++ code"),
|
||||
std::vector<char>(str, str + len));
|
||||
// literal length does not include the null character at the end.
|
||||
return literal_internal_impl(std::move(loc));
|
||||
}
|
||||
|
||||
@@ -91,7 +96,8 @@ inline ::toml::value operator"" _toml(const char* str, std::size_t len)
|
||||
#if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L
|
||||
// value of u8"" literal has been changed from char to char8_t and char8_t is
|
||||
// NOT compatible to char
|
||||
inline ::toml::value operator"" _toml(const char8_t* str, std::size_t len)
|
||||
inline ::toml::basic_value<TOML11_DEFAULT_COMMENT_STRATEGY, std::unordered_map, std::vector>
|
||||
operator"" _toml(const char8_t* str, std::size_t len)
|
||||
{
|
||||
::toml::detail::location loc(
|
||||
std::string("TOML literal encoded in a C++ code"),
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
#ifndef TOML11_MACROS_HPP
|
||||
#define TOML11_MACROS_HPP
|
||||
|
||||
#define TOML11_STRINGIZE_AUX(x) #x
|
||||
#define TOML11_STRINGIZE(x) TOML11_STRINGIZE_AUX(x)
|
||||
|
||||
#define TOML11_CONCATENATE_AUX(x, y) x##y
|
||||
#define TOML11_CONCATENATE(x, y) TOML11_CONCATENATE_AUX(x, y)
|
||||
|
||||
// ============================================================================
|
||||
// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE
|
||||
|
||||
#ifndef TOML11_WITHOUT_DEFINE_NON_INTRUSIVE
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TOML11_ARGS_SIZE
|
||||
|
||||
#define TOML11_INDEX_RSEQ() \
|
||||
32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, \
|
||||
16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||
#define TOML11_ARGS_SIZE_IMPL(\
|
||||
ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8, ARG9, ARG10, \
|
||||
ARG11, ARG12, ARG13, ARG14, ARG15, ARG16, ARG17, ARG18, ARG19, ARG20, \
|
||||
ARG21, ARG22, ARG23, ARG24, ARG25, ARG26, ARG27, ARG28, ARG29, ARG30, \
|
||||
ARG31, ARG32, N, ...) N
|
||||
#define TOML11_ARGS_SIZE_AUX(...) TOML11_ARGS_SIZE_IMPL(__VA_ARGS__)
|
||||
#define TOML11_ARGS_SIZE(...) TOML11_ARGS_SIZE_AUX(__VA_ARGS__, TOML11_INDEX_RSEQ())
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TOML11_FOR_EACH_VA_ARGS
|
||||
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, ARG1 ) FUNCTOR(ARG1)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, __VA_ARGS__)
|
||||
#define TOML11_FOR_EACH_VA_ARGS_AUX_32(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, __VA_ARGS__)
|
||||
|
||||
#define TOML11_FOR_EACH_VA_ARGS(FUNCTOR, ...)\
|
||||
TOML11_CONCATENATE(TOML11_FOR_EACH_VA_ARGS_AUX_, TOML11_ARGS_SIZE(__VA_ARGS__))(FUNCTOR, __VA_ARGS__)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE
|
||||
|
||||
// use it in the following way.
|
||||
// ```cpp
|
||||
// namespace foo
|
||||
// {
|
||||
// struct Foo
|
||||
// {
|
||||
// std::string s;
|
||||
// double d;
|
||||
// int i;
|
||||
// };
|
||||
// } // foo
|
||||
//
|
||||
// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(foo::Foo, s, d, i)
|
||||
// ```
|
||||
// And then you can use `toml::find<foo::Foo>(file, "foo");`
|
||||
//
|
||||
#define TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE(VAR_NAME)\
|
||||
obj.VAR_NAME = toml::find<decltype(obj.VAR_NAME)>(v, TOML11_STRINGIZE(VAR_NAME));
|
||||
|
||||
#define TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE(VAR_NAME)\
|
||||
v[TOML11_STRINGIZE(VAR_NAME)] = obj.VAR_NAME;
|
||||
|
||||
#define TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(NAME, ...)\
|
||||
namespace toml { \
|
||||
template<> \
|
||||
struct from<NAME> \
|
||||
{ \
|
||||
template<typename C, template<typename ...> class T, \
|
||||
template<typename ...> class A> \
|
||||
static NAME from_toml(const basic_value<C, T, A>& v) \
|
||||
{ \
|
||||
NAME obj; \
|
||||
TOML11_FOR_EACH_VA_ARGS(TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE, __VA_ARGS__) \
|
||||
return obj; \
|
||||
} \
|
||||
}; \
|
||||
template<> \
|
||||
struct into<NAME> \
|
||||
{ \
|
||||
static value into_toml(const NAME& obj) \
|
||||
{ \
|
||||
::toml::value v = ::toml::table{}; \
|
||||
TOML11_FOR_EACH_VA_ARGS(TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE, __VA_ARGS__) \
|
||||
return v; \
|
||||
} \
|
||||
}; \
|
||||
} /* toml */
|
||||
|
||||
#endif// TOML11_WITHOUT_DEFINE_NON_INTRUSIVE
|
||||
|
||||
#endif// TOML11_MACROS_HPP
|
||||
+624
-117
File diff suppressed because it is too large
Load Diff
+13
-14
@@ -70,16 +70,16 @@ struct region_base
|
||||
struct location final : public region_base
|
||||
{
|
||||
using const_iterator = typename std::vector<char>::const_iterator;
|
||||
using difference_type = typename const_iterator::difference_type;
|
||||
using difference_type = typename std::iterator_traits<const_iterator>::difference_type;
|
||||
using source_ptr = std::shared_ptr<const std::vector<char>>;
|
||||
|
||||
location(std::string name, std::vector<char> cont)
|
||||
location(std::string source_name, std::vector<char> cont)
|
||||
: source_(std::make_shared<std::vector<char>>(std::move(cont))),
|
||||
line_number_(1), source_name_(std::move(name)), iter_(source_->cbegin())
|
||||
line_number_(1), source_name_(std::move(source_name)), iter_(source_->cbegin())
|
||||
{}
|
||||
location(std::string name, const std::string& cont)
|
||||
location(std::string source_name, const std::string& cont)
|
||||
: source_(std::make_shared<std::vector<char>>(cont.begin(), cont.end())),
|
||||
line_number_(1), source_name_(std::move(name)), iter_(source_->cbegin())
|
||||
line_number_(1), source_name_(std::move(source_name)), iter_(source_->cbegin())
|
||||
{}
|
||||
|
||||
location(const location&) = default;
|
||||
@@ -92,7 +92,7 @@ struct location final : public region_base
|
||||
char front() const noexcept override {return *iter_;}
|
||||
|
||||
// this const prohibits codes like `++(loc.iter())`.
|
||||
const const_iterator iter() const noexcept {return iter_;}
|
||||
std::add_const<const_iterator>::type iter() const noexcept {return iter_;}
|
||||
|
||||
const_iterator begin() const noexcept {return source_->cbegin();}
|
||||
const_iterator end() const noexcept {return source_->cend();}
|
||||
@@ -227,8 +227,7 @@ struct region final : public region_base
|
||||
region& operator+=(const region& other)
|
||||
{
|
||||
// different regions cannot be concatenated
|
||||
assert(this->begin() == other.begin() && this->end() == other.end() &&
|
||||
this->last_ == other.first_);
|
||||
assert(this->source_ == other.source_ && this->last_ == other.first_);
|
||||
|
||||
this->last_ = other.last_;
|
||||
return *this;
|
||||
@@ -343,9 +342,9 @@ struct region final : public region_base
|
||||
}))
|
||||
{
|
||||
// unwrap the first '#' by std::next.
|
||||
auto str = make_string(std::next(comment_found), iter);
|
||||
if(str.back() == '\r') {str.pop_back();}
|
||||
com.push_back(std::move(str));
|
||||
auto s = make_string(std::next(comment_found), iter);
|
||||
if(!s.empty() && s.back() == '\r') {s.pop_back();}
|
||||
com.push_back(std::move(s));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -396,9 +395,9 @@ struct region final : public region_base
|
||||
}))
|
||||
{
|
||||
// unwrap the first '#' by std::next.
|
||||
auto str = make_string(std::next(comment_found), this->line_end());
|
||||
if(str.back() == '\r') {str.pop_back();}
|
||||
com.push_back(std::move(str));
|
||||
auto s = make_string(std::next(comment_found), this->line_end());
|
||||
if(!s.empty() && s.back() == '\r') {s.pop_back();}
|
||||
com.push_back(std::move(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+342
-136
@@ -2,10 +2,19 @@
|
||||
// Distributed under the MIT License.
|
||||
#ifndef TOML11_SERIALIZER_HPP
|
||||
#define TOML11_SERIALIZER_HPP
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <locale.h>
|
||||
#elif defined(__APPLE__) || defined(__FreeBSD__)
|
||||
#include <xlocale.h>
|
||||
#elif defined(__linux__)
|
||||
#include <locale.h>
|
||||
#endif
|
||||
|
||||
#include "lexer.hpp"
|
||||
#include "value.hpp"
|
||||
|
||||
@@ -26,19 +35,24 @@ namespace toml
|
||||
// a `"` and escaping some special character is boring.
|
||||
template<typename charT, typename traits, typename Alloc>
|
||||
std::basic_string<charT, traits, Alloc>
|
||||
format_key(const std::basic_string<charT, traits, Alloc>& key)
|
||||
format_key(const std::basic_string<charT, traits, Alloc>& k)
|
||||
{
|
||||
if(k.empty())
|
||||
{
|
||||
return std::string("\"\"");
|
||||
}
|
||||
|
||||
// check the key can be a bare (unquoted) key
|
||||
detail::location loc(key, std::vector<char>(key.begin(), key.end()));
|
||||
detail::location loc(k, std::vector<char>(k.begin(), k.end()));
|
||||
detail::lex_unquoted_key::invoke(loc);
|
||||
if(loc.iter() == loc.end())
|
||||
{
|
||||
return key; // all the tokens are consumed. the key is unquoted-key.
|
||||
return k; // all the tokens are consumed. the key is unquoted-key.
|
||||
}
|
||||
|
||||
//if it includes special characters, then format it in a "quoted" key.
|
||||
std::basic_string<charT, traits, Alloc> serialized("\"");
|
||||
for(const char c : key)
|
||||
for(const char c : k)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
@@ -49,7 +63,19 @@ format_key(const std::basic_string<charT, traits, Alloc>& key)
|
||||
case '\f': {serialized += "\\f"; break;}
|
||||
case '\n': {serialized += "\\n"; break;}
|
||||
case '\r': {serialized += "\\r"; break;}
|
||||
default : {serialized += c; break;}
|
||||
default: {
|
||||
if (c >= 0x00 && c < 0x20)
|
||||
{
|
||||
std::array<char, 7> buf;
|
||||
std::snprintf(buf.data(), buf.size(), "\\u00%02x", static_cast<int>(c));
|
||||
serialized += buf.data();
|
||||
}
|
||||
else
|
||||
{
|
||||
serialized += c;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
serialized += "\"";
|
||||
@@ -60,9 +86,12 @@ template<typename charT, typename traits, typename Alloc>
|
||||
std::basic_string<charT, traits, Alloc>
|
||||
format_keys(const std::vector<std::basic_string<charT, traits, Alloc>>& keys)
|
||||
{
|
||||
std::basic_string<charT, traits, Alloc> serialized;
|
||||
if(keys.empty()) {return serialized;}
|
||||
if(keys.empty())
|
||||
{
|
||||
return std::string("\"\"");
|
||||
}
|
||||
|
||||
std::basic_string<charT, traits, Alloc> serialized;
|
||||
for(const auto& ky : keys)
|
||||
{
|
||||
serialized += format_key(ky);
|
||||
@@ -97,8 +126,10 @@ struct serializer
|
||||
const int float_prec = std::numeric_limits<toml::floating>::max_digits10,
|
||||
const bool can_be_inlined = false,
|
||||
const bool no_comment = false,
|
||||
std::vector<toml::key> ks = {})
|
||||
std::vector<toml::key> ks = {},
|
||||
const bool value_has_comment = false)
|
||||
: can_be_inlined_(can_be_inlined), no_comment_(no_comment),
|
||||
value_has_comment_(value_has_comment && !no_comment),
|
||||
float_prec_(float_prec), width_(w), keys_(std::move(ks))
|
||||
{}
|
||||
~serializer() = default;
|
||||
@@ -109,18 +140,93 @@ struct serializer
|
||||
}
|
||||
std::string operator()(const integer_type i) const
|
||||
{
|
||||
return std::to_string(i);
|
||||
#if defined(_WIN32)
|
||||
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
|
||||
const std::string original_locale(setlocale(LC_NUMERIC, nullptr));
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__linux__)
|
||||
const auto c_locale = newlocale(LC_NUMERIC_MASK, "C", locale_t(0));
|
||||
locale_t original_locale(0);
|
||||
if(c_locale != locale_t(0))
|
||||
{
|
||||
original_locale = uselocale(c_locale);
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto str = std::to_string(i);
|
||||
|
||||
#if defined(_WIN32)
|
||||
setlocale(LC_NUMERIC, original_locale.c_str());
|
||||
_configthreadlocale(_DISABLE_PER_THREAD_LOCALE);
|
||||
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__linux__)
|
||||
if(original_locale != locale_t(0))
|
||||
{
|
||||
uselocale(original_locale);
|
||||
}
|
||||
#endif
|
||||
return str;
|
||||
}
|
||||
std::string operator()(const floating_type f) const
|
||||
{
|
||||
if(std::isnan(f))
|
||||
{
|
||||
if(std::signbit(f))
|
||||
{
|
||||
return std::string("-nan");
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::string("nan");
|
||||
}
|
||||
}
|
||||
else if(!std::isfinite(f))
|
||||
{
|
||||
if(std::signbit(f))
|
||||
{
|
||||
return std::string("-inf");
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::string("inf");
|
||||
}
|
||||
}
|
||||
|
||||
// set locale to "C".
|
||||
// To make it thread-local, we use OS-specific features.
|
||||
// If we set process-global locale, it can break other thread that also
|
||||
// outputs something simultaneously.
|
||||
#if defined(_WIN32)
|
||||
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
|
||||
const std::string original_locale(setlocale(LC_NUMERIC, nullptr));
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__linux__)
|
||||
const auto c_locale = newlocale(LC_NUMERIC_MASK, "C", locale_t(0));
|
||||
locale_t original_locale(0);
|
||||
if(c_locale != locale_t(0))
|
||||
{
|
||||
original_locale = uselocale(c_locale);
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto fmt = "%.*g";
|
||||
const auto bsz = std::snprintf(nullptr, 0, fmt, this->float_prec_, f);
|
||||
// +1 for null character(\0)
|
||||
std::vector<char> buf(static_cast<std::size_t>(bsz + 1), '\0');
|
||||
std::snprintf(buf.data(), buf.size(), fmt, this->float_prec_, f);
|
||||
|
||||
// restore the original locale
|
||||
#if defined(_WIN32)
|
||||
setlocale(LC_NUMERIC, original_locale.c_str());
|
||||
_configthreadlocale(_DISABLE_PER_THREAD_LOCALE);
|
||||
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__linux__)
|
||||
if(original_locale != locale_t(0))
|
||||
{
|
||||
uselocale(original_locale);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string token(buf.begin(), std::prev(buf.end()));
|
||||
if(token.back() == '.') // 1. => 1.0
|
||||
if(!token.empty() && token.back() == '.') // 1. => 1.0
|
||||
{
|
||||
token += '0';
|
||||
}
|
||||
@@ -144,8 +250,9 @@ struct serializer
|
||||
{
|
||||
if(s.kind == string_t::basic)
|
||||
{
|
||||
if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() ||
|
||||
std::find(s.str.cbegin(), s.str.cend(), '\"') != s.str.cend())
|
||||
if((std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() ||
|
||||
std::find(s.str.cbegin(), s.str.cend(), '\"') != s.str.cend()) &&
|
||||
this->width_ != (std::numeric_limits<std::size_t>::max)())
|
||||
{
|
||||
// if linefeed or double-quote is contained,
|
||||
// make it multiline basic string.
|
||||
@@ -244,92 +351,18 @@ struct serializer
|
||||
|
||||
std::string operator()(const array_type& v) const
|
||||
{
|
||||
if(!v.empty() && v.front().is_table())// v is an array of tables
|
||||
{
|
||||
// if it's not inlined, we need to add `[[table.key]]`.
|
||||
// but if it can be inlined,
|
||||
// ```
|
||||
// table.key = [
|
||||
// {...},
|
||||
// # comment
|
||||
// {...},
|
||||
// ]
|
||||
// ```
|
||||
if(this->can_be_inlined_)
|
||||
{
|
||||
std::string token;
|
||||
if(!keys_.empty())
|
||||
{
|
||||
token += format_key(keys_.back());
|
||||
token += " = ";
|
||||
}
|
||||
bool failed = false;
|
||||
token += "[\n";
|
||||
for(const auto& item : v)
|
||||
{
|
||||
// if an element of the table has a comment, the table
|
||||
// cannot be inlined.
|
||||
if(this->has_comment_inside(item.as_table()))
|
||||
{
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
if(!no_comment_)
|
||||
{
|
||||
for(const auto& c : item.comments())
|
||||
{
|
||||
token += '#';
|
||||
token += c;
|
||||
token += '\n';
|
||||
}
|
||||
}
|
||||
|
||||
const auto t = this->make_inline_table(item.as_table());
|
||||
|
||||
if(t.size() + 1 > width_ || // +1 for the last comma {...},
|
||||
std::find(t.cbegin(), t.cend(), '\n') != t.cend())
|
||||
{
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
token += t;
|
||||
token += ",\n";
|
||||
}
|
||||
if(!failed)
|
||||
{
|
||||
token += "]\n";
|
||||
return token;
|
||||
}
|
||||
// if failed, serialize them as [[array.of.tables]].
|
||||
}
|
||||
|
||||
std::string token;
|
||||
for(const auto& item : v)
|
||||
{
|
||||
if(!no_comment_)
|
||||
{
|
||||
for(const auto& c : item.comments())
|
||||
{
|
||||
token += '#';
|
||||
token += c;
|
||||
token += '\n';
|
||||
}
|
||||
}
|
||||
token += "[[";
|
||||
token += format_keys(keys_);
|
||||
token += "]]\n";
|
||||
token += this->make_multiline_table(item.as_table());
|
||||
}
|
||||
return token;
|
||||
}
|
||||
if(v.empty())
|
||||
{
|
||||
return std::string("[]");
|
||||
}
|
||||
if(this->is_array_of_tables(v))
|
||||
{
|
||||
return make_array_of_tables(v);
|
||||
}
|
||||
|
||||
// not an array of tables. normal array.
|
||||
// first, try to make it inline if none of the elements have a comment.
|
||||
if(!this->has_comment_inside(v))
|
||||
if( ! this->has_comment_inside(v))
|
||||
{
|
||||
const auto inl = this->make_inline_array(v);
|
||||
if(inl.size() < this->width_ &&
|
||||
@@ -350,7 +383,7 @@ struct serializer
|
||||
token += "[\n";
|
||||
for(const auto& item : v)
|
||||
{
|
||||
if(!item.comments().empty() && !no_comment_)
|
||||
if( ! item.comments().empty() && !no_comment_)
|
||||
{
|
||||
// if comment exists, the element must be the only element in the line.
|
||||
// e.g. the following is not allowed.
|
||||
@@ -376,15 +409,25 @@ struct serializer
|
||||
token += '\n';
|
||||
}
|
||||
token += toml::visit(*this, item);
|
||||
if(token.back() == '\n') {token.pop_back();}
|
||||
if(!token.empty() && token.back() == '\n') {token.pop_back();}
|
||||
token += ",\n";
|
||||
continue;
|
||||
}
|
||||
std::string next_elem;
|
||||
next_elem += toml::visit(*this, item);
|
||||
if(item.is_table())
|
||||
{
|
||||
serializer ser(*this);
|
||||
ser.can_be_inlined_ = true;
|
||||
ser.width_ = (std::numeric_limits<std::size_t>::max)();
|
||||
next_elem += toml::visit(ser, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
next_elem += toml::visit(*this, item);
|
||||
}
|
||||
|
||||
// comma before newline.
|
||||
if(next_elem.back() == '\n') {next_elem.pop_back();}
|
||||
if(!next_elem.empty() && next_elem.back() == '\n') {next_elem.pop_back();}
|
||||
|
||||
// if current line does not exceeds the width limit, continue.
|
||||
if(current_line.size() + next_elem.size() + 1 < this->width_)
|
||||
@@ -411,7 +454,10 @@ struct serializer
|
||||
}
|
||||
if(!current_line.empty())
|
||||
{
|
||||
if(current_line.back() != '\n') {current_line += '\n';}
|
||||
if(!current_line.empty() && current_line.back() != '\n')
|
||||
{
|
||||
current_line += '\n';
|
||||
}
|
||||
token += current_line;
|
||||
}
|
||||
token += "]\n";
|
||||
@@ -467,7 +513,19 @@ struct serializer
|
||||
case '\f': {retval += "\\f"; break;}
|
||||
case '\n': {retval += "\\n"; break;}
|
||||
case '\r': {retval += "\\r"; break;}
|
||||
default : {retval += c; break;}
|
||||
default :
|
||||
{
|
||||
if((0x00 <= c && c <= 0x08) || (0x0A <= c && c <= 0x1F) || c == 0x7F)
|
||||
{
|
||||
retval += "\\u00";
|
||||
retval += char(48 + (c / 16));
|
||||
retval += char((c % 16 < 10 ? 48 : 55) + (c % 16));
|
||||
}
|
||||
else
|
||||
{
|
||||
retval += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
@@ -501,7 +559,21 @@ struct serializer
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {retval += *i; break;}
|
||||
default :
|
||||
{
|
||||
const auto c = *i;
|
||||
if((0x00 <= c && c <= 0x08) || (0x0A <= c && c <= 0x1F) || c == 0x7F)
|
||||
{
|
||||
retval += "\\u00";
|
||||
retval += char(48 + (c / 16));
|
||||
retval += char((c % 16 < 10 ? 48 : 55) + (c % 16));
|
||||
}
|
||||
else
|
||||
{
|
||||
retval += c;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// Only 1 or 2 consecutive `"`s are allowed in multiline basic string.
|
||||
@@ -557,8 +629,10 @@ struct serializer
|
||||
for(const auto& item : v)
|
||||
{
|
||||
if(is_first) {is_first = false;} else {token += ',';}
|
||||
token += visit(serializer((std::numeric_limits<std::size_t>::max)(),
|
||||
this->float_prec_, true), item);
|
||||
token += visit(serializer(
|
||||
(std::numeric_limits<std::size_t>::max)(), this->float_prec_,
|
||||
/* inlined */ true, /*no comment*/ false, /*keys*/ {},
|
||||
/*has_comment*/ !item.comments().empty()), item);
|
||||
}
|
||||
token += ']';
|
||||
return token;
|
||||
@@ -577,8 +651,10 @@ struct serializer
|
||||
if(is_first) {is_first = false;} else {token += ',';}
|
||||
token += format_key(kv.first);
|
||||
token += '=';
|
||||
token += visit(serializer((std::numeric_limits<std::size_t>::max)(),
|
||||
this->float_prec_, true), kv.second);
|
||||
token += visit(serializer(
|
||||
(std::numeric_limits<std::size_t>::max)(), this->float_prec_,
|
||||
/* inlined */ true, /*no comment*/ false, /*keys*/ {},
|
||||
/*has_comment*/ !kv.second.comments().empty()), kv.second);
|
||||
}
|
||||
token += '}';
|
||||
return token;
|
||||
@@ -588,8 +664,16 @@ struct serializer
|
||||
{
|
||||
std::string token;
|
||||
|
||||
// print non-table stuff first. because after printing [foo.bar], the
|
||||
// remaining non-table values will be assigned into [foo.bar], not [foo]
|
||||
// print non-table elements first.
|
||||
// ```toml
|
||||
// [foo] # a table we're writing now here
|
||||
// key = "value" # <- non-table element, "key"
|
||||
// # ...
|
||||
// [foo.bar] # <- table element, "bar"
|
||||
// ```
|
||||
// because after printing [foo.bar], the remaining non-table values will
|
||||
// be assigned into [foo.bar], not [foo]. Those values should be printed
|
||||
// earlier.
|
||||
for(const auto& kv : v)
|
||||
{
|
||||
if(kv.second.is_table() || is_array_of_tables(kv.second))
|
||||
@@ -597,21 +681,16 @@ struct serializer
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!kv.second.comments().empty() && !no_comment_)
|
||||
{
|
||||
for(const auto& c : kv.second.comments())
|
||||
{
|
||||
token += '#';
|
||||
token += c;
|
||||
token += '\n';
|
||||
}
|
||||
}
|
||||
token += write_comments(kv.second);
|
||||
|
||||
const auto key_and_sep = format_key(kv.first) + " = ";
|
||||
const auto residual_width = (this->width_ > key_and_sep.size()) ?
|
||||
this->width_ - key_and_sep.size() : 0;
|
||||
token += key_and_sep;
|
||||
token += visit(serializer(residual_width, this->float_prec_, true),
|
||||
kv.second);
|
||||
token += visit(serializer(residual_width, this->float_prec_,
|
||||
/*can be inlined*/ true, /*no comment*/ false, /*keys*/ {},
|
||||
/*has_comment*/ !kv.second.comments().empty()), kv.second);
|
||||
|
||||
if(token.back() != '\n')
|
||||
{
|
||||
token += '\n';
|
||||
@@ -637,45 +716,172 @@ struct serializer
|
||||
ks.push_back(kv.first);
|
||||
|
||||
auto tmp = visit(serializer(this->width_, this->float_prec_,
|
||||
!multiline_table_printed, this->no_comment_, ks),
|
||||
kv.second);
|
||||
!multiline_table_printed, this->no_comment_, ks,
|
||||
/*has_comment*/ !kv.second.comments().empty()), kv.second);
|
||||
|
||||
// If it is the first time to print a multi-line table, it would be
|
||||
// helpful to separate normal key-value pair and subtables by a
|
||||
// newline.
|
||||
// (this checks if the current key-value pair contains newlines.
|
||||
// but it is not perfect because multi-line string can also contain
|
||||
// a newline. in such a case, an empty line will be written) TODO
|
||||
if((!multiline_table_printed) &&
|
||||
std::find(tmp.cbegin(), tmp.cend(), '\n') != tmp.cend())
|
||||
{
|
||||
multiline_table_printed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// still inline tables only.
|
||||
tmp += '\n';
|
||||
}
|
||||
token += '\n'; // separate key-value pairs and subtables
|
||||
|
||||
if(!kv.second.comments().empty() && !no_comment_)
|
||||
{
|
||||
for(const auto& c : kv.second.comments())
|
||||
token += write_comments(kv.second);
|
||||
token += tmp;
|
||||
|
||||
// care about recursive tables (all tables in each level prints
|
||||
// newline and there will be a full of newlines)
|
||||
if(tmp.substr(tmp.size() - 2, 2) != "\n\n" &&
|
||||
tmp.substr(tmp.size() - 4, 4) != "\r\n\r\n" )
|
||||
{
|
||||
token += '#';
|
||||
token += c;
|
||||
token += '\n';
|
||||
}
|
||||
}
|
||||
token += tmp;
|
||||
else
|
||||
{
|
||||
token += write_comments(kv.second);
|
||||
token += tmp;
|
||||
token += '\n';
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
std::string make_array_of_tables(const array_type& v) const
|
||||
{
|
||||
// if it's not inlined, we need to add `[[table.key]]`.
|
||||
// but if it can be inlined, we can format it as the following.
|
||||
// ```
|
||||
// table.key = [
|
||||
// {...},
|
||||
// # comment
|
||||
// {...},
|
||||
// ]
|
||||
// ```
|
||||
// This function checks if inlinization is possible or not, and then
|
||||
// format the array-of-tables in a proper way.
|
||||
//
|
||||
// Note about comments:
|
||||
//
|
||||
// If the array itself has a comment (value_has_comment_ == true), we
|
||||
// should try to make it inline.
|
||||
// ```toml
|
||||
// # comment about array
|
||||
// array = [
|
||||
// # comment about table element
|
||||
// {of = "table"}
|
||||
// ]
|
||||
// ```
|
||||
// If it is formatted as a multiline table, the two comments becomes
|
||||
// indistinguishable.
|
||||
// ```toml
|
||||
// # comment about array
|
||||
// # comment about table element
|
||||
// [[array]]
|
||||
// of = "table"
|
||||
// ```
|
||||
// So we need to try to make it inline, and it force-inlines regardless
|
||||
// of the line width limit.
|
||||
// It may fail if the element of a table has comment. In that case,
|
||||
// the array-of-tables will be formatted as a multiline table.
|
||||
if(this->can_be_inlined_ || this->value_has_comment_)
|
||||
{
|
||||
std::string token;
|
||||
if(!keys_.empty())
|
||||
{
|
||||
token += format_key(keys_.back());
|
||||
token += " = ";
|
||||
}
|
||||
|
||||
bool failed = false;
|
||||
token += "[\n";
|
||||
for(const auto& item : v)
|
||||
{
|
||||
// if an element of the table has a comment, the table
|
||||
// cannot be inlined.
|
||||
if(this->has_comment_inside(item.as_table()))
|
||||
{
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
// write comments for the table itself
|
||||
token += write_comments(item);
|
||||
|
||||
const auto t = this->make_inline_table(item.as_table());
|
||||
|
||||
if(t.size() + 1 > width_ || // +1 for the last comma {...},
|
||||
std::find(t.cbegin(), t.cend(), '\n') != t.cend())
|
||||
{
|
||||
// if the value itself has a comment, ignore the line width limit
|
||||
if( ! this->value_has_comment_)
|
||||
{
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
token += t;
|
||||
token += ",\n";
|
||||
}
|
||||
|
||||
if( ! failed)
|
||||
{
|
||||
token += "]\n";
|
||||
return token;
|
||||
}
|
||||
// if failed, serialize them as [[array.of.tables]].
|
||||
}
|
||||
|
||||
std::string token;
|
||||
for(const auto& item : v)
|
||||
{
|
||||
token += write_comments(item);
|
||||
token += "[[";
|
||||
token += format_keys(keys_);
|
||||
token += "]]\n";
|
||||
token += this->make_multiline_table(item.as_table());
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
std::string write_comments(const value_type& v) const
|
||||
{
|
||||
std::string retval;
|
||||
if(this->no_comment_) {return retval;}
|
||||
|
||||
for(const auto& c : v.comments())
|
||||
{
|
||||
retval += '#';
|
||||
retval += c;
|
||||
retval += '\n';
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool is_array_of_tables(const value_type& v) const
|
||||
{
|
||||
if(!v.is_array()) {return false;}
|
||||
const auto& a = v.as_array();
|
||||
return !a.empty() && a.front().is_table();
|
||||
if(!v.is_array() || v.as_array().empty()) {return false;}
|
||||
return is_array_of_tables(v.as_array());
|
||||
}
|
||||
bool is_array_of_tables(const array_type& v) const
|
||||
{
|
||||
// Since TOML v0.5.0, heterogeneous arrays are allowed. So we need to
|
||||
// check all the element in an array to check if the array is an array
|
||||
// of tables.
|
||||
return std::all_of(v.begin(), v.end(), [](const value_type& elem) {
|
||||
return elem.is_table();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool can_be_inlined_;
|
||||
bool no_comment_;
|
||||
bool value_has_comment_;
|
||||
int float_prec_;
|
||||
std::size_t width_;
|
||||
std::vector<toml::key> keys_;
|
||||
@@ -699,7 +905,7 @@ format(const basic_value<C, M, V>& v, std::size_t w = 80u,
|
||||
oss << v.comments();
|
||||
oss << '\n'; // to split the file comment from the first element
|
||||
}
|
||||
const auto serialized = visit(serializer<value_type>(w, fprec, no_comment, false), v);
|
||||
const auto serialized = visit(serializer<value_type>(w, fprec, false, no_comment), v);
|
||||
oss << serialized;
|
||||
return oss.str();
|
||||
}
|
||||
@@ -720,7 +926,7 @@ template<typename charT, typename traits>
|
||||
std::basic_ostream<charT, traits>&
|
||||
nocomment(std::basic_ostream<charT, traits>& os)
|
||||
{
|
||||
// by default, it is zero. and by defalut, it shows comments.
|
||||
// by default, it is zero. and by default, it shows comments.
|
||||
os.iword(detail::comment_index(os)) = 1;
|
||||
return os;
|
||||
}
|
||||
@@ -729,7 +935,7 @@ template<typename charT, typename traits>
|
||||
std::basic_ostream<charT, traits>&
|
||||
showcomment(std::basic_ostream<charT, traits>& os)
|
||||
{
|
||||
// by default, it is zero. and by defalut, it shows comments.
|
||||
// by default, it is zero. and by default, it shows comments.
|
||||
os.iword(detail::comment_index(os)) = 0;
|
||||
return os;
|
||||
}
|
||||
@@ -746,7 +952,7 @@ operator<<(std::basic_ostream<charT, traits>& os, const basic_value<C, M, V>& v)
|
||||
const int fprec = static_cast<int>(os.precision());
|
||||
os.width(0);
|
||||
|
||||
// by defualt, iword is initialized byl 0. And by default, toml11 outputs
|
||||
// by default, iword is initialized by 0. And by default, toml11 outputs
|
||||
// comments. So `0` means showcomment. 1 means nocommnet.
|
||||
const bool no_comment = (1 == os.iword(detail::comment_index(os)));
|
||||
|
||||
+10
-3
@@ -3,6 +3,7 @@
|
||||
#ifndef TOML11_SOURCE_LOCATION_HPP
|
||||
#define TOML11_SOURCE_LOCATION_HPP
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
|
||||
#include "region.hpp"
|
||||
|
||||
@@ -124,7 +125,7 @@ inline std::string format_underline(const std::string& message,
|
||||
|
||||
std::ostringstream retval;
|
||||
|
||||
if(colorize)
|
||||
if(color::should_color() || colorize)
|
||||
{
|
||||
retval << color::colorize; // turn on ANSI color
|
||||
}
|
||||
@@ -136,12 +137,18 @@ inline std::string format_underline(const std::string& message,
|
||||
// if it is "[error]", it removes that part from the message shown.
|
||||
if(message.size() > 7 && message.substr(0, 7) == "[error]")
|
||||
{
|
||||
retval << color::bold << color::red << "[error]" << color::reset
|
||||
retval
|
||||
#ifndef TOML11_NO_ERROR_PREFIX
|
||||
<< color::bold << color::red << "[error]" << color::reset
|
||||
#endif
|
||||
<< color::bold << message.substr(7) << color::reset << '\n';
|
||||
}
|
||||
else
|
||||
{
|
||||
retval << color::bold << color::red << "[error] " << color::reset
|
||||
retval
|
||||
#ifndef TOML11_NO_ERROR_PREFIX
|
||||
<< color::bold << color::red << "[error] " << color::reset
|
||||
#endif
|
||||
<< color::bold << message << color::reset << '\n';
|
||||
}
|
||||
|
||||
+6
-2
@@ -2,13 +2,17 @@
|
||||
// Distributed under the MIT License.
|
||||
#ifndef TOML11_STRING_HPP
|
||||
#define TOML11_STRING_HPP
|
||||
|
||||
#include "version.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L
|
||||
#if __has_include(<string_view>)
|
||||
#define TOML11_USING_STRING_VIEW 1
|
||||
#include <string_view>
|
||||
#endif
|
||||
#endif
|
||||
@@ -53,7 +57,7 @@ struct string
|
||||
string& operator+=(const std::string& rhs) {str += rhs; return *this;}
|
||||
string& operator+=(const string& rhs) {str += rhs.str; return *this;}
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#if defined(TOML11_USING_STRING_VIEW) && TOML11_USING_STRING_VIEW>0
|
||||
explicit string(std::string_view s): kind(string_t::basic), str(s){}
|
||||
string(std::string_view s, string_t k): kind(k), str(s){}
|
||||
|
||||
+36
-8
@@ -2,6 +2,11 @@
|
||||
// Distributed under the MIT License.
|
||||
#ifndef TOML11_TRAITS_HPP
|
||||
#define TOML11_TRAITS_HPP
|
||||
|
||||
#include "from.hpp"
|
||||
#include "into.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <forward_list>
|
||||
#include <string>
|
||||
@@ -9,7 +14,7 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L
|
||||
#if __has_include(<string_view>)
|
||||
#include <string_view>
|
||||
#endif // has_include(<string_view>)
|
||||
@@ -84,6 +89,22 @@ struct has_into_toml_method_impl
|
||||
static std::false_type check(...);
|
||||
};
|
||||
|
||||
struct has_specialized_from_impl
|
||||
{
|
||||
template<typename T>
|
||||
static std::false_type check(...);
|
||||
template<typename T, std::size_t S = sizeof(::toml::from<T>)>
|
||||
static std::true_type check(::toml::from<T>*);
|
||||
};
|
||||
struct has_specialized_into_impl
|
||||
{
|
||||
template<typename T>
|
||||
static std::false_type check(...);
|
||||
template<typename T, std::size_t S = sizeof(::toml::into<T>)>
|
||||
static std::true_type check(::toml::from<T>*);
|
||||
};
|
||||
|
||||
|
||||
/// Intel C++ compiler can not use decltype in parent class declaration, here
|
||||
/// is a hack to work around it. https://stackoverflow.com/a/23953090/4692076
|
||||
#ifdef __INTEL_COMPILER
|
||||
@@ -114,6 +135,11 @@ template<typename T>
|
||||
struct has_into_toml_method
|
||||
: decltype(has_into_toml_method_impl::check<T>(nullptr)){};
|
||||
|
||||
template<typename T>
|
||||
struct has_specialized_from : decltype(has_specialized_from_impl::check<T>(nullptr)){};
|
||||
template<typename T>
|
||||
struct has_specialized_into : decltype(has_specialized_into_impl::check<T>(nullptr)){};
|
||||
|
||||
#ifdef __INTEL_COMPILER
|
||||
#undef decltype
|
||||
#endif
|
||||
@@ -121,7 +147,7 @@ struct has_into_toml_method
|
||||
// ---------------------------------------------------------------------------
|
||||
// C++17 and/or/not
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L
|
||||
|
||||
using std::conjunction;
|
||||
using std::disjunction;
|
||||
@@ -183,8 +209,10 @@ template<typename T>
|
||||
struct is_container : conjunction<
|
||||
negation<is_map<T>>, // not a map
|
||||
negation<std::is_same<T, std::string>>, // not a std::string
|
||||
#if __cplusplus >= 201703L
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L
|
||||
#if __has_include(<string_view>)
|
||||
negation<std::is_same<T, std::string_view>>, // not a std::string_view
|
||||
#endif // has_include(<string_view>)
|
||||
#endif
|
||||
has_iterator<T>, // has T::iterator
|
||||
has_value_type<T> // has T::value_type
|
||||
@@ -206,7 +234,7 @@ struct is_basic_value<::toml::basic_value<C, M, V>>: std::true_type{};
|
||||
// ---------------------------------------------------------------------------
|
||||
// C++14 index_sequence
|
||||
|
||||
#if __cplusplus >= 201402L
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L
|
||||
|
||||
using std::index_sequence;
|
||||
using std::make_index_sequence;
|
||||
@@ -236,12 +264,12 @@ struct index_sequence_maker<0>
|
||||
template<std::size_t N>
|
||||
using make_index_sequence = typename index_sequence_maker<N-1>::type;
|
||||
|
||||
#endif // __cplusplus >= 2014
|
||||
#endif // cplusplus >= 2014
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// C++14 enable_if_t
|
||||
|
||||
#if __cplusplus >= 201402L
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L
|
||||
|
||||
using std::enable_if_t;
|
||||
|
||||
@@ -250,12 +278,12 @@ using std::enable_if_t;
|
||||
template<bool B, typename T>
|
||||
using enable_if_t = typename std::enable_if<B, T>::type;
|
||||
|
||||
#endif // __cplusplus >= 2014
|
||||
#endif // cplusplus >= 2014
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// return_type_of_t
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L && defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable>=201703
|
||||
|
||||
template<typename F, typename ... Args>
|
||||
using return_type_of_t = std::invoke_result_t<F, Args...>;
|
||||
+24
-1
@@ -21,9 +21,14 @@ class basic_value;
|
||||
using character = char;
|
||||
using key = std::string;
|
||||
|
||||
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ <= 4
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wshadow"
|
||||
#endif
|
||||
|
||||
using boolean = bool;
|
||||
using integer = std::int64_t;
|
||||
using floating = double; // "float" is a keyward, cannot use it here.
|
||||
using floating = double; // "float" is a keyword, cannot use it here.
|
||||
// the following stuffs are structs defined here, so aliases are not needed.
|
||||
// - string
|
||||
// - offset_datetime
|
||||
@@ -32,12 +37,26 @@ using floating = double; // "float" is a keyward, cannot use it here.
|
||||
// - local_date
|
||||
// - local_time
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
# pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
// default toml::value and default array/table. these are defined after defining
|
||||
// basic_value itself.
|
||||
// using value = basic_value<discard_comments, std::unordered_map, std::vector>;
|
||||
// using array = typename value::array_type;
|
||||
// using table = typename value::table_type;
|
||||
|
||||
// to avoid warnings about `value_t::integer` is "shadowing" toml::integer in
|
||||
// GCC -Wshadow=global.
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
# pragma GCC diagnostic push
|
||||
# if 7 <= __GNUC__
|
||||
# pragma GCC diagnostic ignored "-Wshadow=global"
|
||||
# else // gcc-6 or older
|
||||
# pragma GCC diagnostic ignored "-Wshadow"
|
||||
# endif
|
||||
#endif
|
||||
enum class value_t : std::uint8_t
|
||||
{
|
||||
empty = 0,
|
||||
@@ -52,6 +71,9 @@ enum class value_t : std::uint8_t
|
||||
array = 9,
|
||||
table = 10,
|
||||
};
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
# pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
template<typename charT, typename traits>
|
||||
inline std::basic_ostream<charT, traits>&
|
||||
@@ -147,4 +169,5 @@ template<typename T, typename V> struct is_exact_toml_type<T const volatile&, V>
|
||||
|
||||
} // detail
|
||||
} // toml
|
||||
|
||||
#endif// TOML11_TYPES_H
|
||||
+60
-3
@@ -7,8 +7,9 @@
|
||||
#include <utility>
|
||||
|
||||
#include "traits.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#if __cplusplus >= 201402L
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L
|
||||
# define TOML11_MARK_AS_DEPRECATED(msg) [[deprecated(msg)]]
|
||||
#elif defined(__GNUC__)
|
||||
# define TOML11_MARK_AS_DEPRECATED(msg) __attribute__((deprecated(msg)))
|
||||
@@ -21,7 +22,7 @@
|
||||
namespace toml
|
||||
{
|
||||
|
||||
#if __cplusplus >= 201402L
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L
|
||||
|
||||
using std::make_unique;
|
||||
|
||||
@@ -33,7 +34,7 @@ inline std::unique_ptr<T> make_unique(Ts&& ... args)
|
||||
return std::unique_ptr<T>(new T(std::forward<Ts>(args)...));
|
||||
}
|
||||
|
||||
#endif // __cplusplus >= 2014
|
||||
#endif // TOML11_CPLUSPLUS_STANDARD_VERSION >= 2014
|
||||
|
||||
namespace detail
|
||||
{
|
||||
@@ -89,5 +90,61 @@ T from_string(const std::string& str, T opt)
|
||||
return v;
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L
|
||||
template<typename T>
|
||||
decltype(auto) last_one(T&& tail) noexcept
|
||||
{
|
||||
return std::forward<T>(tail);
|
||||
}
|
||||
|
||||
template<typename T, typename ... Ts>
|
||||
decltype(auto) last_one(T&& /*head*/, Ts&& ... tail) noexcept
|
||||
{
|
||||
return last_one(std::forward<Ts>(tail)...);
|
||||
}
|
||||
#else // C++11
|
||||
// The following code
|
||||
// ```cpp
|
||||
// 1 | template<typename T, typename ... Ts>
|
||||
// 2 | auto last_one(T&& /*head*/, Ts&& ... tail)
|
||||
// 3 | -> decltype(last_one(std::forward<Ts>(tail)...))
|
||||
// 4 | {
|
||||
// 5 | return last_one(std::forward<Ts>(tail)...);
|
||||
// 6 | }
|
||||
// ```
|
||||
// does not work because the function `last_one(...)` is not yet defined at
|
||||
// line #3, so `decltype()` cannot deduce the type returned from `last_one`.
|
||||
// So we need to determine return type in a different way, like a meta func.
|
||||
|
||||
template<typename T, typename ... Ts>
|
||||
struct last_one_in_pack
|
||||
{
|
||||
using type = typename last_one_in_pack<Ts...>::type;
|
||||
};
|
||||
template<typename T>
|
||||
struct last_one_in_pack<T>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
template<typename ... Ts>
|
||||
using last_one_in_pack_t = typename last_one_in_pack<Ts...>::type;
|
||||
|
||||
template<typename T>
|
||||
T&& last_one(T&& tail) noexcept
|
||||
{
|
||||
return std::forward<T>(tail);
|
||||
}
|
||||
template<typename T, typename ... Ts>
|
||||
enable_if_t<(sizeof...(Ts) > 0), last_one_in_pack_t<Ts&& ...>>
|
||||
last_one(T&& /*head*/, Ts&& ... tail)
|
||||
{
|
||||
return last_one(std::forward<Ts>(tail)...);
|
||||
}
|
||||
|
||||
#endif
|
||||
} // detail
|
||||
|
||||
}// toml
|
||||
#endif // TOML11_UTILITY
|
||||
+108
-85
@@ -66,9 +66,20 @@ throw_key_not_found_error(const Value& v, const key& ky)
|
||||
// ```
|
||||
// It actually points to the top-level table at the first character,
|
||||
// not `[table]`. But it is too confusing. To avoid the confusion, the error
|
||||
// message should explicitly say "key not found in the top-level table".
|
||||
// message should explicitly say "key not found in the top-level table",
|
||||
// or "the parsed file is empty" if there is no content at all (0 bytes in file).
|
||||
const auto loc = v.location();
|
||||
if(loc.line() == 1 && loc.region() == 1)
|
||||
if(loc.line() == 1 && loc.region() == 0)
|
||||
{
|
||||
// First line with a zero-length region means "empty file".
|
||||
// The region will be generated at `parse_toml_file` function
|
||||
// if the file contains no bytes.
|
||||
throw std::out_of_range(format_underline(concat_to_string(
|
||||
"key \"", ky, "\" not found in the top-level table"), {
|
||||
{loc, "the parsed file is empty"}
|
||||
}));
|
||||
}
|
||||
else if(loc.line() == 1 && loc.region() == 1)
|
||||
{
|
||||
// Here it assumes that top-level table starts at the first character.
|
||||
// The region corresponds to the top-level table will be generated at
|
||||
@@ -90,7 +101,7 @@ throw_key_not_found_error(const Value& v, const key& ky)
|
||||
// ```toml
|
||||
// a = {b = "c"}
|
||||
// ```
|
||||
// toml11 consideres the inline table body as the table region. Here,
|
||||
// toml11 considers the inline table body as the table region. Here,
|
||||
// `{b = "c"}` is the region of the table "a". The size of the region
|
||||
// is 9, not 1. The shotest inline table still has two characters, `{`
|
||||
// and `}`. The size cannot be 1.
|
||||
@@ -99,7 +110,7 @@ throw_key_not_found_error(const Value& v, const key& ky)
|
||||
// ```toml
|
||||
// [a]
|
||||
// ```
|
||||
// toml11 consideres the whole table key as the table region. Here,
|
||||
// toml11 considers the whole table key as the table region. Here,
|
||||
// `[a]` is the table region. The size is 3, not 1.
|
||||
//
|
||||
throw std::out_of_range(format_underline(concat_to_string(
|
||||
@@ -236,6 +247,7 @@ class basic_value
|
||||
}
|
||||
basic_value& operator=(const basic_value& v)
|
||||
{
|
||||
if(this == std::addressof(v)) {return *this;}
|
||||
this->cleanup();
|
||||
this->region_info_ = v.region_info_;
|
||||
this->comments_ = v.comments_;
|
||||
@@ -258,6 +270,7 @@ class basic_value
|
||||
}
|
||||
basic_value& operator=(basic_value&& v)
|
||||
{
|
||||
if(this == std::addressof(v)) {return *this;}
|
||||
this->cleanup();
|
||||
this->region_info_ = std::move(v.region_info_);
|
||||
this->comments_ = std::move(v.comments_);
|
||||
@@ -281,9 +294,9 @@ class basic_value
|
||||
|
||||
// overwrite comments ----------------------------------------------------
|
||||
|
||||
basic_value(const basic_value& v, std::vector<std::string> comments)
|
||||
basic_value(const basic_value& v, std::vector<std::string> com)
|
||||
: type_(v.type()), region_info_(v.region_info_),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
switch(v.type())
|
||||
{
|
||||
@@ -301,9 +314,9 @@ class basic_value
|
||||
}
|
||||
}
|
||||
|
||||
basic_value(basic_value&& v, std::vector<std::string> comments)
|
||||
basic_value(basic_value&& v, std::vector<std::string> com)
|
||||
: type_(v.type()), region_info_(std::move(v.region_info_)),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
switch(this->type_) // here this->type_ is already initialized
|
||||
{
|
||||
@@ -359,9 +372,9 @@ class basic_value
|
||||
template<typename C,
|
||||
template<typename ...> class T,
|
||||
template<typename ...> class A>
|
||||
basic_value(const basic_value<C, T, A>& v, std::vector<std::string> comments)
|
||||
basic_value(const basic_value<C, T, A>& v, std::vector<std::string> com)
|
||||
: type_(v.type()), region_info_(v.region_info_),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
switch(v.type())
|
||||
{
|
||||
@@ -443,10 +456,10 @@ class basic_value
|
||||
assigner(this->boolean_, b);
|
||||
return *this;
|
||||
}
|
||||
basic_value(boolean b, std::vector<std::string> comments)
|
||||
basic_value(boolean b, std::vector<std::string> com)
|
||||
: type_(value_t::boolean),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->boolean_, b);
|
||||
}
|
||||
@@ -478,10 +491,10 @@ class basic_value
|
||||
template<typename T, typename std::enable_if<detail::conjunction<
|
||||
std::is_integral<T>, detail::negation<std::is_same<T, boolean>>>::value,
|
||||
std::nullptr_t>::type = nullptr>
|
||||
basic_value(T i, std::vector<std::string> comments)
|
||||
basic_value(T i, std::vector<std::string> com)
|
||||
: type_(value_t::integer),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->integer_, static_cast<integer>(i));
|
||||
}
|
||||
@@ -511,10 +524,10 @@ class basic_value
|
||||
|
||||
template<typename T, typename std::enable_if<
|
||||
std::is_floating_point<T>::value, std::nullptr_t>::type = nullptr>
|
||||
basic_value(T f, std::vector<std::string> comments)
|
||||
basic_value(T f, std::vector<std::string> com)
|
||||
: type_(value_t::floating),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->floating_, f);
|
||||
}
|
||||
@@ -535,10 +548,10 @@ class basic_value
|
||||
assigner(this->string_, s);
|
||||
return *this;
|
||||
}
|
||||
basic_value(toml::string s, std::vector<std::string> comments)
|
||||
basic_value(toml::string s, std::vector<std::string> com)
|
||||
: type_(value_t::string),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->string_, std::move(s));
|
||||
}
|
||||
@@ -563,17 +576,17 @@ class basic_value
|
||||
{
|
||||
assigner(this->string_, toml::string(std::move(s), kind));
|
||||
}
|
||||
basic_value(std::string s, std::vector<std::string> comments)
|
||||
basic_value(std::string s, std::vector<std::string> com)
|
||||
: type_(value_t::string),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->string_, toml::string(std::move(s)));
|
||||
}
|
||||
basic_value(std::string s, string_t kind, std::vector<std::string> comments)
|
||||
basic_value(std::string s, string_t kind, std::vector<std::string> com)
|
||||
: type_(value_t::string),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->string_, toml::string(std::move(s), kind));
|
||||
}
|
||||
@@ -598,22 +611,22 @@ class basic_value
|
||||
{
|
||||
assigner(this->string_, toml::string(std::string(s), kind));
|
||||
}
|
||||
basic_value(const char* s, std::vector<std::string> comments)
|
||||
basic_value(const char* s, std::vector<std::string> com)
|
||||
: type_(value_t::string),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->string_, toml::string(std::string(s)));
|
||||
}
|
||||
basic_value(const char* s, string_t kind, std::vector<std::string> comments)
|
||||
basic_value(const char* s, string_t kind, std::vector<std::string> com)
|
||||
: type_(value_t::string),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->string_, toml::string(std::string(s), kind));
|
||||
}
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#if defined(TOML11_USING_STRING_VIEW) && TOML11_USING_STRING_VIEW>0
|
||||
basic_value(std::string_view s)
|
||||
: type_(value_t::string),
|
||||
region_info_(std::make_shared<region_base>(region_base{}))
|
||||
@@ -628,10 +641,10 @@ class basic_value
|
||||
assigner(this->string_, toml::string(s));
|
||||
return *this;
|
||||
}
|
||||
basic_value(std::string_view s, std::vector<std::string> comments)
|
||||
basic_value(std::string_view s, std::vector<std::string> com)
|
||||
: type_(value_t::string),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->string_, toml::string(s));
|
||||
}
|
||||
@@ -641,10 +654,10 @@ class basic_value
|
||||
{
|
||||
assigner(this->string_, toml::string(s, kind));
|
||||
}
|
||||
basic_value(std::string_view s, string_t kind, std::vector<std::string> comments)
|
||||
basic_value(std::string_view s, string_t kind, std::vector<std::string> com)
|
||||
: type_(value_t::string),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->string_, toml::string(s, kind));
|
||||
}
|
||||
@@ -666,10 +679,10 @@ class basic_value
|
||||
assigner(this->local_date_, ld);
|
||||
return *this;
|
||||
}
|
||||
basic_value(const local_date& ld, std::vector<std::string> comments)
|
||||
basic_value(const local_date& ld, std::vector<std::string> com)
|
||||
: type_(value_t::local_date),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->local_date_, ld);
|
||||
}
|
||||
@@ -682,10 +695,10 @@ class basic_value
|
||||
{
|
||||
assigner(this->local_time_, lt);
|
||||
}
|
||||
basic_value(const local_time& lt, std::vector<std::string> comments)
|
||||
basic_value(const local_time& lt, std::vector<std::string> com)
|
||||
: type_(value_t::local_time),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->local_time_, lt);
|
||||
}
|
||||
@@ -707,10 +720,10 @@ class basic_value
|
||||
}
|
||||
template<typename Rep, typename Period>
|
||||
basic_value(const std::chrono::duration<Rep, Period>& dur,
|
||||
std::vector<std::string> comments)
|
||||
std::vector<std::string> com)
|
||||
: type_(value_t::local_time),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->local_time_, local_time(dur));
|
||||
}
|
||||
@@ -732,10 +745,10 @@ class basic_value
|
||||
{
|
||||
assigner(this->local_datetime_, ldt);
|
||||
}
|
||||
basic_value(const local_datetime& ldt, std::vector<std::string> comments)
|
||||
basic_value(const local_datetime& ldt, std::vector<std::string> com)
|
||||
: type_(value_t::local_datetime),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->local_datetime_, ldt);
|
||||
}
|
||||
@@ -756,10 +769,10 @@ class basic_value
|
||||
{
|
||||
assigner(this->offset_datetime_, odt);
|
||||
}
|
||||
basic_value(const offset_datetime& odt, std::vector<std::string> comments)
|
||||
basic_value(const offset_datetime& odt, std::vector<std::string> com)
|
||||
: type_(value_t::offset_datetime),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->offset_datetime_, odt);
|
||||
}
|
||||
@@ -778,10 +791,10 @@ class basic_value
|
||||
assigner(this->offset_datetime_, offset_datetime(tp));
|
||||
}
|
||||
basic_value(const std::chrono::system_clock::time_point& tp,
|
||||
std::vector<std::string> comments)
|
||||
std::vector<std::string> com)
|
||||
: type_(value_t::offset_datetime),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->offset_datetime_, offset_datetime(tp));
|
||||
}
|
||||
@@ -802,10 +815,10 @@ class basic_value
|
||||
{
|
||||
assigner(this->array_, ary);
|
||||
}
|
||||
basic_value(const array_type& ary, std::vector<std::string> comments)
|
||||
basic_value(const array_type& ary, std::vector<std::string> com)
|
||||
: type_(value_t::array),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->array_, ary);
|
||||
}
|
||||
@@ -833,10 +846,10 @@ class basic_value
|
||||
template<typename T, typename std::enable_if<
|
||||
std::is_convertible<T, value_type>::value,
|
||||
std::nullptr_t>::type = nullptr>
|
||||
basic_value(std::initializer_list<T> list, std::vector<std::string> comments)
|
||||
basic_value(std::initializer_list<T> list, std::vector<std::string> com)
|
||||
: type_(value_t::array),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
array_type ary(list.begin(), list.end());
|
||||
assigner(this->array_, std::move(ary));
|
||||
@@ -876,10 +889,10 @@ class basic_value
|
||||
detail::negation<std::is_same<T, array_type>>,
|
||||
detail::is_container<T>
|
||||
>::value, std::nullptr_t>::type = nullptr>
|
||||
basic_value(const T& list, std::vector<std::string> comments)
|
||||
basic_value(const T& list, std::vector<std::string> com)
|
||||
: type_(value_t::array),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
static_assert(std::is_convertible<typename T::value_type, value_type>::value,
|
||||
"elements of a container should be convertible to toml::value");
|
||||
@@ -915,10 +928,10 @@ class basic_value
|
||||
{
|
||||
assigner(this->table_, tab);
|
||||
}
|
||||
basic_value(const table_type& tab, std::vector<std::string> comments)
|
||||
basic_value(const table_type& tab, std::vector<std::string> com)
|
||||
: type_(value_t::table),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
assigner(this->table_, tab);
|
||||
}
|
||||
@@ -943,10 +956,10 @@ class basic_value
|
||||
}
|
||||
|
||||
basic_value(std::initializer_list<std::pair<key, basic_value>> list,
|
||||
std::vector<std::string> comments)
|
||||
std::vector<std::string> com)
|
||||
: type_(value_t::table),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
table_type tab;
|
||||
for(const auto& elem : list) {tab[elem.first] = elem.second;}
|
||||
@@ -982,10 +995,10 @@ class basic_value
|
||||
detail::negation<std::is_same<Map, table_type>>,
|
||||
detail::is_map<Map>
|
||||
>::value, std::nullptr_t>::type = nullptr>
|
||||
basic_value(const Map& mp, std::vector<std::string> comments)
|
||||
basic_value(const Map& mp, std::vector<std::string> com)
|
||||
: type_(value_t::table),
|
||||
region_info_(std::make_shared<region_base>(region_base{})),
|
||||
comments_(std::move(comments))
|
||||
comments_(std::move(com))
|
||||
{
|
||||
table_type tab;
|
||||
for(const auto& elem : mp) {tab[elem.first] = elem.second;}
|
||||
@@ -1017,8 +1030,8 @@ class basic_value
|
||||
|
||||
template<typename T, typename std::enable_if<
|
||||
detail::has_into_toml_method<T>::value, std::nullptr_t>::type = nullptr>
|
||||
basic_value(const T& ud, std::vector<std::string> comments)
|
||||
: basic_value(ud.into_toml(), std::move(comments))
|
||||
basic_value(const T& ud, std::vector<std::string> com)
|
||||
: basic_value(ud.into_toml(), std::move(com))
|
||||
{}
|
||||
template<typename T, typename std::enable_if<
|
||||
detail::has_into_toml_method<T>::value, std::nullptr_t>::type = nullptr>
|
||||
@@ -1033,8 +1046,8 @@ class basic_value
|
||||
template<typename T, std::size_t S = sizeof(::toml::into<T>)>
|
||||
basic_value(const T& ud): basic_value(::toml::into<T>::into_toml(ud)) {}
|
||||
template<typename T, std::size_t S = sizeof(::toml::into<T>)>
|
||||
basic_value(const T& ud, std::vector<std::string> comments)
|
||||
: basic_value(::toml::into<T>::into_toml(ud), std::move(comments))
|
||||
basic_value(const T& ud, std::vector<std::string> com)
|
||||
: basic_value(::toml::into<T>::into_toml(ud), std::move(com))
|
||||
{}
|
||||
template<typename T, std::size_t S = sizeof(::toml::into<T>)>
|
||||
basic_value& operator=(const T& ud)
|
||||
@@ -1047,10 +1060,10 @@ class basic_value
|
||||
//
|
||||
// Those constructors take detail::region that contains parse result.
|
||||
|
||||
basic_value(boolean b, detail::region reg)
|
||||
basic_value(boolean b, detail::region reg, std::vector<std::string> cm)
|
||||
: type_(value_t::boolean),
|
||||
region_info_(std::make_shared<detail::region>(std::move(reg))),
|
||||
comments_(region_info_->comments())
|
||||
comments_(std::move(cm))
|
||||
{
|
||||
assigner(this->boolean_, b);
|
||||
}
|
||||
@@ -1058,68 +1071,75 @@ class basic_value
|
||||
detail::conjunction<
|
||||
std::is_integral<T>, detail::negation<std::is_same<T, boolean>>
|
||||
>::value, std::nullptr_t>::type = nullptr>
|
||||
basic_value(T i, detail::region reg)
|
||||
basic_value(T i, detail::region reg, std::vector<std::string> cm)
|
||||
: type_(value_t::integer),
|
||||
region_info_(std::make_shared<detail::region>(std::move(reg))),
|
||||
comments_(region_info_->comments())
|
||||
comments_(std::move(cm))
|
||||
{
|
||||
assigner(this->integer_, static_cast<integer>(i));
|
||||
}
|
||||
template<typename T, typename std::enable_if<
|
||||
std::is_floating_point<T>::value, std::nullptr_t>::type = nullptr>
|
||||
basic_value(T f, detail::region reg)
|
||||
basic_value(T f, detail::region reg, std::vector<std::string> cm)
|
||||
: type_(value_t::floating),
|
||||
region_info_(std::make_shared<detail::region>(std::move(reg))),
|
||||
comments_(region_info_->comments())
|
||||
comments_(std::move(cm))
|
||||
{
|
||||
assigner(this->floating_, static_cast<floating>(f));
|
||||
}
|
||||
basic_value(toml::string s, detail::region reg)
|
||||
basic_value(toml::string s, detail::region reg,
|
||||
std::vector<std::string> cm)
|
||||
: type_(value_t::string),
|
||||
region_info_(std::make_shared<detail::region>(std::move(reg))),
|
||||
comments_(region_info_->comments())
|
||||
comments_(std::move(cm))
|
||||
{
|
||||
assigner(this->string_, std::move(s));
|
||||
}
|
||||
basic_value(const local_date& ld, detail::region reg)
|
||||
basic_value(const local_date& ld, detail::region reg,
|
||||
std::vector<std::string> cm)
|
||||
: type_(value_t::local_date),
|
||||
region_info_(std::make_shared<detail::region>(std::move(reg))),
|
||||
comments_(region_info_->comments())
|
||||
comments_(std::move(cm))
|
||||
{
|
||||
assigner(this->local_date_, ld);
|
||||
}
|
||||
basic_value(const local_time& lt, detail::region reg)
|
||||
basic_value(const local_time& lt, detail::region reg,
|
||||
std::vector<std::string> cm)
|
||||
: type_(value_t::local_time),
|
||||
region_info_(std::make_shared<detail::region>(std::move(reg))),
|
||||
comments_(region_info_->comments())
|
||||
comments_(std::move(cm))
|
||||
{
|
||||
assigner(this->local_time_, lt);
|
||||
}
|
||||
basic_value(const local_datetime& ldt, detail::region reg)
|
||||
basic_value(const local_datetime& ldt, detail::region reg,
|
||||
std::vector<std::string> cm)
|
||||
: type_(value_t::local_datetime),
|
||||
region_info_(std::make_shared<detail::region>(std::move(reg))),
|
||||
comments_(region_info_->comments())
|
||||
comments_(std::move(cm))
|
||||
{
|
||||
assigner(this->local_datetime_, ldt);
|
||||
}
|
||||
basic_value(const offset_datetime& odt, detail::region reg)
|
||||
basic_value(const offset_datetime& odt, detail::region reg,
|
||||
std::vector<std::string> cm)
|
||||
: type_(value_t::offset_datetime),
|
||||
region_info_(std::make_shared<detail::region>(std::move(reg))),
|
||||
comments_(region_info_->comments())
|
||||
comments_(std::move(cm))
|
||||
{
|
||||
assigner(this->offset_datetime_, odt);
|
||||
}
|
||||
basic_value(const array_type& ary, detail::region reg)
|
||||
basic_value(const array_type& ary, detail::region reg,
|
||||
std::vector<std::string> cm)
|
||||
: type_(value_t::array),
|
||||
region_info_(std::make_shared<detail::region>(std::move(reg))),
|
||||
comments_(region_info_->comments())
|
||||
comments_(std::move(cm))
|
||||
{
|
||||
assigner(this->array_, ary);
|
||||
}
|
||||
basic_value(const table_type& tab, detail::region reg)
|
||||
basic_value(const table_type& tab, detail::region reg,
|
||||
std::vector<std::string> cm)
|
||||
: type_(value_t::table),
|
||||
region_info_(std::make_shared<detail::region>(std::move(reg))),
|
||||
comments_(region_info_->comments())
|
||||
comments_(std::move(cm))
|
||||
{
|
||||
assigner(this->table_, tab);
|
||||
}
|
||||
@@ -1127,8 +1147,10 @@ class basic_value
|
||||
template<typename T, typename std::enable_if<
|
||||
detail::is_exact_toml_type<T, value_type>::value,
|
||||
std::nullptr_t>::type = nullptr>
|
||||
basic_value(std::pair<T, detail::region> parse_result)
|
||||
: basic_value(std::move(parse_result.first), std::move(parse_result.second))
|
||||
basic_value(std::pair<T, detail::region> parse_result, std::vector<std::string> com)
|
||||
: basic_value(std::move(parse_result.first),
|
||||
std::move(parse_result.second),
|
||||
std::move(com))
|
||||
{}
|
||||
|
||||
// type checking and casting ============================================
|
||||
@@ -1692,9 +1714,9 @@ class basic_value
|
||||
{
|
||||
switch(this->type_)
|
||||
{
|
||||
case value_t::string : {string_.~string(); return;}
|
||||
case value_t::array : {array_.~array_storage(); return;}
|
||||
case value_t::table : {table_.~table_storage(); return;}
|
||||
case value_t::string : {string_.~string(); return;}
|
||||
case value_t::array : {array_.~array_storage(); return;}
|
||||
case value_t::table : {table_.~table_storage(); return;}
|
||||
default : return;
|
||||
}
|
||||
}
|
||||
@@ -1730,7 +1752,8 @@ class basic_value
|
||||
};
|
||||
|
||||
// default toml::value and default array/table.
|
||||
using value = basic_value<discard_comments, std::unordered_map, std::vector>;
|
||||
// TOML11_DEFAULT_COMMENT_STRATEGY is defined in comments.hpp
|
||||
using value = basic_value<TOML11_DEFAULT_COMMENT_STRATEGY, std::unordered_map, std::vector>;
|
||||
using array = typename value::array_type;
|
||||
using table = typename value::table_type;
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#ifndef TOML11_VERSION_HPP
|
||||
#define TOML11_VERSION_HPP
|
||||
|
||||
// This file checks C++ version.
|
||||
|
||||
#ifndef __cplusplus
|
||||
# error "__cplusplus is not defined"
|
||||
#endif
|
||||
|
||||
// Since MSVC does not define `__cplusplus` correctly unless you pass
|
||||
// `/Zc:__cplusplus` when compiling, the workaround macros are added.
|
||||
// Those enables you to define version manually or to use MSVC specific
|
||||
// version macro automatically.
|
||||
//
|
||||
// The value of `__cplusplus` macro is defined in the C++ standard spec, but
|
||||
// MSVC ignores the value, maybe because of backward compatibility. Instead,
|
||||
// MSVC defines _MSVC_LANG that has the same value as __cplusplus defined in
|
||||
// the C++ standard. First we check the manual version definition, and then
|
||||
// we check if _MSVC_LANG is defined. If neither, use normal `__cplusplus`.
|
||||
//
|
||||
// FYI: https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-170
|
||||
// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170
|
||||
//
|
||||
#if defined(TOML11_ENFORCE_CXX11)
|
||||
# define TOML11_CPLUSPLUS_STANDARD_VERSION 201103L
|
||||
#elif defined(TOML11_ENFORCE_CXX14)
|
||||
# define TOML11_CPLUSPLUS_STANDARD_VERSION 201402L
|
||||
#elif defined(TOML11_ENFORCE_CXX17)
|
||||
# define TOML11_CPLUSPLUS_STANDARD_VERSION 201703L
|
||||
#elif defined(TOML11_ENFORCE_CXX20)
|
||||
# define TOML11_CPLUSPLUS_STANDARD_VERSION 202002L
|
||||
#elif defined(_MSVC_LANG) && defined(_MSC_VER) && 1910 <= _MSC_VER
|
||||
# define TOML11_CPLUSPLUS_STANDARD_VERSION _MSVC_LANG
|
||||
#else
|
||||
# define TOML11_CPLUSPLUS_STANDARD_VERSION __cplusplus
|
||||
#endif
|
||||
|
||||
#if TOML11_CPLUSPLUS_STANDARD_VERSION < 201103L && _MSC_VER < 1900
|
||||
# error "toml11 requires C++11 or later."
|
||||
#endif
|
||||
|
||||
#endif// TOML11_VERSION_HPP
|
||||
Reference in New Issue
Block a user