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/29627158/3115457 for further information.
9
10import argparse
11import sys
12import os.path
13import re
14import fileinput
15import datetime
16import glob
17
18
19def check_header_comment(filename):
20    """Checks if the header-comment of the given file needs fixing."""
21    # Check input file.
22    name = os.path.basename( filename )
23    # Read content of input file.
24    sourcefile = open( filename, "rU" )
25    content = sourcefile.read()
26    sourcefile.close()
27    # Search content for '$Id$'.
28    match = re.search(r'\$Id\$', content)
29    if match == None:
30        # Make sure that the correct value for '$Id$' was already set.
31        match = re.search(r'\$Id: ' + name + r'\s+[^$]+\$', content)
32        if match != None:
33            # The given file needs no fixing.
34            return False
35    # The given file needs fixing.
36    return True
37
38
39def check_input_files_for_variadic_seq(headerDir, sourceDir):
40    """Checks if files, used as input when pre-processing MPL-containers in their variadic form, need fixing."""
41    # Check input files in include/source-directories.
42    files  = glob.glob( os.path.join( headerDir, "*.hpp" ) )
43    files += glob.glob( os.path.join( headerDir, "aux_", "*.hpp" ) )
44    files += glob.glob( os.path.join( sourceDir, "src", "*" ) )
45    for currentFile in sorted( files ):
46        if check_header_comment( currentFile ):
47            return True
48    return False
49
50
51def check_input_files_for_numbered_seq(sourceDir, suffix, containers):
52    """Check if files, used as input when pre-processing MPL-containers in their numbered form, need fixing."""
53    # Check input files for each MPL-container type.
54    for container in containers:
55        files = glob.glob( os.path.join( sourceDir, container, container + '*' + suffix ) )
56        for currentFile in sorted( files ):
57            if check_header_comment( currentFile ):
58                return True
59    return False
60
61
62def check_input_files(headerDir, sourceDir, containers=['vector', 'list', 'set', 'map'],
63                      seqType='both', verbose=False):
64    """Checks if source- and header-files, used as input when pre-processing MPL-containers, need fixing."""
65    # Check the input files for containers in their variadic form.
66    result1 = False
67    if seqType == "both" or seqType == "variadic":
68        if verbose:
69            print "Check if input files for pre-processing Boost.MPL variadic containers need fixing."
70        result1 = check_input_files_for_variadic_seq(headerDir, sourceDir)
71        if verbose:
72            if result1:
73                print "  At least one input file needs fixing!"
74            else:
75                print "  No input file needs fixing!"
76    # Check the input files for containers in their numbered form.
77    result2 = False
78    result3 = False
79    if seqType == "both" or seqType == "numbered":
80        if verbose:
81            print "Check input files for pre-processing Boost.MPL numbered containers."
82        result2 = check_input_files_for_numbered_seq(headerDir, ".hpp", containers)
83        result3 = check_input_files_for_numbered_seq(sourceDir, ".cpp", containers)
84        if verbose:
85            if result2 or result3:
86                print "  At least one input file needs fixing!"
87            else:
88                print "  No input file needs fixing!"
89    # Return result.
90    return result1 or result2 or result3
91
92def fix_header_comment(filename, timestamp):
93    """Fixes the header-comment of the given file."""
94    # Fix input file.
95    name = os.path.basename( filename )
96    for line in fileinput.input( filename, inplace=1, mode="rU" ):
97        # If header-comment already contains anything for '$Id$', remove it.
98        line = re.sub(r'\$Id:[^$]+\$', r'$Id$', line.rstrip())
99        # Replace '$Id$' by a string containing the file's name (and a timestamp)!
100        line = re.sub(re.escape(r'$Id$'), r'$Id: ' + name + r' ' + timestamp.isoformat() + r' $', line.rstrip())
101        print(line)
102
103
104def fix_input_files_for_variadic_seq(headerDir, sourceDir, timestamp):
105    """Fixes files used as input when pre-processing MPL-containers in their variadic form."""
106    # Fix files in include/source-directories.
107    files  = glob.glob( os.path.join( headerDir, "*.hpp" ) )
108    files += glob.glob( os.path.join( headerDir, "aux_", "*.hpp" ) )
109    files += glob.glob( os.path.join( sourceDir, "src", "*" ) )
110    for currentFile in sorted( files ):
111        fix_header_comment( currentFile, timestamp )
112
113
114def fix_input_files_for_numbered_seq(sourceDir, suffix, timestamp, containers):
115    """Fixes files used as input when pre-processing MPL-containers in their numbered form."""
116    # Fix input files for each MPL-container type.
117    for container in containers:
118        files = glob.glob( os.path.join( sourceDir, container, container + '*' + suffix ) )
119        for currentFile in sorted( files ):
120            fix_header_comment( currentFile, timestamp )
121
122
123def fix_input_files(headerDir, sourceDir, containers=['vector', 'list', 'set', 'map'],
124                    seqType='both', verbose=False):
125    """Fixes source- and header-files used as input when pre-processing MPL-containers."""
126    # The new modification time.
127    timestamp = datetime.datetime.now();
128    # Fix the input files for containers in their variadic form.
129    if seqType == "both" or seqType == "variadic":
130        if verbose:
131            print "Fix input files for pre-processing Boost.MPL variadic containers."
132        fix_input_files_for_variadic_seq(headerDir, sourceDir, timestamp)
133    # Fix the input files for containers in their numbered form.
134    if seqType == "both" or seqType == "numbered":
135        if verbose:
136            print "Fix input files for pre-processing Boost.MPL numbered containers."
137        fix_input_files_for_numbered_seq(headerDir, ".hpp", timestamp, containers)
138        fix_input_files_for_numbered_seq(sourceDir, ".cpp", timestamp, containers)
139
140
141def to_existing_absolute_path(string):
142    """Converts a path into its absolute path and verifies that it exists or throws an exception."""
143    value = os.path.abspath(string)
144    if not os.path.exists( value ) or not os.path.isdir( value ):
145        msg = '"%r" is not a valid path to a directory.' % string
146        raise argparse.ArgumentTypeError(msg)
147    return value
148
149
150def main():
151    """The main function."""
152
153    # Prepare and run cmdline-parser.
154    cmdlineParser = argparse.ArgumentParser(
155                    description="Fixes the input files used for pre-processing of Boost.MPL headers.")
156    cmdlineParser.add_argument("-v", "--verbose", dest='verbose', action='store_true',
157                               help="Be a little bit more verbose.")
158    cmdlineParser.add_argument("--check-only", dest='checkonly', action='store_true',
159                               help="Only checks if fixing is required.")
160    cmdlineParser.add_argument(dest='sourceDir', metavar="<source-dir>",
161                               type=to_existing_absolute_path,
162                               help="The source-directory of Boost.")
163    args = cmdlineParser.parse_args()
164
165    # Some verbose debug output.
166    if args.verbose:
167        print "Arguments extracted from command-line:"
168        print "  verbose           = ", args.verbose
169        print "  check-only        = ", args.checkonly
170        print "  source directory  = ", args.sourceDir
171
172    # The directories for header- and source files of Boost.MPL.
173    # NOTE: Assuming 'args.sourceDir' is the source-directory of the entire boost project.
174    headerDir = os.path.join( args.sourceDir, "boost", "mpl" )
175    sourceDir = os.path.join( args.sourceDir, "libs", "mpl", "preprocessed" )
176    # Check that the header/source-directories exist.
177    if not os.path.exists( headerDir ) or not os.path.exists( sourceDir ):
178        # Maybe 'args.sourceDir' is not the source-directory of the entire boost project
179        # but instead of the Boost.MPL git-directory, only?
180        headerDir = os.path.join( args.sourceDir, "include", "boost", "mpl" )
181        sourceDir = os.path.join( args.sourceDir, "preprocessed" )
182        if not os.path.exists( headerDir ) or not os.path.exists( sourceDir ):
183            cmdlineParser.print_usage()
184            print "error: Cannot find Boost.MPL header/source files in given Boost source-directory!"
185            sys.exit(0)
186
187    # Some verbose debug output.
188    if args.verbose:
189        print "Chosen header-directory: ", headerDir
190        print "Chosen source-directory: ", sourceDir
191
192    if args.checkonly:
193        # Check input files for generating pre-processed headers.
194        result = check_input_files(headerDir, sourceDir, verbose = args.verbose)
195        if result:
196            print "Fixing the input-files used for pre-processing of Boost.MPL headers IS required."
197        else:
198            print "Fixing the input-files used for pre-processing of Boost.MPL headers is NOT required."
199    else:
200        # Fix input files for generating pre-processed headers.
201        fix_input_files(headerDir, sourceDir, verbose = args.verbose)
202
203
204if __name__ == '__main__':
205    main()
206