diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:44:05 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:44:05 +0300 |
| commit | 0b24459ac12b6cf9fd5a401d647796ca254a8fa8 (patch) | |
| tree | f2fd66e2476976a51e2a51330fd95dc6e87b24c1 /tomlplusplus/tools | |
| parent | b85e90fc3480da0e6a48da73201a0b22488cc650 (diff) | |
| parent | 1c8b7466e4946fcc3bf20484c0e1d001202cca5a (diff) | |
| download | Project-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-x | tomlplusplus/tools/ci_single_header_check.py | 31 | ||||
| -rw-r--r-- | tomlplusplus/tools/clang_format.bat | 41 | ||||
| -rwxr-xr-x | tomlplusplus/tools/generate_conformance_tests.py | 635 | ||||
| -rw-r--r-- | tomlplusplus/tools/generate_single_header.bat | 19 | ||||
| -rwxr-xr-x | tomlplusplus/tools/generate_single_header.py | 265 | ||||
| -rwxr-xr-x | tomlplusplus/tools/generate_windows_test_targets.py | 181 | ||||
| -rw-r--r-- | tomlplusplus/tools/requirements.txt | 4 | ||||
| -rw-r--r-- | tomlplusplus/tools/utils.py | 55 | ||||
| -rw-r--r-- | tomlplusplus/tools/version.py | 68 |
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) |
