| # 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") |