summaryrefslogtreecommitdiff
path: root/tomlplusplus/tools
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:44:05 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:44:05 +0300
commit0b24459ac12b6cf9fd5a401d647796ca254a8fa8 (patch)
treef2fd66e2476976a51e2a51330fd95dc6e87b24c1 /tomlplusplus/tools
parentb85e90fc3480da0e6a48da73201a0b22488cc650 (diff)
parent1c8b7466e4946fcc3bf20484c0e1d001202cca5a (diff)
downloadProject-Tick-0b24459ac12b6cf9fd5a401d647796ca254a8fa8.tar.gz
Project-Tick-0b24459ac12b6cf9fd5a401d647796ca254a8fa8.zip
Add 'tomlplusplus/' from commit '1c8b7466e4946fcc3bf20484c0e1d001202cca5a'
git-subtree-dir: tomlplusplus git-subtree-mainline: b85e90fc3480da0e6a48da73201a0b22488cc650 git-subtree-split: 1c8b7466e4946fcc3bf20484c0e1d001202cca5a
Diffstat (limited to 'tomlplusplus/tools')
-rwxr-xr-xtomlplusplus/tools/ci_single_header_check.py31
-rw-r--r--tomlplusplus/tools/clang_format.bat41
-rwxr-xr-xtomlplusplus/tools/generate_conformance_tests.py635
-rw-r--r--tomlplusplus/tools/generate_single_header.bat19
-rwxr-xr-xtomlplusplus/tools/generate_single_header.py265
-rwxr-xr-xtomlplusplus/tools/generate_windows_test_targets.py181
-rw-r--r--tomlplusplus/tools/requirements.txt4
-rw-r--r--tomlplusplus/tools/utils.py55
-rw-r--r--tomlplusplus/tools/version.py68
9 files changed, 1299 insertions, 0 deletions
diff --git a/tomlplusplus/tools/ci_single_header_check.py b/tomlplusplus/tools/ci_single_header_check.py
new file mode 100755
index 0000000000..aeed9498ca
--- /dev/null
+++ b/tomlplusplus/tools/ci_single_header_check.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+# This file is a part of toml++ and is subject to the the terms of the MIT license.
+# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
+# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
+# SPDX-License-Identifier: MIT
+
+import sys
+import utils
+from pathlib import Path
+
+
+def main():
+ hpp_path = Path(Path(__file__).resolve().parents[1], 'toml.hpp').resolve()
+ hash1 = utils.sha1(utils.read_all_text_from_file(hpp_path, logger=True))
+ print(rf'Hash 1: {hash1}')
+ utils.run_python_script(r'generate_single_header.py')
+ hash2 = utils.sha1(utils.read_all_text_from_file(hpp_path, logger=True))
+ print(rf'Hash 2: {hash2}')
+ if (hash1 != hash2):
+ print(
+ "toml.hpp wasn't up-to-date!\nRun generate_single_header.py before your commit to prevent this error.",
+ file=sys.stderr
+ )
+ return 1
+ print("toml.hpp was up-to-date")
+ return 0
+
+
+
+if __name__ == '__main__':
+ utils.run(main, verbose=True)
diff --git a/tomlplusplus/tools/clang_format.bat b/tomlplusplus/tools/clang_format.bat
new file mode 100644
index 0000000000..7f2a139b7a
--- /dev/null
+++ b/tomlplusplus/tools/clang_format.bat
@@ -0,0 +1,41 @@
+@ECHO off
+SETLOCAL enableextensions enabledelayedexpansion
+PUSHD .
+CD /d "%~dp0\.."
+
+REM --------------------------------------------------------------------------------------
+REM Runs clang format on all the C++ files in the project
+REM --------------------------------------------------------------------------------------
+
+WHERE /Q clang-format
+IF %ERRORLEVEL% NEQ 0 (
+ ECHO Could not find clang-format
+ PAUSE
+ POPD
+ ENDLOCAL
+ EXIT /B %ERRORLEVEL%
+)
+
+CALL :RunClangFormatOnDirectories ^
+ include\toml++ ^
+ include\toml++\impl ^
+ tests ^
+ examples
+
+POPD
+@ENDLOCAL
+EXIT /B 0
+
+:RunClangFormatOnDirectories
+(
+ FOR %%i IN (%*) DO (
+ IF EXIST "%%~i" (
+ ECHO Formatting files in "%%~i"
+ clang-format --style=file -i "%%~i\*.cpp" >nul 2>&1
+ clang-format --style=file -i "%%~i\*.h" >nul 2>&1
+ clang-format --style=file -i "%%~i\*.hpp" >nul 2>&1
+ clang-format --style=file -i "%%~i\*.inl" >nul 2>&1
+ )
+ )
+ EXIT /B
+)
diff --git a/tomlplusplus/tools/generate_conformance_tests.py b/tomlplusplus/tools/generate_conformance_tests.py
new file mode 100755
index 0000000000..0da53ffb8a
--- /dev/null
+++ b/tomlplusplus/tools/generate_conformance_tests.py
@@ -0,0 +1,635 @@
+#!/usr/bin/env python3
+# This file is a part of toml++ and is subject to the the terms of the MIT license.
+# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
+# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
+# SPDX-License-Identifier: MIT
+
+import sys
+import utils
+import re
+import json
+import yaml
+import math
+import dateutil.parser
+from pathlib import Path
+from datetime import datetime, date, time
+from io import StringIO
+
+
+
+def sanitize(s):
+ s = re.sub(r'[ _:;\/-]+', '_', s, 0, re.I | re.M)
+ if s in ('bool', 'float', 'int', 'double', 'auto', 'array', 'table'):
+ s = s + '_'
+ return s
+
+
+
+def is_problematic_control_char(val):
+ if isinstance(val, str):
+ val = ord(val)
+ return (0x00 <= val <= 0x08) or (0x0B <= val <= 0x1F) or val == 0x7F
+
+
+
+def has_problematic_control_chars(val):
+ for c in val:
+ if is_problematic_control_char(c):
+ return True
+ return False
+
+
+
+def requires_unicode(s):
+ for c in s:
+ if ord(c) > 127:
+ return True
+ return False
+
+
+
+def make_string_literal(val, escape_all = False, escape_any = False):
+ get_ord = (lambda c: ord(c)) if isinstance(val, str) else (lambda c: c)
+ if escape_all:
+ with StringIO() as buf:
+ line_len = 0
+ for c in val:
+ c_ord = get_ord(c)
+ if not line_len:
+ buf.write('\n\t\t"')
+ line_len += 1
+ if c_ord <= 0xFF:
+ buf.write(rf'\x{c_ord:02X}')
+ line_len += 4
+ elif c_ord <= 0xFFFF:
+ buf.write(rf'\u{c_ord:04X}')
+ line_len += 6
+ else:
+ buf.write(rf'\U{c_ord:08X}')
+ line_len += 10
+ if line_len >= 100:
+ buf.write('"')
+ line_len = 0
+ if line_len:
+ buf.write('"')
+ return buf.getvalue()
+ elif escape_any:
+ with StringIO() as buf:
+ buf.write(r'"')
+ for c in val:
+ c_ord = get_ord(c)
+ if c_ord == 0x22: # "
+ buf.write(r'\"')
+ elif c_ord == 0x5C: # \
+ buf.write(r'\\')
+ elif c_ord == 0x0A: # \n
+ buf.write('\\n"\n\t\t"')
+ elif c_ord == 0x0B: # \v
+ buf.write(r'\v')
+ elif c_ord == 0x0C: # \f
+ buf.write(r'\f')
+ elif c_ord == 0x0D: # \r
+ buf.write(r'\r')
+ elif is_problematic_control_char(c_ord):
+ if c_ord <= 0xFF:
+ buf.write(rf'\x{c_ord:02X}')
+ elif c_ord <= 0xFFFF:
+ buf.write(rf'\u{c_ord:04X}')
+ else:
+ buf.write(rf'\U{c_ord:08X}')
+ else:
+ buf.write(chr(c_ord))
+ buf.write(r'"')
+ return buf.getvalue()
+ else:
+ return rf'R"({val})"'
+
+
+
+
+def python_value_to_tomlpp(val):
+ if isinstance(val, str):
+ if not val:
+ return r'""sv'
+ elif re.fullmatch(r'^[+-]?[0-9]+[eE][+-]?[0-9]+$', val, re.M):
+ return str(float(val))
+ else:
+ return rf'{make_string_literal(val, escape_any = has_problematic_control_chars(val))}sv'
+ elif isinstance(val, bool):
+ return 'true' if val else 'false'
+ elif isinstance(val, float):
+ if math.isinf(val):
+ return f'{"-" if val < 0.0 else ""}std::numeric_limits<double>::infinity()'
+ elif math.isnan(val):
+ return 'std::numeric_limits<double>::quiet_NaN()'
+ else:
+ return str(val)
+ elif isinstance(val, int):
+ if val == 9223372036854775807:
+ return 'std::numeric_limits<int64_t>::max()'
+ elif val == -9223372036854775808:
+ return 'std::numeric_limits<int64_t>::min()'
+ else:
+ return str(val)
+ elif isinstance(val, (TomlPPArray, TomlPPTable)):
+ return str(val)
+ elif isinstance(val, (date, time, datetime)):
+ date_args = None
+ if isinstance(val, (date, datetime)):
+ date_args = rf'{val.year}, {val.month}, {val.day}'
+ time_args = None
+ if isinstance(val, (time, datetime)):
+ time_args = rf'{val.hour}, {val.minute}'
+ if val.second and val.microsecond:
+ time_args = rf'{time_args}, {val.second}, {val.microsecond*1000}'
+ elif val.second:
+ time_args = rf'{time_args}, {val.second}'
+ elif val.microsecond:
+ time_args = rf'{time_args}, 0, {val.microsecond*1000}'
+ if isinstance(val, datetime):
+ offset_init = ''
+ if val.tzinfo is not None:
+ offset = val.tzinfo.utcoffset(val)
+ mins = offset.total_seconds() / 60
+ offset = (int(mins / 60), int(mins % 60))
+ offset_init = rf', {{ {offset[0]}, {offset[1]} }}'
+ return rf'toml::date_time{{ {{ {date_args} }}, {{ {time_args} }}{offset_init} }}'
+ elif isinstance(val, time):
+ return rf'toml::time{{ {time_args} }}'
+ elif isinstance(val, date):
+ return rf'toml::date{{ {date_args} }}'
+ else:
+ raise ValueError(str(type(val)))
+
+
+
+class TomlPPArray:
+
+ def __init__(self, init_data=None):
+ self.values = init_data if init_data else list()
+
+ def render(self, indent = '', indent_declaration = False):
+ s = ''
+ if indent_declaration:
+ s += indent
+ if len(self.values) == 0:
+ s += 'toml::array{}'
+ else:
+ s += 'toml::array{'
+ for val in self.values:
+ s += '\n' + indent + '\t'
+ if isinstance(val, TomlPPArray) and len(self.values) == 1:
+ s += 'toml::inserter{'
+ if isinstance(val, (TomlPPTable, TomlPPArray)) and len(val) > 0:
+ s += val.render(indent + '\t')
+ else:
+ s += python_value_to_tomlpp(val)
+ if isinstance(val, TomlPPArray) and len(self.values) == 1:
+ s += '}'
+ s += ','
+ s += '\n' + indent + '}'
+ return s
+
+ def __str__(self):
+ return self.render()
+
+ def __len__(self):
+ return len(self.values)
+
+
+
+class TomlPPTable:
+
+ def __init__(self, init_data=None):
+ self.values = init_data if init_data else dict()
+
+ def render(self, indent = '', indent_declaration = False):
+ s = ''
+ if indent_declaration:
+ s += indent
+ if len(self.values) == 0:
+ s += 'toml::table{}'
+ else:
+ s += 'toml::table{'
+ for key, val in self.values.items():
+ s += '\n' + indent + '\t{ '
+ if isinstance(val, (TomlPPTable, TomlPPArray)) and len(val) > 0:
+ s += '\n' + indent + '\t\t{},'.format(python_value_to_tomlpp(str(key)))
+ s += ' ' + val.render(indent + '\t\t')
+ s += '\n' + indent + '\t'
+ else:
+ s += '{}, {} '.format(python_value_to_tomlpp(str(key)), python_value_to_tomlpp(val))
+ s += '},'
+ s += '\n' + indent + '}'
+ return s
+
+ def __str__(self):
+ return self.render()
+
+ def __len__(self):
+ return len(self.values)
+
+
+
+def json_to_python(val):
+
+ if isinstance(val, dict):
+ if len(val) == 2 and "type" in val and "value" in val:
+ val_type = val["type"]
+ if val_type == "integer":
+ return int(val["value"])
+ elif val_type == "float":
+ return float(val["value"])
+ elif val_type == "string":
+ return str(val["value"])
+ elif val_type == "bool":
+ return True if val["value"].lower() == "true" else False
+ elif val_type == "array":
+ return json_to_python(val["value"])
+ elif val_type in ("datetime", "date", "time", "datetime-local", "date-local", "time-local"):
+ dt_val = dateutil.parser.parse(val["value"])
+ if val_type in ("date", "date-local"):
+ return dt_val.date()
+ elif val_type in ("time", "time-local"):
+ return dt_val.time()
+ else:
+ return dt_val
+ else:
+ raise ValueError(val_type)
+ else:
+ vals = dict()
+ for k,v in val.items():
+ vals[k] = json_to_python(v)
+ return vals
+
+ elif isinstance(val, list):
+ vals = list()
+ for v in val:
+ vals.append(json_to_python(v))
+ return vals
+
+ else:
+ raise ValueError(str(type(val)))
+
+
+
+def python_to_tomlpp(node):
+ if isinstance(node, dict):
+ table = TomlPPTable()
+ for key, val in node.items():
+ table.values[key] = python_to_tomlpp(val)
+ return table
+ elif isinstance(node, (set, list, tuple)):
+ array = TomlPPArray()
+ for val in node:
+ array.values.append(python_to_tomlpp(val))
+ return array
+ else:
+ return node
+
+
+
+class TomlTest:
+
+ def __init__(self, file_path, name, is_valid_case):
+ self.__name = name
+ self.__identifier = sanitize(self.__name)
+ self.__group = self.__identifier.strip('_').split('_')[0]
+
+ # read file
+ self.__raw = True
+ self.__bytes = False
+ with open(file_path, "rb") as f:
+ self.__source = f.read()
+
+ # if we find a utf-16 or utf-32 BOM, treat the file as bytes
+ if len(self.__source) >= 4:
+ prefix = self.__source[:4]
+ if prefix == b'\x00\x00\xFE\xFF' or prefix == b'\xFF\xFE\x00\x00':
+ self.__bytes = True
+ if len(self.__source) >= 2:
+ prefix = self.__source[:2]
+ if prefix == b'\xFF\xFE' or prefix == b'\xFE\xFF':
+ self.__bytes = True
+
+ # if we find a utf-8 BOM, treat it as a string but don't use a raw string literal
+ if not self.__bytes and len(self.__source) >= 3:
+ prefix = self.__source[:3]
+ if prefix == b'\xEF\xBB\xBF':
+ self.__raw = False
+
+ # if we're not treating it as bytes, decode the bytes into a utf-8 string
+ if not self.__bytes:
+ try:
+ self.__source = str(self.__source, encoding='utf-8')
+
+ # disable raw literals if the string contains some things that should be escaped
+ for c in self.__source:
+ if is_problematic_control_char(c):
+ self.__raw = False
+ break
+
+ # disable raw literals if the string has trailing backslashes followed by whitespace on the same line
+ # (GCC doesn't like it and generates some noisy warnings)
+ if self.__raw and re.search(r'\\[ \t]+?\n', self.__source, re.S):
+ self.__raw = False
+
+ except UnicodeDecodeError:
+ self.__bytes = True
+
+ # strip off trailing newlines for non-byte strings (they're just noise)
+ if not self.__bytes:
+ while self.__source.endswith('\r\n'):
+ self.__source = self.__source[:-2]
+ self.__source = self.__source.rstrip('\n')
+
+ # parse preprocessor conditions
+ self.__conditions = []
+ if is_valid_case:
+ self.__expected = True
+ path_base = str(Path(file_path.parent, file_path.stem))
+ yaml_file = Path(path_base + r'.yaml')
+ if yaml_file.exists():
+ self.__expected = python_to_tomlpp(yaml.load(
+ utils.read_all_text_from_file(yaml_file, logger=True),
+ Loader=yaml.FullLoader
+ ))
+ else:
+ json_file = Path(path_base + r'.json')
+ if json_file.exists():
+ self.__expected = python_to_tomlpp(json_to_python(json.loads(
+ utils.read_all_text_from_file(json_file, logger=True),
+ )))
+
+ else:
+ self.__expected = False
+
+ def name(self):
+ return self.__name
+
+ def identifier(self):
+ return self.__identifier
+
+ def group(self):
+ return self.__group
+
+ def add_condition(self, cond):
+ self.__conditions.append(cond)
+ return self
+
+ def condition(self):
+ if not self.__conditions or not self.__conditions[0]:
+ return ''
+ if len(self.__conditions) == 1:
+ return rf'{self.__conditions[0]}'
+ return rf'{" && ".join([rf"{c}" for c in self.__conditions])}'
+
+ def expected(self):
+ return self.__expected
+
+ def __str__(self):
+ return rf'static constexpr auto {self.__identifier} = {make_string_literal(self.__source, escape_all = self.__bytes, escape_any = not self.__raw)}sv;'
+
+
+
+def load_tests(source_folder, is_valid_set, ignore_list = None):
+ source_folder = source_folder.resolve()
+ utils.assert_existing_directory(source_folder)
+ files = utils.get_all_files(source_folder, all="*.toml", recursive=True)
+ strip_source_folder_len = len(str(source_folder))
+ files = [(f, str(f)[strip_source_folder_len+1:-5].replace('\\', '-').replace('/', '-').strip()) for f in files]
+ if ignore_list:
+ files_ = []
+ for f,n in files:
+ ignored = False
+ for ignore in ignore_list:
+ if ignore is None:
+ continue
+ if isinstance(ignore, str):
+ if n == ignore:
+ ignored = True
+ break
+ elif ignore.fullmatch(n) is not None: # regex
+ ignored = True
+ break
+ if not ignored:
+ files_.append((f, n))
+ files = files_
+ tests = []
+ for f,n in files:
+ tests.append(TomlTest(f, n, is_valid_set))
+ return tests
+
+
+
+def add_condition(tests, condition, names):
+ for test in tests:
+ matched = False
+ for name in names:
+ if isinstance(name, str):
+ if test.name() == name:
+ matched = True
+ break
+ elif name.fullmatch(test.name()) is not None: # regex
+ matched = True
+ break
+ if matched:
+ test.add_condition(condition)
+
+
+
+def find_tests_dir(*relative_path):
+ paths = (
+ (Path.cwd(),),
+ ('.',),
+ (utils.entry_script_dir(), '..', '..') # side-by-side with toml_++ repo folder
+ )
+ for p in paths:
+ try:
+ path = Path(*p, *relative_path).resolve()
+ if path.exists() and path.is_dir():
+ return path
+ except:
+ pass
+ return None
+
+
+
+def load_burnsushi_tests(tests):
+
+ root_dir = find_tests_dir('toml-test', 'tests')
+ if root_dir is None:
+ raise Exception(r'could not find burntsushi/toml-test')
+
+ tests['valid']['burntsushi'] = load_tests(Path(root_dir, 'valid'), True, (
+ # broken by the json reader
+ 'key-alphanum',
+ ))
+ add_condition(tests['valid']['burntsushi'], '!TOML_MSVC', (
+ 'inline-table-key-dotted', # causes MSVC to run out of heap space during compilation O_o
+ ))
+ add_condition(tests['valid']['burntsushi'], 'TOML_LANG_UNRELEASED', (
+ 'string-escape-esc', # \e in strings
+ 'datetime-no-seconds', # omitting seconds from date-times
+ 'inline-table-newline',
+ 'key-unicode',
+ 'string-hex-escape'
+ ))
+
+ tests['invalid']['burntsushi'] = load_tests(Path(root_dir, 'invalid'), False)
+ add_condition(tests['invalid']['burntsushi'], '!TOML_LANG_UNRELEASED', (
+ 'datetime-no-secs',
+ re.compile(r'inline-table-linebreak-.*'),
+ 'inline-table-trailing-comma',
+ 'key-special-character',
+ 'multi-line-inline-table',
+ 'string-basic-byte-escapes',
+ ))
+
+
+
+def load_iarna_tests(tests):
+
+ root_dir = find_tests_dir('toml-spec-tests')
+ if root_dir is None:
+ raise Exception(r'could not find iarni/toml-spec-tests')
+
+ tests['invalid']['iarna'] = load_tests(Path(root_dir, 'errors'), False)
+ add_condition(tests['invalid']['iarna'], '!TOML_LANG_UNRELEASED', (
+ 'inline-table-trailing-comma',
+ ))
+
+ tests['valid']['iarna'] = load_tests(Path(root_dir, 'values'), True, (
+ # these are stress-tests for 'large' datasets. I test these separately. Having them inline in C++ code is insane.
+ 'qa-array-inline-1000',
+ 'qa-array-inline-nested-1000',
+ 'qa-key-literal-40kb',
+ 'qa-key-string-40kb',
+ 'qa-scalar-literal-40kb',
+ 'qa-scalar-literal-multiline-40kb',
+ 'qa-scalar-string-40kb',
+ 'qa-scalar-string-multiline-40kb',
+ 'qa-table-inline-1000',
+ 'qa-table-inline-nested-1000',
+ # bugged: https://github.com/iarna/toml-spec-tests/issues/3
+ 'spec-date-time-6',
+ 'spec-date-time-local-2',
+ 'spec-time-2',
+ ))
+
+
+
+def write_test_file(name, all_tests):
+
+ for test in all_tests:
+ unicode = requires_unicode(str(test))
+ if not unicode and not isinstance(test.expected(), bool):
+ unicode = requires_unicode(test.expected().render())
+ if unicode:
+ test.add_condition(r'UNICODE_LITERALS_OK')
+
+ tests_by_group = {}
+ for test in all_tests:
+ if test.group() not in tests_by_group:
+ tests_by_group[test.group()] = {}
+ cond = test.condition()
+ if cond not in tests_by_group[test.group()]:
+ tests_by_group[test.group()][cond] = []
+ tests_by_group[test.group()][cond].append(test)
+ all_tests = tests_by_group
+
+ test_file_path = Path(utils.entry_script_dir(), '..', 'tests', rf'conformance_{sanitize(name.strip())}.cpp').resolve()
+ with StringIO() as test_file_buffer:
+ write = lambda txt,end='\n': print(txt, file=test_file_buffer, end=end)
+
+ # preamble
+ write(r'// This file is a part of toml++ and is subject to the the terms of the MIT license.')
+ write(r'// Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>')
+ write(r'// See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.')
+ write(r'// SPDX-License-Identifier: MIT')
+ write(r'//-----')
+ write(r'// this file was generated by generate_conformance_tests.py - do not modify it directly')
+ write(r'')
+ write(r'#include "tests.hpp"')
+
+ # test data
+ write(r'')
+ write('namespace')
+ write('{', end='')
+ for group, conditions in all_tests.items():
+ for condition, tests in conditions.items():
+ write('')
+ if condition != '':
+ write(f'#if {condition}');
+ write('')
+ for test in tests:
+ write(f'\t{test}')
+ if condition != '':
+ write('')
+ write(f'#endif // {condition}');
+ write('}')
+
+ # tests
+ write('')
+ write(f'TEST_CASE("conformance - {name}")')
+ write('{', end='')
+ for group, conditions in all_tests.items():
+ for condition, tests in conditions.items():
+ if condition != '':
+ write('')
+ write(f'#if {condition}');
+ for test in tests:
+ write('')
+ write(f'\tSECTION("{test.name()}") {{')
+ write('')
+ expected = test.expected()
+ if isinstance(expected, bool):
+ if expected:
+ write(f'\tparsing_should_succeed(FILE_LINE_ARGS, {test.identifier()}); // {test.name()}')
+ else:
+ write(f'\tparsing_should_fail(FILE_LINE_ARGS, {test.identifier()}); // {test.name()}')
+ else:
+ s = expected.render('\t\t')
+ write(f'\tparsing_should_succeed(FILE_LINE_ARGS, {test.identifier()}, [](toml::table&& tbl) // {test.name()}')
+ write('\t{')
+ write(f'\t\tconst auto expected = {s};')
+ write('\t\tREQUIRE(tbl == expected);')
+ write('\t});')
+ write('')
+ write('\t}')
+ write('')
+ if condition != '':
+ write('')
+ write(f'#endif // {condition}');
+ write('}')
+ write('')
+
+ test_file_content = test_file_buffer.getvalue()
+
+ # clang-format
+ print(f"Running clang-format for {test_file_path}")
+ try:
+ test_file_content = utils.apply_clang_format(test_file_content, cwd=test_file_path.parent)
+ except Exception as ex:
+ print(rf'Error running clang-format:', file=sys.stderr)
+ utils.print_exception(ex)
+
+ # write to disk
+ print(rf'Writing {test_file_path}')
+ with open(test_file_path, 'w', encoding='utf-8', newline='\n') as test_file:
+ test_file.write(test_file_content)
+
+
+
+def main():
+ all_tests = { 'valid': dict(), 'invalid': dict() }
+ load_burnsushi_tests(all_tests)
+ load_iarna_tests(all_tests)
+ for validity, sources in all_tests.items():
+ for source, tests in sources.items():
+ write_test_file('{}/{}'.format(source, validity), tests )
+
+
+
+if __name__ == '__main__':
+ utils.run(main, verbose=True)
diff --git a/tomlplusplus/tools/generate_single_header.bat b/tomlplusplus/tools/generate_single_header.bat
new file mode 100644
index 0000000000..7fe0def0b5
--- /dev/null
+++ b/tomlplusplus/tools/generate_single_header.bat
@@ -0,0 +1,19 @@
+@ECHO off
+SETLOCAL enableextensions enabledelayedexpansion
+PUSHD .
+CD /d "%~dp0"
+
+REM --------------------------------------------------------------------------------------
+REM Invokes generate_single_header.py.
+REM --------------------------------------------------------------------------------------
+
+py generate_single_header.py %*
+if %ERRORLEVEL% NEQ 0 (
+ PAUSE
+ GOTO FINISH
+)
+
+:FINISH
+POPD
+@ENDLOCAL
+EXIT /B %ERRORLEVEL%
diff --git a/tomlplusplus/tools/generate_single_header.py b/tomlplusplus/tools/generate_single_header.py
new file mode 100755
index 0000000000..fed4ae4e1e
--- /dev/null
+++ b/tomlplusplus/tools/generate_single_header.py
@@ -0,0 +1,265 @@
+#!/usr/bin/env python3
+# This file is a part of toml++ and is subject to the the terms of the MIT license.
+# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
+# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
+# SPDX-License-Identifier: MIT
+
+import sys
+import utils
+import re
+from pathlib import Path
+from io import StringIO
+
+
+
+
+class Preprocessor:
+
+ __re_includes = re.compile(r'^\s*#\s*include\s+"(.+?)".*?$', re.I | re.M)
+ __re_pragma_once = re.compile(r'^\s*#\s*pragma\s+once\s*$', re.M)
+
+ def __init__(self, file):
+ self.__processed_files = set()
+ self.__once_only = set()
+ self.__directory_stack = [ Path.cwd() ]
+ self.__include_stack = []
+ self.__entry_root = ''
+ self.__string = self.__preprocess(file)
+
+ def __preprocess(self, incl):
+
+ if not isinstance(incl, (Path, str)): # a regex match object
+ incl = incl.group(1).strip()
+ if isinstance(incl, str):
+ incl = Path(incl.strip().replace('\\',r'/'))
+ if not incl.is_absolute():
+ incl = Path(self.__directory_stack[-1], incl).resolve()
+ self.__processed_files.add(incl)
+ if incl in self.__once_only:
+ return ''
+ self.__include_stack.append(incl)
+
+ text = utils.read_all_text_from_file(incl, logger=True).strip() + '\n'
+ text = text.replace('\r\n', '\n') # convert windows newlines
+
+ self.__directory_stack.append(incl.parent)
+ if self.__re_pragma_once.search(text):
+ self.__once_only.add(incl)
+ if len(self.__include_stack) == 1 and self.__entry_root == '':
+ self.__entry_root = str(incl.parent).replace('\\',r'/')
+ if len(self.__include_stack) > 1:
+ text = self.__re_pragma_once.sub('', text)
+
+ text = self.__re_includes.sub(lambda m : self.__preprocess(m), text, 0)
+
+ incl_normalized = str(incl).replace('\\',r'/')
+ if incl_normalized.startswith(self.__entry_root):
+ incl_normalized = incl_normalized[len(self.__entry_root):].strip('/')
+
+ if len(self.__include_stack) > 1 and incl_normalized not in (r'impl/header_start.hpp', r'impl/header_end.hpp'):
+ header = utils.make_divider(incl_normalized, 10, pattern = r'*')
+ footer = ''
+ if len(self.__include_stack) > 2:
+ footer = str(self.__include_stack[-2]).replace('\\',r'/')
+ if footer.startswith(self.__entry_root):
+ footer = footer[len(self.__entry_root):].strip('/')
+ footer = utils.make_divider(footer, 10, pattern = r'*')
+
+ text = f'\n\n{header}\n\n{text}\n\n{footer}'.rstrip()
+
+ self.__include_stack.pop()
+ self.__directory_stack.pop()
+ return '\n\n' + text + '\n\n'
+
+ def __str__(self):
+ return self.__string
+
+ def processed_files(self):
+ out = list(self.__processed_files)
+ out.sort()
+ return out
+
+
+
+def main():
+
+ # establish local directories
+ root_dir = utils.entry_script_dir().parent
+ include_dir = Path(root_dir, 'include', 'toml++')
+
+ # preprocess header(s)
+ toml_h = str(Preprocessor(Path(include_dir, 'toml.hpp')))
+
+ # strip various things:
+ if 1:
+ for i in range(3):
+ # trailing whitespace
+ toml_h = re.sub('([^ \t])[ \t]+\n', r'\1\n', toml_h)
+ # explicit 'strip this' blocks
+ toml_h = re.sub(r'(?:\n[ \t]*)?//[#!][ \t]*[{][{].*?//[#!][ \t]*[}][}].*?\n', '\n', toml_h, flags=re.S)
+ # spdx license identifiers
+ toml_h = re.sub(r'^\s*//\s*SPDX-License-Identifier:.+?$', '', toml_h, 0, re.I | re.M)
+ # double blank lines
+ toml_h = re.sub('\n(?:[ \t]*\n[ \t]*)+\n', '\n\n', toml_h)
+ # magic comments
+ blank_line = r'(?:[ \t]*\n)'
+ comment_line = r'(?:[ \t]*//(?:[/#!<]| ?(?:---|===|\^\^\^|vvv))[^\n]*\n)'
+ toml_h = re.sub(rf'\n{comment_line}{blank_line}+{comment_line}', '\n', toml_h)
+ toml_h = re.sub(rf'([{{,])\s*\n(?:{comment_line}|{blank_line})+', r'\1\n', toml_h)
+ toml_h = re.sub(rf'{comment_line}+', '\n', toml_h)
+ # consecutive header separators
+ header_separator = r'(?://\*\*\*\**[ \t]+[a-zA-Z0-9_/.-]+[ \t]+\*\*\*\*+\n)'
+ toml_h = re.sub(rf'(?:{header_separator}{blank_line}*)+({header_separator})', r'\1', toml_h)
+ # weird spacing edge case between } and pp directives
+ toml_h = re.sub('\n[}]\n#', r'\n}\n\n#', toml_h, re.S)
+ # enable warnings -> disable warnings
+ toml_h = re.sub('(TOML_ENABLE_WARNINGS;)\n[ \t\n]*\n(TOML_DISABLE_WARNINGS;)', r'', toml_h)
+ # blank lines between consecutive TOML_XXXXX_WARNINGS statements
+ toml_h = re.sub('(TOML_[A-Z_]+?_WARNINGS;)\n[ \t\n]*\n(TOML_[A-Z_]+?_WARNINGS;)', r'\1\n\2', toml_h)
+ # blank lines between consecutive #includes
+ toml_h = re.sub('[#]\s*include\s*<(.+?)>\n[ \t\n]*\n[#]\s*include\s*<(.+?)>', r'#include <\1>\n#include <\2>', toml_h)
+ # blank lines following opening brackets or a comma
+ toml_h = re.sub(r'([^@][({,])\n\n', r'\1\n', toml_h)
+ # blank lines preceeding closing brackets
+ toml_h = re.sub(r'\n\n([ \t]*[})])', r'\n\1', toml_h)
+ # IWYU pragmas
+ toml_h = re.sub(r'\n// IWYU pragma: [^\n]+\n', '\n', toml_h)
+ # ensure only one trailing newline
+ toml_h = toml_h.strip() + '\n'
+
+ # change TOML_LIB_SINGLE_HEADER to 1
+ toml_h = re.sub(
+ '#\s*define\s+TOML_LIB_SINGLE_HEADER\s+[0-9]+',
+ '#define TOML_LIB_SINGLE_HEADER 1',
+ toml_h, 0, re.I
+ )
+
+ # read version number
+ version_h = utils.read_all_text_from_file(Path(include_dir, 'impl/version.hpp'), logger=True)
+ match = re.search(
+ r'#\s*define\s+TOML_LIB_MAJOR\s+([0-9]+)[^0-9].*'
+ + r'#\s*define\s+TOML_LIB_MINOR\s+([0-9]+)[^0-9].*'
+ + r'#\s*define\s+TOML_LIB_PATCH\s+([0-9]+)[^0-9]',
+ version_h, re.I | re.S)
+ if match is None:
+ raise Exception("could not find TOML_LIB_MAJOR, TOML_LIB_MINOR or TOML_LIB_PATCH impl/version.hpp")
+ version = rf'{int(match[1])}.{int(match[2])}.{int(match[3])}'
+ print(rf'Library version: {version}')
+
+ # build the preamble (license etc)
+ preamble = []
+ preamble.append(rf'''
+// toml++ v{version}
+// https://github.com/marzer/tomlplusplus
+// SPDX-License-Identifier: MIT''')
+ preamble.append(r'''
+// - THIS FILE WAS ASSEMBLED FROM MULTIPLE HEADER FILES BY A SCRIPT - PLEASE DON'T EDIT IT DIRECTLY -
+//
+// If you wish to submit a contribution to toml++, hooray and thanks! Before you crack on, please be aware that this
+// file was assembled from a number of smaller files by a python script, and code contributions should not be made
+// against it directly. You should instead make your changes in the relevant source file(s). The file names of the files
+// that contributed to this header can be found at the beginnings and ends of the corresponding sections of this file.''')
+ preamble.append(r'''
+// TOML Language Specifications:
+// latest: https://github.com/toml-lang/toml/blob/master/README.md
+// v1.0.0: https://toml.io/en/v1.0.0
+// v0.5.0: https://toml.io/en/v0.5.0
+// changelog: https://github.com/toml-lang/toml/blob/master/CHANGELOG.md''')
+ preamble.append(utils.read_all_text_from_file(Path(utils.entry_script_dir(), '..', 'LICENSE').resolve(), logger=True))
+
+ # write the output
+ with StringIO(newline='\n') as output:
+
+ # build in a string buffer
+ write = lambda txt, end='\n': print(txt, file=output, end=end)
+ if (len(preamble) > 0):
+ write(utils.make_divider())
+ for pre in preamble:
+ write('//')
+ for line in pre.strip().splitlines():
+ if len(line) == 0:
+ write('//')
+ continue
+ if not line.startswith('//'):
+ write('// ', end = '')
+ write(line)
+ write('//')
+ write(utils.make_divider())
+ write(toml_h)
+ write('')
+
+ output_str = output.getvalue().strip()
+
+ # analyze the output to find any potentially missing #undefs
+ if 1:
+ re_define = re.compile(r'^\s*#\s*define\s+([a-zA-Z0-9_]+)(?:$|\s|\()')
+ re_undef = re.compile(r'^\s*#\s*undef\s+([a-zA-Z0-9_]+)(?:$|\s|//)')
+ defines = dict()
+ for output_line in output_str.splitlines():
+ defined = True
+ m = re_define.match(output_line)
+ if not m:
+ defined = False
+ m = re_undef.match(output_line)
+ if m:
+ defines[m.group(1)] = defined
+ ignore_list = ( # macros that are meant to stay public (user configs etc)
+ r'INCLUDE_TOMLPLUSPLUS_H',
+ r'POXY_IMPLEMENTATION_DETAIL',
+ r'TOML_ALL_INLINE',
+ r'TOML_API',
+ r'TOML_CALLCONV',
+ r'TOML_CONCAT',
+ r'TOML_CONCAT_1',
+ r'TOML_CONFIG_HEADER',
+ r'TOML_CUDA',
+ r'TOML_ENABLE_FORMATTERS',
+ r'TOML_ENABLE_PARSER',
+ r'TOML_ENABLE_SIMD',
+ r'TOML_ENABLE_UNRELEASED_FEATURES',
+ r'TOML_ENABLE_WINDOWS_COMPAT',
+ r'TOML_ENABLE_FLOAT16',
+ r'TOML_EXCEPTIONS',
+ r'TOML_EXPORTED_CLASS',
+ r'TOML_EXPORTED_FREE_FUNCTION',
+ r'TOML_EXPORTED_MEMBER_FUNCTION',
+ r'TOML_EXPORTED_STATIC_FUNCTION',
+ r'TOML_HEADER_ONLY',
+ r'TOML_LANG_MAJOR',
+ r'TOML_LANG_MINOR',
+ r'TOML_LANG_PATCH',
+ r'TOML_LIB_MAJOR',
+ r'TOML_LIB_MINOR',
+ r'TOML_LIB_PATCH',
+ r'TOML_LIB_SINGLE_HEADER',
+ r'TOML_MAX_NESTED_VALUES',
+ r'TOML_MAX_DOTTED_KEYS_DEPTH',
+ r'TOML_NAMESPACE_END',
+ r'TOML_NAMESPACE_START',
+ r'TOML_OPTIONAL_TYPE',
+ r'TOML_SMALL_FLOAT_TYPE',
+ r'TOML_SMALL_INT_TYPE',
+ r'TOML_UNDEF_MACROS',
+ r'TOMLPLUSPLUS_H',
+ r'TOMLPLUSPLUS_HPP',
+ r'TOML_SHARED_LIB'
+ )
+ set_defines = []
+ for define, currently_set in defines.items():
+ if currently_set and define not in ignore_list:
+ set_defines.append(define)
+ if len(set_defines) > 0:
+ set_defines.sort()
+ print(f"Potentially missing #undefs:")
+ for define in set_defines:
+ print(f"\t#undef {define}")
+
+ # write the output file
+ output_file_path = Path(utils.entry_script_dir(), '..', 'toml.hpp').resolve()
+ print("Writing to {}".format(output_file_path))
+ with open(output_file_path,'w', encoding='utf-8', newline='\n') as output_file:
+ print(output_str, file=output_file)
+
+
+if __name__ == '__main__':
+ utils.run(main, verbose=True)
diff --git a/tomlplusplus/tools/generate_windows_test_targets.py b/tomlplusplus/tools/generate_windows_test_targets.py
new file mode 100755
index 0000000000..45221eaa9b
--- /dev/null
+++ b/tomlplusplus/tools/generate_windows_test_targets.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+# This file is a part of toml++ and is subject to the the terms of the MIT license.
+# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
+# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
+# SPDX-License-Identifier: MIT
+
+import sys
+import utils
+import re
+import itertools
+from pathlib import Path
+from uuid import UUID, uuid5
+
+
+def main():
+
+ mode_keys = [ '!!debug', '!x86', 'cpplatest', 'unrel', 'noexcept' ]
+ modes = [ [] ]
+ for n in range(1, len(mode_keys)):
+ for combo in itertools.combinations(mode_keys, n):
+ modes.append([i for i in combo])
+ modes.append(mode_keys)
+ for mode in modes:
+ if '!x86' not in mode:
+ mode.insert(0, '!x64')
+ if '!!debug' not in mode:
+ mode.insert(0, '!!release')
+ mode.sort()
+ for i in range(0, len(mode)):
+ while mode[i].startswith('!'):
+ mode[i] = mode[i][1:]
+ modes.sort()
+
+ test_root = Path(utils.entry_script_dir(), '..', 'tests', 'vs').resolve()
+ uuid_namespace = UUID('{51C7001B-048C-4AF0-B598-D75E78FF31F0}')
+ configuration_name = lambda x: 'Debug' if x.lower() == 'debug' else 'Release'
+ platform_name = lambda x: 'Win32' if x == 'x86' else x
+ for mode in modes:
+ file_path = Path(test_root, 'test_{}.vcxproj'.format('_'.join(mode)))
+ print(f"Writing to {file_path}")
+ with open(file_path, 'w', encoding='utf-8-sig', newline='\r\n') as file:
+ write = lambda txt: print(txt, file=file)
+ write(r'''
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="{configuration}|{platform}">
+ <Configuration>{configuration}</Configuration>
+ <Platform>{platform}</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>16.0</VCProjectVersion>
+ <ProjectGuid>{{{uuid}}}</ProjectGuid>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ <PreferredToolArchitecture>x64</PreferredToolArchitecture>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|{platform}'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v143</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|{platform}'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v143</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"
+ Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <Import Project="../../toml++.props" />
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalIncludeDirectories>..\tests;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <ExceptionHandling>{exceptions}</ExceptionHandling>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <PrecompiledHeaderFile>tests.hpp</PrecompiledHeaderFile>
+ <PreprocessorDefinitions>TOML_ENABLE_UNRELEASED_FEATURES={unreleased_features};%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>LEAK_TESTS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions Condition="'%(ExceptionHandling)'=='false'">_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions Condition="'%(ExceptionHandling)'=='false'">SHOULD_HAVE_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions Condition="'%(ExceptionHandling)'!='false'">SHOULD_HAVE_EXCEPTIONS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <LanguageStandard>std{standard}</LanguageStandard>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <WarningLevel>EnableAllWarnings</WarningLevel>
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4127</DisableSpecificWarnings> <!-- conditional expr is constant -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4324</DisableSpecificWarnings> <!-- structure was padded due to alignment specifier -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4464</DisableSpecificWarnings> <!-- relative include path contains '..' -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4505</DisableSpecificWarnings> <!-- unreferenced local function removed -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4514</DisableSpecificWarnings> <!-- unreferenced inline function has been removed -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4577</DisableSpecificWarnings> <!-- 'noexcept' used with no exception handling mode specified -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4582</DisableSpecificWarnings> <!-- constructor is not implicitly called -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4623</DisableSpecificWarnings> <!-- default constructor was implicitly defined as deleted -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4625</DisableSpecificWarnings> <!-- copy constructor was implicitly defined as deleted -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4626</DisableSpecificWarnings> <!-- assignment operator was implicitly defined as deleted -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4710</DisableSpecificWarnings> <!-- function not inlined -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4711</DisableSpecificWarnings> <!-- function selected for automatic expansion -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4738</DisableSpecificWarnings> <!-- storing 32-bit float result in memory -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4820</DisableSpecificWarnings> <!-- N bytes padding added -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4866</DisableSpecificWarnings> <!-- compiler may not enforce ltr eval in operator[] -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4868</DisableSpecificWarnings> <!-- compiler may not enforce ltr eval in initializer list -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);4946</DisableSpecificWarnings> <!-- reinterpret_cast used between related classes -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);5026</DisableSpecificWarnings> <!-- move constructor was implicitly defined as deleted -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);5027</DisableSpecificWarnings> <!-- move assignment operator was implicitly defined as deleted -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);5039</DisableSpecificWarnings> <!-- potentially throwing function passed to 'extern "C"' -->
+ <DisableSpecificWarnings>%(DisableSpecificWarnings);5045</DisableSpecificWarnings> <!-- Compiler will insert Spectre mitigation -->
+ </ClCompile>
+ </ItemDefinitionGroup>
+ <PropertyGroup>
+ <LocalDebuggerWorkingDirectory>$(ProjectDir)..\</LocalDebuggerWorkingDirectory>
+ </PropertyGroup>
+ <ItemGroup>
+ <ClCompile Include="..\at_path.cpp" />
+ <ClCompile Include="..\path.cpp" />
+ <ClCompile Include="..\conformance_burntsushi_invalid.cpp" />
+ <ClCompile Include="..\conformance_burntsushi_valid.cpp" />
+ <ClCompile Include="..\conformance_iarna_invalid.cpp" />
+ <ClCompile Include="..\conformance_iarna_valid.cpp" />
+ <ClCompile Include="..\for_each.cpp" />
+ <ClCompile Include="..\formatters.cpp" />
+ <ClCompile Include="..\impl_toml.cpp">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\main.cpp">
+ <PrecompiledHeader>NotUsing</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\manipulating_arrays.cpp" />
+ <ClCompile Include="..\manipulating_tables.cpp" />
+ <ClCompile Include="..\manipulating_parse_result.cpp" />
+ <ClCompile Include="..\manipulating_values.cpp" />
+ <ClCompile Include="..\parsing_arrays.cpp" />
+ <ClCompile Include="..\parsing_booleans.cpp" />
+ <ClCompile Include="..\parsing_comments.cpp" />
+ <ClCompile Include="..\parsing_dates_and_times.cpp" />
+ <ClCompile Include="..\parsing_floats.cpp" />
+ <ClCompile Include="..\parsing_integers.cpp" />
+ <ClCompile Include="..\parsing_key_value_pairs.cpp" />
+ <ClCompile Include="..\parsing_spec_example.cpp" />
+ <ClCompile Include="..\parsing_strings.cpp" />
+ <ClCompile Include="..\parsing_tables.cpp" />
+ <ClCompile Include="..\tests.cpp">
+ <PrecompiledHeader>Create</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="..\user_feedback.cpp" />
+ <ClCompile Include="..\using_iterators.cpp" />
+ <ClCompile Include="..\visit.cpp" />
+ <ClCompile Include="..\windows_compat.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <Natvis Include="..\..\toml++.natvis" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\leakproof.hpp" />
+ <ClInclude Include="..\lib_catch2.hpp" />
+ <ClInclude Include="..\lib_tloptional.hpp" />
+ <ClInclude Include="..\settings.hpp" />
+ <ClInclude Include="..\tests.hpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="..\cpp.hint" />
+ <None Include="..\meson.build" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+</Project>
+ '''.strip().format(
+ configuration=next(configuration_name(x) for x in mode if x in ('debug', 'release')),
+ platform=next(platform_name(x) for x in mode if x in ('x64', 'x86')),
+ uuid=str(uuid5(uuid_namespace, '_'.join(mode))).upper(),
+ exceptions='false' if 'noexcept' in mode else 'Sync',
+ unreleased_features=1 if 'unrel' in mode else 0,
+ standard='cpplatest' if 'cpplatest' in mode else 'cpp17'
+ ))
+
+if __name__ == '__main__':
+ utils.run(main, verbose=True)
diff --git a/tomlplusplus/tools/requirements.txt b/tomlplusplus/tools/requirements.txt
new file mode 100644
index 0000000000..3e3e1136fb
--- /dev/null
+++ b/tomlplusplus/tools/requirements.txt
@@ -0,0 +1,4 @@
+misk>=0.7.0
+poxy>=0.7.0
+pyyaml
+python-dateutil
diff --git a/tomlplusplus/tools/utils.py b/tomlplusplus/tools/utils.py
new file mode 100644
index 0000000000..eda553a954
--- /dev/null
+++ b/tomlplusplus/tools/utils.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+# This file is a part of toml++ and is subject to the the terms of the MIT license.
+# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
+# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
+# SPDX-License-Identifier: MIT
+
+import sys
+import subprocess
+from pathlib import Path
+from misk import *
+
+
+def repeat_pattern(pattern, count):
+ if len(pattern) == 1:
+ return pattern * count
+ text = ''
+ for i in range(0, count):
+ text = text + pattern[i % len(pattern)]
+ return text
+
+
+
+def make_divider(text = None, text_col = 40, pattern = '-', line_length = 120):
+ if (text is None):
+ return "//" + repeat_pattern(pattern, line_length-2)
+ else:
+ text = "//{} {} ".format(repeat_pattern(pattern, text_col - 2), text);
+ if (len(text) < line_length):
+ return text + repeat_pattern(pattern, line_length - len(text))
+ else:
+ return text
+
+
+
+def apply_clang_format(text, cwd=None):
+ return subprocess.run(
+ 'clang-format --style=file'.split(),
+ check=True,
+ capture_output=True,
+ cwd=str(Path.cwd() if cwd is None else cwd),
+ encoding='utf-8',
+ input=text
+ ).stdout
+
+
+def run(main_func, verbose=False):
+ try:
+ result = main_func()
+ if result is None:
+ sys.exit(0)
+ else:
+ sys.exit(int(result))
+ except Exception as err:
+ print_exception(err, include_type=verbose, include_traceback=verbose, skip_frames=1)
+ sys.exit(-1)
diff --git a/tomlplusplus/tools/version.py b/tomlplusplus/tools/version.py
new file mode 100644
index 0000000000..1d611d664b
--- /dev/null
+++ b/tomlplusplus/tools/version.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+# This file is a part of toml++ and is subject to the the terms of the MIT license.
+# Copyright (c) Mark Gillard <mark.gillard@outlook.com.au>
+# See https://github.com/marzer/tomlplusplus/blob/master/LICENSE for the full license text.
+# SPDX-License-Identifier: MIT
+
+import sys
+import re
+from argparse import ArgumentParser
+from pathlib import Path
+
+
+
+def read_text_file(path):
+ print(rf'Reading {path}')
+ with open(path, r'r', encoding=r'utf-8') as f:
+ return f.read()
+
+
+
+def write_text_file(path, text):
+ print(rf'Writing {path}')
+ with open(path, r'w', encoding=r'utf-8', newline='\n') as f:
+ f.write(text)
+
+
+
+if __name__ == '__main__':
+
+ args = ArgumentParser(r'version.py', description=r'Sets the project version in all the necessary places.')
+ args.add_argument(r'version', type=str)
+ args = args.parse_args()
+
+ version = re.fullmatch(r'\s*[vV]?\s*([0-9]+)\s*[.,;]+\s*([0-9]+)\s*[.,;]+\s*([0-9]+)\s*', args.version)
+ if not version:
+ print(rf"Couldn't parse version triplet from '{args.version}'", file=sys.stderr)
+ sys.exit(1)
+ version = (int(version[1]), int(version[2]), int(version[3]))
+ version_str = rf'{version[0]}.{version[1]}.{version[2]}'
+ print(rf'version: {version_str}')
+
+ root = Path(__file__).parent.parent.resolve()
+
+ path = root / r'meson.build'
+ text = read_text_file(path)
+ text = re.sub(r'''(\s|^)version\s*:\s*['"].*?['"]''', rf"\1version: '{version_str}'", text, count=1)
+ write_text_file(path, text)
+
+ path = root / r'CMakeLists.txt'
+ text = read_text_file(path)
+ text = re.sub(r'''(\s|^)VERSION\s+[0-9](?:[.][0-9]){2}''', rf"\1VERSION {version_str}", text, count=1, flags=re.I)
+ write_text_file(path, text)
+
+ for path in (root / r'include/toml++/impl/version.hpp', root / r'toml.hpp'):
+ text = read_text_file(path)
+ text = re.sub(r'''(\s*#\s*define\s+TOML_LIB_MAJOR)\s+[0-9]+''', rf"\1 {version[0]}", text)
+ text = re.sub(r'''(\s*#\s*define\s+TOML_LIB_MINOR)\s+[0-9]+''', rf"\1 {version[1]}", text)
+ text = re.sub(r'''(\s*#\s*define\s+TOML_LIB_PATCH)\s+[0-9]+''', rf"\1 {version[2]}", text)
+ write_text_file(path, text)
+
+ noop_sub = r'#$%^nbsp^%$#'
+ for file in (r'README.md', r'docs/pages/main_page.md'):
+ path = root / file
+ text = read_text_file(path)
+ text = re.sub(r'''(toml(?:plusplus|\+\+|pp)\s*[/:^]\s*)[0-9](?:[.][0-9]){2}''', rf"\1{noop_sub}{version_str}", text, flags=re.I)
+ text = re.sub(r'''(GIT_TAG\s+)(?:v\s*)?[0-9](?:[.][0-9]){2}''', rf"\1v{version_str}", text, flags=re.I)
+ text = text.replace(noop_sub, '')
+ write_text_file(path, text)