#!/usr/bin/env python 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 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 """ 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" def __init__(self, base, inc): with open(os.path.join(base, inc)) as f: inner = f.read() version = version_finder.search(inner) if version: self.__class__.VERSION = version.groups()[0] # 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 = [] self.headers = set(includes_system.findall(inner)) 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 MakeHeader( output, main_header, include_dir="../include", namespace=None, macro=None ): # Set tag if possible to class variable try: proc = Popen( ["git", "describe", "--tags", "--always"], cwd=str(DIR), stdout=PIPE ) out, _ = proc.communicate() except OSError: pass else: if proc.returncode == 0: HeaderFile.TAG = out.decode("utf-8").strip() base_dir = os.path.abspath(os.path.join(DIR, include_dir)) main_header = os.path.join(base_dir, main_header) licence_file = os.path.abspath(os.path.join(DIR, "../LICENSE")) with open(licence_file) as f: HeaderFile.LICENSE = "".join("// " + line for line in f) with open(main_header) as f: header = f.read() include_files = includes_local.findall(header) headers = [HeaderFile(base_dir, inc) for inc in include_files] single_header = reduce(add, headers) if macro is not None: before = "CLI11_" print("Converting macros", before, "->", macro) single_header.macro_replacement(before, macro) if namespace: print("Adding namespace", namespace) single_header.insert_namespace(namespace) with open(output, "w") as f: f.write(str(single_header)) print("Created", output) 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( "--main", default="CLI/CLI.hpp", 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_") args = parser.parse_args() MakeHeader(args.output, args.main, args.include, args.namespace, args.macro)