#!/usr/bin/python # Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ import sys import logging import optparse import re import os import shutil import time from cStringIO import StringIO MAX_LINE_LENGTH = 80 FILE_TYPE_CONFIG = { 'php': {'prefix': ' * ', 'firstComment': '/**', 'lastComment': ' */', 'linesBefore': 0, 'linesAfter': 0}, 'js': {'prefix': ' * ', 'firstComment': '/**', 'lastComment': ' */', 'linesBefore': 0, 'linesAfter': 0}, 'py': {'prefix': '# ', 'firstComment': None, 'lastComment': None, 'linesBefore': 0, 'linesAfter': 0}, 'less': {'prefix': ' * ', 'firstComment': '/**', 'lastComment': ' */', 'linesBefore': 0, 'linesAfter': 0} } REPLACE_TOKENS = { 'YEAR': time.strftime('%Y') } SECTION_MARKER = re.compile(r'\{\{\{ICINGA_LICENSE_HEADER\}\}\}') LICENSE_DATA = None __version__ = '1.0' __LICENSE_STORE = {} __SUFFIX_MATCHER = None class LogFormatter(logging.Formatter): """Log formatter with color support which is enabled automaticallyby importing it.""" def __init__(self, *args, **kwargs): logging.Formatter.__init__(self, *args, **kwargs) self._color = sys.stderr.isatty() if self._color: self._colors = { logging.DEBUG: ('\x1b[34m',), # Blue logging.INFO: ('\x1b[32m',), # Green logging.WARNING: ('\x1b[33m',), # Yellow logging.ERROR: ('\x1b[31m',), # Red logging.CRITICAL: ('\x1b[1m', '\x1b[31m'), # Bold, Red } self._footer = '\x1b[0m' def format(self, record): formatted_message = logging.Formatter.format(self, record) if self._color: formatted_message = (''.join(self._colors.get(record.levelno)) + formatted_message + len(self._colors.get(record.levelno)) * self._footer) return formatted_message def add_optparse_logging_options(parser, default_loglevel='DEBUG'): """Add log levels to option parser""" LOGLEVELS = ('INFO', 'WARNING', 'ERROR', 'CRITICAL', 'DEBUG') parser.add_option('-v', '--verbose', dest='logging_level', default=default_loglevel, choices=LOGLEVELS, help="Print verbose informational messages. One of %s. [default: %%default]" % ', '.join( LOGLEVELS)) def init_logging(loglevel): """Initialize loglevels""" channel = logging.StreamHandler() channel.setFormatter(LogFormatter(fmt='%(asctime)-15s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')) logging.getLogger().addHandler(channel) logging.getLogger().setLevel(getattr(logging, loglevel)) return logging.getLogger(__name__) def init_optparse(): """Initialize opt parser""" parser = optparse.OptionParser(usage='%prog -d -L -B ', version='%%prog %s' % __version__) parser.add_option("-d", "--directory", action="append", type="string", dest="dir", help="Directory, multiple switches possible") parser.add_option("-L", "--license", action="store", type="string", dest="license", help="Path to license file") parser.add_option("-B", "--backup", action="store", type="string", dest="backup", help="Backup suffix, e.g. '.BAK'") add_optparse_logging_options(parser) return parser def match_file_suffix(file_name): """Test if er have configuration for this file""" global __SUFFIX_MATCHER if __SUFFIX_MATCHER == None: keys = FILE_TYPE_CONFIG.keys() match = r'\.(' + '|'.join(keys) + r')$' __SUFFIX_MATCHER = re.compile(match) return __SUFFIX_MATCHER.search(file_name) def load_files(dirs): """Load all files found into an array""" filelist = [] for directory in dirs: for root, subFolders, files in os.walk(directory): for file_name in files: if match_file_suffix(file_name): filelist.append(os.path.join(root, file_name)) return filelist def count_regex_matches(pattern, string): """Counting regex matchings, e.g. tokens in a file""" total = 0 start = 0 while True: match_object = pattern.search(string, start) if match_object is None: return total total += 1 start = match_object.start() + 1 def test_license_token(data, file_name): """Test if we have a valid license token in a file""" global SECTION_MARKER c = count_regex_matches(SECTION_MARKER, data) log = logging.getLogger(__name__) if c == 2: return True elif c == 0: log.warn('No license token in file %s', file_name) elif c < 2: log.error('Incomplete license token in file %s', file_name) else: log.error('More that one license token in file %s', file_name) return False def get_license(type): """Creates license data for a specific configuration""" global FILE_TYPE_CONFIG global LICENSE_DATA global REPLACE_TOKENS global __LICENSE_STORE try: return __LICENSE_STORE[type] except(KeyError): if not LICENSE_DATA: __LICENSE_STORE[type] = '' return '' config = FILE_TYPE_CONFIG[type] license_data = [] license_data.extend([''] * config['linesBefore']) if config['firstComment'] != None: license_data.append(config['firstComment']) for line in LICENSE_DATA.split('\n'): if line: license_data.append(config['prefix'] + line) else: # Whitespace is uselses in this case (#4603) license_data.append(config['prefix'].rstrip()) if config['lastComment'] != None: license_data.append(config['lastComment']) license_data.extend([''] * config['linesAfter']) __LICENSE_STORE[type] = '\n'.join(license_data) __LICENSE_STORE[type] = __LICENSE_STORE[type] % REPLACE_TOKENS return __LICENSE_STORE[type] def read_file_content(file_name): """Read file into a string""" fhandle = open(file_name, 'r') content = fhandle.read() fhandle.close return content def write_file_content(file_name, content): """Write a string into a file""" fhandle = open(file_name, 'w') fhandle.write(content) fhandle.close() def replace_text(org_data, license_data): """Replace the license token in the string""" shandle = StringIO(org_data) out = '' test = False while True: line = shandle.readline() if line == '': break if SECTION_MARKER.search(line) and test == False: test = True elif SECTION_MARKER.search(line) and test == True: test = False if license_data: out += license_data out += '\n' elif test == True: continue out += line shandle.close() return out def process_files(files, backup): """Iterate over files and trigger reokacement""" global FILE_TYPE_CONFIG log = logging.getLogger(__name__) for file_name in files: data = read_file_content(file_name) if test_license_token(data, file_name): base, ext = os.path.splitext(file_name) ext = ext[1:] try: config = FILE_TYPE_CONFIG[ext] license_data = get_license(ext) new_data = replace_text(data, license_data) if data != new_data: log.info('File changed: %s', file_name) if backup: newfile = file_name + backup log.info('Backup %s to %s', file_name, newfile) shutil.copy(file_name, newfile) write_file_content(file_name, new_data) log.info('Written to file: %s', file_name) except (KeyError): log.error('No header config for file type %s', ext) continue def main(): """Main script entry point""" global LICENSE_DATA parser = init_optparse() (options, args) = parser.parse_args() log = init_logging(options.logging_level) if options.dir is None or not len(options.dir) or not options.license: log.error('--dir and --license are mandatory') parser.print_help() return (1) log.debug('starting') LICENSE_DATA = read_file_content(options.license) log.info('Scanning directories ...') files = load_files(options.dir) log.info('Got %d matching ones', len(files)) log.info('Processing files ...') process_files(files, options.backup) if __name__ == '__main__': sys.exit(main())