1# Copyright Deniz Bahadir 2015 2# 3# Distributed under the Boost Software License, Version 1.0. 4# (See accompanying file LICENSE_1_0.txt or copy at 5# http://www.boost.org/LICENSE_1_0.txt) 6# 7# See http://www.boost.org/libs/mpl for documentation. 8# See http://stackoverflow.com/a/20660264/3115457 for further information. 9# See http://stackoverflow.com/a/29627158/3115457 for further information. 10 11import fix_boost_mpl_preprocess as fixmpl 12import argparse 13import sys 14import os 15import os.path 16import re 17import fileinput 18import shutil 19 20 21def create_more_container_files(sourceDir, suffix, maxElements, containers, containers2): 22 """Creates additional files for the individual MPL-containers.""" 23 24 # Create files for each MPL-container with 20 to 'maxElements' elements 25 # which will be used during generation. 26 for container in containers: 27 for i in range(20, maxElements, 10): 28 # Create copy of "template"-file. 29 newFile = os.path.join( sourceDir, container, container + str(i+10) + suffix ) 30 shutil.copyfile( os.path.join( sourceDir, container, container + "20" + suffix ), newFile ) 31 # Adjust copy of "template"-file accordingly. 32 for line in fileinput.input( newFile, inplace=1, mode="rU" ): 33 line = re.sub(r'20', '%TWENTY%', line.rstrip()) 34 line = re.sub(r'11', '%ELEVEN%', line.rstrip()) 35 line = re.sub(r'10(?![0-9])', '%TEN%', line.rstrip()) 36 line = re.sub(r'%TWENTY%', re.escape(str(i+10)), line.rstrip()) 37 line = re.sub(r'%ELEVEN%', re.escape(str(i + 1)), line.rstrip()) 38 line = re.sub(r'%TEN%', re.escape(str(i)), line.rstrip()) 39 print(line) 40 for container in containers2: 41 for i in range(20, maxElements, 10): 42 # Create copy of "template"-file. 43 newFile = os.path.join( sourceDir, container, container + str(i+10) + "_c" + suffix ) 44 shutil.copyfile( os.path.join( sourceDir, container, container + "20_c" + suffix ), newFile ) 45 # Adjust copy of "template"-file accordingly. 46 for line in fileinput.input( newFile, inplace=1, mode="rU" ): 47 line = re.sub(r'20', '%TWENTY%', line.rstrip()) 48 line = re.sub(r'11', '%ELEVEN%', line.rstrip()) 49 line = re.sub(r'10(?![0-9])', '%TEN%', line.rstrip()) 50 line = re.sub(r'%TWENTY%', re.escape(str(i+10)), line.rstrip()) 51 line = re.sub(r'%ELEVEN%', re.escape(str(i + 1)), line.rstrip()) 52 line = re.sub(r'%TEN%', re.escape(str(i)), line.rstrip()) 53 print(line) 54 55 56def create_input_for_numbered_sequences(headerDir, sourceDir, containers, maxElements): 57 """Creates additional source- and header-files for the numbered sequence MPL-containers.""" 58 # Create additional container-list without "map". 59 containersWithoutMap = containers[:] 60 try: 61 containersWithoutMap.remove('map') 62 except ValueError: 63 # We can safely ignore if "map" is not contained in 'containers'! 64 pass 65 # Create header/source-files. 66 create_more_container_files(headerDir, ".hpp", maxElements, containers, containersWithoutMap) 67 create_more_container_files(sourceDir, ".cpp", maxElements, containers, containersWithoutMap) 68 69 70def adjust_container_limits_for_variadic_sequences(headerDir, containers, maxElements): 71 """Adjusts the limits of variadic sequence MPL-containers.""" 72 for container in containers: 73 headerFile = os.path.join( headerDir, "limits", container + ".hpp" ) 74 regexMatch = r'(define\s+BOOST_MPL_LIMIT_' + container.upper() + r'_SIZE\s+)[0-9]+' 75 regexReplace = r'\g<1>' + re.escape( str(maxElements) ) 76 for line in fileinput.input( headerFile, inplace=1, mode="rU" ): 77 line = re.sub(regexMatch, regexReplace, line.rstrip()) 78 print(line) 79 80 81def current_boost_dir(): 82 """Returns the (relative) path to the Boost source-directory this file is located in (if any).""" 83 # Path to directory containing this script. 84 path = os.path.dirname( os.path.realpath(__file__) ) 85 # Making sure it is located in "${boost-dir}/libs/mpl/preprocessed". 86 for directory in reversed( ["libs", "mpl", "preprocessed"] ): 87 (head, tail) = os.path.split(path) 88 if tail == directory: 89 path = head 90 else: 91 return None 92 return os.path.relpath( path ) 93 94 95 96def to_positive_multiple_of_10(string): 97 """Converts a string into its encoded positive integer (greater zero) or throws an exception.""" 98 try: 99 value = int(string) 100 except ValueError: 101 msg = '"%r" is not a positive multiple of 10 (greater zero).' % string 102 raise argparse.ArgumentTypeError(msg) 103 if value <= 0 or value % 10 != 0: 104 msg = '"%r" is not a positive multiple of 10 (greater zero).' % string 105 raise argparse.ArgumentTypeError(msg) 106 return value 107 108 109def to_existing_absolute_path(string): 110 """Converts a path into its absolute path and verifies that it exists or throws an exception.""" 111 value = os.path.abspath(string) 112 if not os.path.exists( value ) or not os.path.isdir( value ): 113 msg = '"%r" is not a valid path to a directory.' % string 114 raise argparse.ArgumentTypeError(msg) 115 return value 116 117 118def main(): 119 """The main function.""" 120 121 # Find the current Boost source-directory in which this script is located. 122 sourceDir = current_boost_dir() 123 if sourceDir == None: 124 sourceDir = "" 125 126 # Prepare and run cmdline-parser. 127 cmdlineParser = argparse.ArgumentParser(description="A generator-script for pre-processed Boost.MPL headers.") 128 cmdlineParser.add_argument("-v", "--verbose", dest='verbose', action='store_true', 129 help="Be a little bit more verbose.") 130 cmdlineParser.add_argument("-s", "--sequence-type", dest='seqType', choices=['variadic', 'numbered', 'both'], 131 default='both', 132 help="Only update pre-processed headers for the selected sequence types, " 133 "either 'numbered' sequences, 'variadic' sequences or 'both' sequence " 134 "types. (Default=both)") 135 cmdlineParser.add_argument("--no-vector", dest='want_vector', action='store_false', 136 help="Do not update pre-processed headers for Boost.MPL Vector.") 137 cmdlineParser.add_argument("--no-list", dest='want_list', action='store_false', 138 help="Do not update pre-processed headers for Boost.MPL List.") 139 cmdlineParser.add_argument("--no-set", dest='want_set', action='store_false', 140 help="Do not update pre-processed headers for Boost.MPL Set.") 141 cmdlineParser.add_argument("--no-map", dest='want_map', action='store_false', 142 help="Do not update pre-processed headers for Boost.MPL Map.") 143 cmdlineParser.add_argument("--num-elements", dest='numElements', metavar="<num-elements>", 144 type=to_positive_multiple_of_10, default=100, 145 help="The maximal number of elements per container sequence. (Default=100)") 146 cmdlineParser.add_argument(dest='sourceDir', metavar="<source-dir>", default=current_boost_dir(), nargs='?', 147 type=to_existing_absolute_path, 148 help="The source-directory of Boost. (Default=\"" + sourceDir + "\")") 149 args = cmdlineParser.parse_args() 150 151 # Some verbose debug output. 152 if args.verbose: 153 print "Arguments extracted from command-line:" 154 print " verbose = ", args.verbose 155 print " source directory = ", args.sourceDir 156 print " num elements = ", args.numElements 157 print " sequence type = ", args.seqType 158 print " want: vector = ", args.want_vector 159 print " want: list = ", args.want_list 160 print " want: set = ", args.want_set 161 print " want: map = ", args.want_map 162 163 # Verify that we received any source-directory. 164 if args.sourceDir == None: 165 print "You should specify a valid path to the Boost source-directory." 166 sys.exit(0) 167 168 # The directories for header- and source files of Boost.MPL. 169 # NOTE: Assuming 'args.sourceDir' is the source-directory of the entire boost project. 170 headerDir = os.path.join( args.sourceDir, "boost", "mpl" ) 171 sourceDir = os.path.join( args.sourceDir, "libs", "mpl", "preprocessed" ) 172 # Check that the header/source-directories exist. 173 if not os.path.exists( headerDir ) or not os.path.exists( sourceDir ): 174 # Maybe 'args.sourceDir' is not the source-directory of the entire boost project 175 # but instead of the Boost.MPL git-directory, only? 176 headerDir = os.path.join( args.sourceDir, "include", "boost", "mpl" ) 177 sourceDir = os.path.join( args.sourceDir, "preprocessed" ) 178 if not os.path.exists( headerDir ) or not os.path.exists( sourceDir ): 179 cmdlineParser.print_usage() 180 print "error: Cannot find Boost.MPL header/source files in given Boost source-directory!" 181 sys.exit(0) 182 183 # Some verbose debug output. 184 if args.verbose: 185 print "Chosen header-directory: ", headerDir 186 print "Chosen source-directory: ", sourceDir 187 188 # Create list of containers for which files shall be pre-processed. 189 containers = [] 190 if args.want_vector: 191 containers.append('vector') 192 if args.want_list: 193 containers.append('list') 194 if args.want_set: 195 containers.append('set') 196 if args.want_map: 197 containers.append('map') 198 if containers == []: 199 print "Nothing to do." 200 print "(Why did you prevent generating pre-processed headers for all Boost.MPL container types?)" 201 sys.exit(0) 202 203 # Possibly fix the header-comments of input-files needed for pre-processing. 204 if args.verbose: 205 print "Checking if prior to pre-processing some input-files need fixing." 206 needFixing = fixmpl.check_input_files(headerDir, sourceDir, containers, args.seqType, args.verbose) 207 if needFixing: 208 if args.verbose: 209 print "Fixing of some input-files prior to pre-processing is needed." 210 print "Will fix them now!" 211 fixmpl.fix_input_files(headerDir, sourceDir, containers, args.seqType, args.verbose) 212 213 # Some verbose debug output. 214 if args.verbose: 215 print "Containers for which to pre-process headers: ", containers 216 217 # Create (additional) input files for generating pre-processed headers of numbered sequence MPL containers. 218 if args.seqType == "both" or args.seqType == "numbered": 219 create_input_for_numbered_sequences(headerDir, sourceDir, containers, args.numElements) 220 # Modify settings for generating pre-processed headers of variadic sequence MPL containers. 221 if args.seqType == "both" or args.seqType == "variadic": 222 adjust_container_limits_for_variadic_sequences(headerDir, containers, args.numElements) 223 224 # Generate MPL-preprocessed files. 225 os.chdir( sourceDir ) 226 if args.seqType == "both" or args.seqType == "numbered": 227 if args.want_vector: 228 if args.verbose: 229 print "Pre-process headers for Boost.MPL numbered vectors." 230 os.system( "python " + os.path.join( sourceDir, "preprocess_vector.py" ) + " all " + args.sourceDir ) 231 if args.want_list: 232 if args.verbose: 233 print "Pre-process headers for Boost.MPL numbered lists." 234 os.system( "python " + os.path.join( sourceDir, "preprocess_list.py" ) + " all " + args.sourceDir ) 235 if args.want_set: 236 if args.verbose: 237 print "Pre-process headers for Boost.MPL numbered sets." 238 os.system( "python " + os.path.join( sourceDir, "preprocess_set.py" ) + " all " + args.sourceDir ) 239 if args.want_map: 240 if args.verbose: 241 print "Pre-process headers for Boost.MPL numbered maps." 242 os.system( "python " + os.path.join( sourceDir, "preprocess_map.py" ) + " all " + args.sourceDir ) 243 if args.seqType == "both" or args.seqType == "variadic": 244 if args.verbose: 245 print "Pre-process headers for Boost.MPL variadic containers." 246 os.system( "python " + os.path.join( sourceDir, "preprocess.py" ) + " all " + args.sourceDir ) 247 248 249if __name__ == '__main__': 250 main() 251