refactor: new version of MakeSingleFiles (#546)

* refactor: new version of MakeSingleFiles

* fix: use CMake and set version
This commit is contained in:
Henry Schreiner 2021-01-04 17:47:06 -05:00 committed by GitHub
parent 32e6c3f347
commit f862849488
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 222 additions and 140 deletions

View File

@ -22,11 +22,8 @@ jobs:
- uses: actions/setup-python@v2
- name: Make header
run: python ./scripts/MakeSingleHeader.py CLI11.hpp
- name: Prepare CMake config
run: cmake -S . -B build
run: cmake -S . -B build -DCLI11_SINGLE_FILE=ON
- name: Make package
run: cmake --build build --target package_source
@ -37,10 +34,13 @@ jobs:
cp build/CLI11-*-Source.* CLI11-Source
cp build/CLI11-*-Source.* .
- name: Make header
run: cmake --build build --target CLI11-generate-single-file
- uses: actions/upload-artifact@v2
with:
name: CLI11.hpp
path: CLI11.hpp
path: build/include/CLI11.hpp
- uses: actions/upload-artifact@v2
with:

67
CLI11.hpp.in Normal file
View File

@ -0,0 +1,67 @@
// CLI11: Version {version}
// Originally designed by Henry Schreiner
// https://github.com/CLIUtils/CLI11
//
// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts
// from: {git}
//
// CLI11 {version} Copyright (c) 2017-2020 University of Cincinnati, developed by Henry
// Schreiner under NSF AWARD 1414736. All rights reserved.
//
// Redistribution and use in source and binary forms of CLI11, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Standard combined includes:
{public_includes}
{version_hpp}
{macros_hpp}
{validators_hpp_filesystem}
namespace {namespace} {{
{string_tools_hpp}
{error_hpp}
{type_tools_hpp}
{split_hpp}
{config_fwd_hpp}
{validators_hpp}
{formatter_fwd_hpp}
{option_hpp}
{app_hpp}
{config_hpp}
{formatter_hpp}
}} // namespace {namespace}

View File

@ -247,7 +247,10 @@ if(CLI11_SINGLE_FILE)
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp"
COMMAND Python::Interpreter
"${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py"
"${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp"
${CLI11_headers}
--main "${CMAKE_CURRENT_SOURCE_DIR}/CLI11.hpp.in"
--output "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp"
--version "${CLI11_VERSION}"
DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp"
${CLI11_headers})

View File

@ -6,6 +6,7 @@
#pragma once
// [CLI11:public_includes:set]
#include <algorithm>
#include <cstdint>
#include <functional>
@ -18,6 +19,7 @@
#include <string>
#include <utility>
#include <vector>
// [CLI11:public_includes:end]
// CLI Library includes
#include "ConfigFwd.hpp"
@ -30,6 +32,7 @@
#include "TypeTools.hpp"
namespace CLI {
// [CLI11:app_hpp:verbatim]
#ifndef CLI11_PARSE
#define CLI11_PARSE(app, argc, argv) \
@ -3234,4 +3237,5 @@ struct AppFriend {
};
} // namespace detail
// [CLI11:app_hpp:end]
} // namespace CLI

View File

@ -6,19 +6,21 @@
#pragma once
// [CLI11:public_includes:set]
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
// [CLI11:public_includes:set]
#include "App.hpp"
#include "ConfigFwd.hpp"
#include "StringTools.hpp"
namespace CLI {
// [CLI11:config_hpp:verbatim]
namespace detail {
inline std::string convert_arg_for_ini(const std::string &arg) {
@ -348,4 +350,5 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
return out.str();
}
// [CLI11:config_hpp:end]
} // namespace CLI

View File

@ -6,16 +6,19 @@
#pragma once
// [CLI11:public_includes:set]
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
// [CLI11:public_includes:end]
#include "Error.hpp"
#include "StringTools.hpp"
namespace CLI {
// [CLI11:config_fwd_hpp:verbatim]
class App;
@ -128,4 +131,5 @@ class ConfigINI : public ConfigTOML {
valueDelimiter = '=';
}
};
// [CLI11:config_fwd_hpp:end]
} // namespace CLI

View File

@ -6,16 +6,19 @@
#pragma once
// [CLI11:public_includes:set]
#include <exception>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
// [CLI11:public_includes:end]
// CLI library includes
#include "StringTools.hpp"
namespace CLI {
// [CLI11:error_hpp:verbatim]
// Use one of these on all error classes.
// These are temporary and are undef'd at the end of this file.
@ -344,4 +347,5 @@ class OptionNotFound : public Error {
/// @}
// [CLI11:error_hpp:end]
} // namespace CLI

View File

@ -6,14 +6,17 @@
#pragma once
// [CLI11:public_includes:set]
#include <algorithm>
#include <string>
#include <vector>
// [CLI11:public_includes:end]
#include "App.hpp"
#include "FormatterFwd.hpp"
namespace CLI {
// [CLI11:formatter_hpp:verbatim]
inline std::string
Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const {
@ -285,4 +288,5 @@ inline std::string Formatter::make_option_usage(const Option *opt) const {
return opt->get_required() ? out.str() : "[" + out.str() + "]";
}
// [CLI11:formatter_hpp:end]
} // namespace CLI

View File

@ -6,14 +6,17 @@
#pragma once
// [CLI11:public_includes:set]
#include <map>
#include <string>
#include <utility>
#include <vector>
// [CLI11:public_includes:end]
#include "StringTools.hpp"
namespace CLI {
// [CLI11:formatter_fwd_hpp:verbatim]
class Option;
class App;
@ -177,4 +180,5 @@ class Formatter : public FormatterBase {
///@}
};
// [CLI11:formatter_fwd_hpp:end]
} // namespace CLI

View File

@ -6,7 +6,7 @@
#pragma once
// [CLI11:verbatim]
// [CLI11:macros_hpp:verbatim]
// The following version macro is very similar to the one in PyBind11
#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER)
@ -41,4 +41,4 @@
#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason)))
#endif
// [CLI11:verbatim]
// [CLI11:macros_hpp:end]

View File

@ -6,6 +6,7 @@
#pragma once
// [CLI11:public_includes:set]
#include <algorithm>
#include <functional>
#include <memory>
@ -14,6 +15,7 @@
#include <tuple>
#include <utility>
#include <vector>
// [CLI11:public_includes:end]
#include "Error.hpp"
#include "Macros.hpp"
@ -22,6 +24,7 @@
#include "Validators.hpp"
namespace CLI {
// [CLI11:option_hpp:verbatim]
using results_t = std::vector<std::string>;
/// callback function definition
@ -1316,4 +1319,5 @@ class Option : public OptionBase<Option> {
}
}; // namespace CLI
// [CLI11:option_hpp:end]
} // namespace CLI

View File

@ -6,15 +6,19 @@
#pragma once
// [CLI11:public_includes:set]
#include <string>
#include <tuple>
#include <utility>
#include <vector>
// [CLI11:public_includes:end]
#include "Error.hpp"
#include "StringTools.hpp"
namespace CLI {
// [CLI11:split_hpp:verbatim]
namespace detail {
// Returns false if not a short option. Otherwise, sets opt name and rest and returns true
@ -135,4 +139,5 @@ get_names(const std::vector<std::string> &input) {
}
} // namespace detail
// [CLI11:split_hpp:end]
} // namespace CLI

View File

@ -6,6 +6,7 @@
#pragma once
// [CLI11:public_includes:set]
#include <algorithm>
#include <iomanip>
#include <locale>
@ -14,9 +15,12 @@
#include <string>
#include <type_traits>
#include <vector>
// [CLI11:public_includes:end]
namespace CLI {
// [CLI11:string_tools_hpp:verbatim]
/// Include the items in this namespace to get free conversion of enums to/from streams.
/// (This is available inside CLI as well, so CLI11 will use this without a using statement).
namespace enums {
@ -409,4 +413,6 @@ inline std::string &add_quotes_if_needed(std::string &str) {
} // namespace detail
// [CLI11:string_tools_hpp:end]
} // namespace CLI

View File

@ -6,7 +6,7 @@
#pragma once
#include "StringTools.hpp"
// [CLI11:public_includes:set]
#include <cstdint>
#include <exception>
#include <memory>
@ -14,8 +14,12 @@
#include <type_traits>
#include <utility>
#include <vector>
// [CLI11:public_includes:end]
#include "StringTools.hpp"
namespace CLI {
// [CLI11:type_tools_hpp:verbatim]
// Type tools
@ -1540,4 +1544,5 @@ void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
#endif
} // namespace detail
// [CLI11:type_tools_hpp:end]
} // namespace CLI

View File

@ -10,6 +10,7 @@
#include "StringTools.hpp"
#include "TypeTools.hpp"
// [CLI11:public_includes:set]
#include <cmath>
#include <cstdint>
#include <functional>
@ -20,8 +21,9 @@
#include <string>
#include <utility>
#include <vector>
// [CLI11:public_includes:end]
// [CLI11:verbatim]
// [CLI11:validators_hpp_filesystem:verbatim]
// C standard library
// Only needed for existence checking
@ -55,9 +57,10 @@
#include <sys/types.h>
#endif
// [CLI11:verbatim]
// [CLI11:validators_hpp_filesystem:end]
namespace CLI {
// [CLI11:validators_hpp:verbatim]
class Option;
@ -1112,4 +1115,5 @@ inline std::pair<std::string, std::string> split_program_name(std::string comman
} // namespace detail
/// @}
// [CLI11:validators_hpp:end]
} // namespace CLI

View File

@ -6,11 +6,11 @@
#pragma once
// [CLI11:verbatim]
// [CLI11:version_hpp:verbatim]
#define CLI11_VERSION_MAJOR 1
#define CLI11_VERSION_MINOR 9
#define CLI11_VERSION_PATCH 1
#define CLI11_VERSION "1.9.1"
// [CLI11:verbatim]
// [CLI11:version_hpp:end]

View File

@ -5,171 +5,136 @@ from __future__ import print_function, unicode_literals
import os
import re
from argparse import ArgumentParser
from operator import add
from copy import copy
from functools import reduce
from subprocess import Popen, PIPE
import warnings
includes_local = re.compile(r"""^#include "(.*)"$""", re.MULTILINE)
includes_system = re.compile(r"""^#include \<(.*)\>$""", re.MULTILINE)
version_finder = re.compile(r"""^#define CLI11_VERSION \"(.*)\"$""", re.MULTILINE)
verbatim_tag_str = r"""
^ # Begin of line
[^\n^\[]+ # Some characters, not including [ or the end of a line
\[ # A literal [
[^\]^\n]* # Anything except a closing ]
CLI11:verbatim # The tag
[^\]^\n]* # Anything except a closing ]
\] # A literal ]
[^\n]* # Up to end of line
$ # End of a line
tag_str = r"""
^ # Begin of line
[/\s]+ # Whitespace or comment // chars
\[ # A literal [
{tag}: # The tag
(?P<name>[\w_]+) # name: group name
: # Colon
(?P<action>[\w_]+) # action: type of include
\] # A literal ]
\s* # Whitespace
$ # End of a line
(?P<content>.*) # All
^ # Begin of line
[/\s]+ # Whitespace or comment // chars
\[ # A literal [
{tag}: # The tag
(?P=name) # Repeated name
: # Colon
end # Literal "end"
\] # A literal ]
\s* # Whitespace
$ # End of a line
"""
verbatim_all = re.compile(
verbatim_tag_str + "(.*)" + verbatim_tag_str, re.MULTILINE | re.DOTALL | re.VERBOSE
)
DIR = os.path.dirname(os.path.abspath(__file__))
class HeaderFile(object):
TAG = "Unknown git revision"
LICENSE = "// BSD 3 clause"
VERSION = "Unknown"
class HeaderGroups(dict):
def __init__(self, tag):
self.re_matcher = re.compile(
tag_str.format(tag=tag), re.MULTILINE | re.DOTALL | re.VERBOSE
)
super(HeaderGroups, self).__init__()
def __init__(self, base, inc):
with open(os.path.join(base, inc)) as f:
def read_header(self, filename):
with open(filename) as f:
inner = f.read()
version = version_finder.search(inner)
if version:
self.__class__.VERSION = version.groups()[0]
matches = self.re_matcher.findall(inner)
# add self.verbatim
if "CLI11:verbatim" in inner:
self.verbatim = ["\n\n// Verbatim copy from {0}:".format(inc)]
self.verbatim += verbatim_all.findall(inner)
inner = verbatim_all.sub("", inner)
else:
self.verbatim = []
if not matches:
warnings.warn(
"Failed to find any matches in {filename}".format(filename=filename)
)
self.headers = set(includes_system.findall(inner))
for name, action, content in matches:
if action == "verbatim":
assert (
name not in self
), "{name} read in more than once! Quitting.".format(name=name)
self[name] = content
elif action == "set":
self[name] = self.get(name, set()) | set(content.strip().splitlines())
else:
raise RuntimeError("Action not understood, must be verbatim or set")
self.body = "\n// From {0}:\n\n".format(inc) + inner[inner.find("namespace") :]
self.namespace = None
def __add__(self, other):
out = copy(self)
out.headers |= other.headers
out.body += other.body
out.verbatim += other.verbatim
return out
@property
def header_str(self):
return "\n".join("#include <" + h + ">" for h in sorted(self.headers))
@property
def verbatim_str(self):
return "\n".join(self.verbatim)
def insert_namespace(self, namespace):
self.namespace = namespace
def macro_replacement(self, before, after):
self.verbatim = [x.replace(before, after) for x in self.verbatim]
self.body = self.body.replace(before, after)
def __str__(self):
result = """\
#pragma once
// CLI11: Version {self.VERSION}
// Originally designed by Henry Schreiner
// https://github.com/CLIUtils/CLI11
//
// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts
// from: {self.TAG}
//
// From LICENSE:
//
{self.LICENSE}
// Standard combined includes:
{self.header_str}
""".format(
self=self
)
if self.namespace:
result += "\nnamespace " + self.namespace + " {\n\n"
result += "{self.verbatim_str}\n{self.body}\n".format(self=self)
if self.namespace:
result += "} // namespace " + self.namespace + "\n\n"
return result
def post_process(self):
for key in self:
if isinstance(self[key], set):
self[key] = "\n".join(self[key])
def MakeHeader(
output, main_header, include_dir="../include", namespace=None, macro=None
):
def MakeHeader(output, main_header, files, tag, namespace, macro=None, version=None):
groups = HeaderGroups(tag)
# Set tag if possible to class variable
try:
proc = Popen(
["git", "describe", "--tags", "--always"], cwd=str(DIR), stdout=PIPE
)
out, _ = proc.communicate()
groups["git"] = out.decode("utf-8").strip() if proc.returncode == 0 else ""
except OSError:
pass
else:
if proc.returncode == 0:
HeaderFile.TAG = out.decode("utf-8").strip()
groups["git"] = ""
base_dir = os.path.abspath(os.path.join(DIR, include_dir))
main_header = os.path.join(base_dir, main_header)
header_dir = os.path.dirname(main_header)
licence_file = os.path.abspath(os.path.join(DIR, "../LICENSE"))
for f in files:
groups.read_header(f)
with open(licence_file) as f:
HeaderFile.LICENSE = "".join("// " + line for line in f)
groups["namespace"] = namespace
groups["version"] = version or groups["git"]
groups.post_process()
with open(main_header) as f:
header = f.read()
include_files = includes_local.findall(header)
headers = [HeaderFile(header_dir, inc) for inc in include_files]
single_header = reduce(add, headers)
single_header = f.read().format(**groups)
if macro is not None:
before = "CLI11_"
print("Converting macros", before, "->", macro)
single_header.macro_replacement(before, macro)
before, after = macro
print("Converting macros", before, "->", after)
single_header.replace(before, after)
if namespace:
print("Adding namespace", namespace)
single_header.insert_namespace(namespace)
if output is not None:
with open(output, "w") as f:
f.write(single_header)
with open(output, "w") as f:
f.write(str(single_header))
print("Created", output)
print("Created", output)
else:
print(single_header)
if __name__ == "__main__":
parser = ArgumentParser(
usage="Convert source to single header include. Can optionally add namespace and search-replace replacements (for macros)."
)
parser.add_argument("output", help="Single header file output")
parser.add_argument("--output", default=None, help="Single header file output")
parser.add_argument(
"--main",
default="CLI/CLI.hpp",
default="CLI11.hpp.in",
help="The main include file that defines the other files",
)
parser.add_argument("--include", default="../include", help="The include directory")
parser.add_argument("--namespace", help="Add an optional namespace")
parser.add_argument("--macro", help="Replaces CLI11_ with NEW_PREFIX_")
parser.add_argument("files", nargs="*", help="The header files")
parser.add_argument("--namespace", default="CLI", help="Set the namespace")
parser.add_argument("--tag", default="CLI11", help="Tag to look up")
parser.add_argument(
"--macro", nargs=2, help="Replaces OLD_PREFIX_ with NEW_PREFIX_"
)
parser.add_argument("--version", help="Include this version in the generated file")
args = parser.parse_args()
MakeHeader(args.output, args.main, args.include, args.namespace, args.macro)
MakeHeader(
args.output,
args.main,
args.files,
args.tag,
args.namespace,
args.macro,
args.version,
)