1#!/usr/bin/env python
2
3import argparse
4import os, sys
5import re
6import datetime as dt
7
8# python 3 compatibility
9try:
10    import cStringIO as sstream
11except ImportError:
12    from io import StringIO
13
14description = "Converts sol to a single file for convenience."
15
16# command line parser
17parser = argparse.ArgumentParser(usage='%(prog)s [options...]', description=description)
18parser.add_argument('--output', '-o', help='name and location of where to place file', metavar='file', default='sol.hpp')
19parser.add_argument('--quiet', help='suppress all output', action='store_true')
20args = parser.parse_args()
21
22script_path = os.path.normpath(os.path.dirname(os.path.realpath(__file__)))
23working_dir = os.getcwd()
24os.chdir(script_path)
25
26intro = """// The MIT License (MIT)
27
28// Copyright (c) 2013-2016 Rapptz, ThePhD and contributors
29
30// Permission is hereby granted, free of charge, to any person obtaining a copy of
31// this software and associated documentation files (the "Software"), to deal in
32// the Software without restriction, including without limitation the rights to
33// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
34// the Software, and to permit persons to whom the Software is furnished to do so,
35// subject to the following conditions:
36
37// The above copyright notice and this permission notice shall be included in all
38// copies or substantial portions of the Software.
39
40// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
42// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
43// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
44// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
45// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
46
47// This file was generated with a script.
48// Generated {time} UTC
49// This header was generated with sol {version} (revision {revision})
50// https://github.com/ThePhD/sol2
51
52#ifndef {guard}
53#define {guard}
54
55"""
56
57module_path = os.path.join(script_path)
58
59includes = set([])
60standard_include = re.compile(r'#include <(.*?)>')
61local_include = re.compile(r'#include "(.*?)"')
62ifndef_cpp = re.compile(r'#ifndef SOL_.*?_HPP')
63define_cpp = re.compile(r'#define SOL_.*?_HPP')
64endif_cpp = re.compile(r'#endif // SOL_.*?_HPP')
65
66def get_include(line, base_path):
67    local_match = local_include.match(line)
68    if local_match:
69        # local include found
70        full_path = os.path.normpath(os.path.join(base_path, local_match.group(1))).replace('\\', '/')
71        return full_path
72
73    return None
74
75
76def is_include_guard(line):
77    return ifndef_cpp.match(line) or define_cpp.match(line) or endif_cpp.match(line)
78
79def get_revision():
80    return os.popen('git rev-parse --short HEAD').read().strip()
81
82def get_version():
83    return os.popen('git describe --tags --abbrev=0').read().strip()
84
85def process_file(filename, out):
86    global includes
87    filename = os.path.normpath(filename)
88    relativefilename = filename.replace(script_path + os.sep, "").replace("\\", "/")
89
90    if filename in includes:
91        return
92
93    includes.add(filename)
94
95    if not args.quiet:
96        print('processing {}'.format(filename))
97
98    out.write('// beginning of {}\n\n'.format(relativefilename))
99    empty_line_state = True
100
101    with open(filename, 'r', encoding='utf-8') as f:
102        for line in f:
103            # skip comments
104            if line.startswith('//'):
105                continue
106
107            # skip include guard non-sense
108            if is_include_guard(line):
109                continue
110
111            # get relative directory
112            base_path = os.path.dirname(filename)
113
114            # check if it's a standard file
115            std = standard_include.search(line)
116            if std:
117                std_file = os.path.join('std', std.group(0))
118                if std_file in includes:
119                    continue
120                includes.add(std_file)
121
122            # see if it's an include file
123            name = get_include(line, base_path)
124
125            if name:
126                process_file(name, out)
127                continue
128
129            empty_line = len(line.strip()) == 0
130
131            if empty_line and empty_line_state:
132                continue
133
134            empty_line_state = empty_line
135
136            # line is fine
137            out.write(line)
138
139    out.write('// end of {}\n\n'.format(relativefilename))
140
141
142version = get_version()
143revision = get_revision()
144include_guard = 'SOL_SINGLE_INCLUDE_HPP'
145
146if not args.quiet:
147    print('Creating single header for sol')
148    print('Current version: {version} (revision {revision})\n'.format(version = version, revision = revision))
149
150
151processed_files = [os.path.join(script_path, x) for x in ['sol.hpp']]
152result = ''
153
154ss = StringIO()
155ss.write(intro.format(time=dt.datetime.utcnow(), revision=revision, version=version, guard=include_guard))
156for processed_file in processed_files:
157    process_file(processed_file, ss)
158
159ss.write('#endif // {}\n'.format(include_guard))
160result = ss.getvalue()
161ss.close()
162
163with open(args.output, 'w', encoding='utf-8') as f:
164    f.write(result)
165
166