| # Copyright (c) 2010 The Hewlett-Packard Development Company |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer; |
| # redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution; |
| # neither the name of the copyright holders nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import os |
| |
| # lanuage type for each file extension |
| lang_types = { |
| '.c' : "C", |
| '.cl' : "C", |
| '.h' : "C", |
| '.cc' : "C++", |
| '.hh' : "C++", |
| '.cxx' : "C++", |
| '.hxx' : "C++", |
| '.cpp' : "C++", |
| '.hpp' : "C++", |
| '.C' : "C++", |
| '.H' : "C++", |
| '.i' : "swig", |
| '.py' : "python", |
| '.pl' : "perl", |
| '.pm' : "perl", |
| '.s' : "asm", |
| '.S' : "asm", |
| '.l' : "lex", |
| '.ll' : "lex", |
| '.y' : "yacc", |
| '.yy' : "yacc", |
| '.isa' : "isa", |
| '.sh' : "shell", |
| '.slicc' : "slicc", |
| '.sm' : "slicc", |
| '.awk' : "awk", |
| '.el' : "lisp", |
| '.txt' : "text", |
| '.tex' : "tex", |
| '.mk' : "make", |
| '.dts' : "dts", |
| } |
| |
| # languages based on file prefix |
| lang_prefixes = ( |
| ('SCons', 'scons'), |
| ('Make', 'make'), |
| ('make', 'make'), |
| ('Doxyfile', 'doxygen'), |
| ) |
| |
| # languages based on #! line of first file |
| hash_bang = ( |
| ('python', 'python'), |
| ('perl', 'perl'), |
| ('sh', 'shell'), |
| ) |
| |
| # the list of all languages that we detect |
| all_languages = frozenset(lang_types.values()) |
| all_languages |= frozenset(lang for start,lang in lang_prefixes) |
| all_languages |= frozenset(lang for start,lang in hash_bang) |
| |
| def lang_type(filename, firstline=None, openok=True): |
| '''identify the language of a given filename and potentially the |
| firstline of the file. If the firstline of the file is not |
| provided and openok is True, open the file and read the first line |
| if necessary''' |
| |
| basename = os.path.basename(filename) |
| name,extension = os.path.splitext(basename) |
| |
| # first try to detect language based on file extension |
| try: |
| return lang_types[extension] |
| except KeyError: |
| pass |
| |
| # now try to detect language based on file prefix |
| for start,lang in lang_prefixes: |
| if basename.startswith(start): |
| return lang |
| |
| # if a first line was not provided but the file is ok to open, |
| # grab the first line of the file. |
| if firstline is None and openok: |
| handle = open(filename, 'r') |
| firstline = handle.readline() |
| handle.close() |
| |
| # try to detect language based on #! in first line |
| if firstline and firstline.startswith('#!'): |
| for string,lang in hash_bang: |
| if firstline.find(string) > 0: |
| return lang |
| |
| # sorry, we couldn't detect the language |
| return None |
| |
| # directories and files to ignore by default |
| default_dir_ignore = frozenset(('build', 'ext')) |
| default_file_ignore = frozenset(('parsetab.py', )) |
| |
| def find_files(base, languages=all_languages, |
| dir_ignore=default_dir_ignore, |
| file_ignore=default_file_ignore): |
| '''find all files in a directory and its subdirectories based on a |
| set of languages, ignore directories specified in dir_ignore and |
| files specified in file_ignore''' |
| if base[-1] != '/': |
| base += '/' |
| |
| def update_dirs(dirs): |
| '''strip the ignored directories out of the provided list''' |
| index = len(dirs) - 1 |
| for i,d in enumerate(reversed(dirs)): |
| if d in dir_ignore: |
| del dirs[index - i] |
| |
| # walk over base |
| for root,dirs,files in os.walk(base): |
| root = root.replace(base, '', 1) |
| |
| # strip ignored directories from the list |
| update_dirs(dirs) |
| |
| for filename in files: |
| if filename in file_ignore: |
| # skip ignored files |
| continue |
| |
| # try to figure out the language of the specified file |
| fullpath = os.path.join(base, root, filename) |
| language = lang_type(fullpath) |
| |
| # if the file is one of the langauges that we want return |
| # its name and the language |
| if language in languages: |
| yield fullpath, language |
| |
| def update_file(dst, src, language, mutator): |
| '''update a file of the specified language with the provided |
| mutator generator. If inplace is provided, update the file in |
| place and return the handle to the updated file. If inplace is |
| false, write the updated file to cStringIO''' |
| |
| # if the source and destination are the same, we're updating in place |
| inplace = dst == src |
| |
| if isinstance(src, str): |
| # if a filename was provided, open the file |
| if inplace: |
| mode = 'r+' |
| else: |
| mode = 'r' |
| src = open(src, mode) |
| |
| orig_lines = [] |
| |
| # grab all of the lines of the file and strip them of their line ending |
| old_lines = list(line.rstrip('\r\n') for line in src) |
| new_lines = list(mutator(old_lines, src.name, language)) |
| |
| for line in src: |
| line = line |
| |
| if inplace: |
| # if we're updating in place and the file hasn't changed, do nothing |
| if old_lines == new_lines: |
| return |
| |
| # otherwise, truncate the file and seek to the beginning. |
| dst = src |
| dst.truncate(0) |
| dst.seek(0) |
| elif isinstance(dst, str): |
| # if we're not updating in place and a destination file name |
| # was provided, create a file object |
| dst = open(dst, 'w') |
| |
| for line in new_lines: |
| dst.write(line) |
| dst.write('\n') |