#!/usr/bin/env python from __future__ import print_function, unicode_literals import os import re import argparse import operator 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(operator.add, headers) if macro: before, after = macro print("Converting macros", before, "->", after) single_header.macro_replacement(before, after) 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 = argparse.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", nargs=2, help="Macro replacement: CLI11_ NEW_PREFIX_") args = parser.parse_args() MakeHeader(args.output, args.main, args.include, args.namespace, args.macro)