def report_results(self, results: List[str], prefix_if_suppressed: str = '') -> int: """ Print each result (a formatted violation), suppressing similar results if needed. """ emitted = 0 for result in results: printout(result) emitted += 1 # assuming each result is a violation "almost" identical to the rest if self.suppress_similar and emitted >= self.suppresses_after: remaining = len(results) - emitted # if results are being piped or redirected, we don't need to emit a diagnostic # note that the PyCharm bit is just for testing purposes should_notify = printing.results.isatty( ) or 'PYCHARM' in os.environ if remaining > 0 and should_notify: # note that this does not require --verbose; # when a suppression occurs it should always be mentioned printdiag('{0}(...{1} more suppressed)'.format( prefix_if_suppressed, remaining)) break return emitted
def check_for_update(): """ Determine whether a newer version is available remotely. """ from urllib.request import urlopen from urllib.error import URLError, HTTPError url = 'https://raw.githubusercontent.com/jhauberg/comply/master/comply/version.py' try: # specify a very short timeout, as this is a non-essential feature # and should not stall program exit indefinitely with urlopen(url, timeout=5) as response: # we're certain this file is UTF8, so we'll decode it right away response_body = response.read().decode('utf8') # search for the version string matches = re.search(VERSION_PATTERN, response_body, re.M) if matches: # if found, grab it and compare to the current installation remote_version_identifier = matches.group(1) if parse_version(__version__) < parse_version( remote_version_identifier): printdiag(Colors.GOOD + 'A newer version is available ({0})'.format( remote_version_identifier) + Colors.RESET) except HTTPError: # fail silently pass except URLError: # fail silently pass
def exit_if_not_compatible(): """ Warn and exit if system is running unsupported Python version. """ if not is_compatible(): printdiag('Python 3.5 or newer required', as_error=True) sys.exit(EXIT_CODE_FAILURE)
def not_checked(path: str, type: str, reason: str): """ Print a diagnostic stating when a file was not checked. """ if reason is not None: printdiag('{type} \'{path}\' was not checked ({reason}).'.format( type=type, path=path, reason=reason)) else: printdiag('{type} \'{path}\' was not checked.'.format(type=type, path=path))
def report_progress(self, count: int, total: int): """ Print a progress indication. """ if not self.is_verbose: return number_of_ticks = Reporter.determine_progress_ticks(count, total) printdiag('.' * number_of_ticks, end='')
def print_rules_checked(rules: list, since_starting): """ Print the number of rules checked and time taken. """ time_since_report = datetime.datetime.now() - since_starting report_in_seconds = time_since_report / datetime.timedelta(seconds=1) total_time_taken = report_in_seconds num_rules = len(rules) rules_grammar = 'rule' if num_rules == 1 else 'rules' printdiag('Checked {0} {1} in {2:.1f} seconds'.format( num_rules, rules_grammar, total_time_taken))
def make_reporter(reporting_mode: str) -> Reporter: """ Return a reporter appropriate for the mode. """ if reporting_mode == 'human': return HumanReporter() elif reporting_mode == 'oneline': return OneLineReporter() elif reporting_mode == 'xcode': return XcodeReporter() printdiag('Reporting mode \'{0}\' not available.'.format(reporting_mode), as_error=True) return Reporter()
def print_invalid_names(names: list, rules: list): """ Go through and determine whether any of the provided names do not exist as named rules. """ for name in names: if not is_name_valid(name, rules): # attempt fixing the name to provide a suggestion suggested_name = name.replace('_', '-').replace(' ', '-') if is_name_valid(suggested_name, rules): printdiag( 'Rule \'{rule}\' does not exist. Did you mean \'{suggestion}\'?' .format(rule=name, suggestion=suggested_name)) else: printdiag('Rule \'{rule}\' does not exist.'.format(rule=name))
def determine_version_or_exit() -> str: """ Determine version identifier or exit the program. """ with open('comply/version.py') as file: version_contents = file.read() version_match = re.search(VERSION_PATTERN, version_contents, re.M) if version_match: version = version_match.group(1) return version printdiag('Version could not be determined') sys.exit(EXIT_CODE_FAILURE)
def report_before_results(self, violations: List[RuleViolation]): """ Print a diagnostic before reporting results. This diagnostic should indicate the total number of violations collected; not the number of results to print (some may be suppressed). """ if not self.is_verbose: return count = len(violations) violation_or_violations = 'violation' if count == 1 else 'violations' diag = ' Found {0} {1}'.format(count, violation_or_violations) printdiag(diag)
def print_report(report: CheckResult): """ Print the number of violations found in a report. """ # note the whitespace; important for the full format later on severe_format = '({0} severe) ' if report.num_severe_violations > 0 else '' severe_format = severe_format.format(report.num_severe_violations) total_violations = report.num_violations + report.num_severe_violations violations_grammar = 'violation' if total_violations == 1 else 'violations' files_format = '{1}/{0}' if report.num_files_with_violations > 0 else '{0}' files_format = files_format.format(report.num_files, report.num_files_with_violations) printdiag('Found {num_violations} {violations} {severe}' 'in {files} files'.format(num_violations=total_violations, violations=violations_grammar, severe=severe_format, files=files_format))
def print_profiling_results(rules: list): """ Print benchmarking results/time taken for each rule. """ num_rules_profiled = 0 for rule in rules: time_taken = rule.total_time_spent_collecting if time_taken >= 0.1: printdiag(' [{0}] took {1:.1f} seconds'.format( rule.name, rule.total_time_spent_collecting)) num_rules_profiled += 1 num_rules_not_profiled = len(rules) - num_rules_profiled if num_rules_not_profiled > 0: printdiag( ' (...{0} rules took nearly no time and were not shown)'.format( num_rules_not_profiled))
def report_before_checking(self, path: str, encoding: str = None, show_progress: bool = True): """ Print a diagnostic before initiating a check on a given file. """ if self.is_verbose: normalized_path = os.path.normpath(path) encoding = (' ({0})'.format(encoding.upper()) if encoding is not None else '') progress = (' [{n:0{width}d}/{total}]'.format( n=self.files_encountered, width=len(str(self.files_total)), total=self.files_total) if self.files_total > 1 and show_progress else '') diag = 'Checking \'{path}\'{enc}{progress} '.format( path=truncated(normalized_path), enc=encoding, progress=progress) printdiag(diag, end='')
def main(): """ Entry point for invoking the comply module. """ exit_if_not_compatible() if comply.PROFILING_IS_ENABLED: printdiag(( 'Profiling is enabled by default; ' 'profiling should only be enabled through --profile or for debugging purposes' ), as_error=True) if not supports_unicode(): if not is_windows_environment(): # do not warn about this on Windows, as it probably won't work anyway printdiag('Unsupported shell encoding \'{0}\'. ' 'Set environment variable `PYTHONIOENCODING` as UTF-8:\n' '\texport PYTHONIOENCODING=UTF-8'.format( diagnostics.encoding), as_error=True) arguments = docopt(__doc__, version='comply ' + __version__) enable_profiling = arguments['--profile'] comply.PROFILING_IS_ENABLED = enable_profiling is_verbose = arguments['--verbose'] if enable_profiling and not is_verbose: printdiag('Profiling is enabled; --verbose was set automatically') is_verbose = True is_strict = arguments['--strict'] only_severe = arguments['--only-severe'] checks = expand_params(arguments['--check']) exceptions = expand_params(arguments['--except']) severities = ([RuleViolation.DENY] if only_severe else [ RuleViolation.DENY, RuleViolation.WARN, RuleViolation.ALLOW ]) # remove potential duplicates checks = list(set(checks)) exceptions = list(set(exceptions)) rules = filtered_rules(checks, exceptions, severities) reporting_mode = arguments['--reporter'] reporter = make_reporter(reporting_mode) reporter.suppress_similar = not is_strict reporter.is_strict = is_strict reporter.is_verbose = is_verbose if arguments['--limit'] is not None: reporter.limit = int(arguments['--limit']) if not comply.printing.results.isatty() and reporter.suppress_similar: # when piping output elsewhere, let it be known that some results might be suppressed printdiag('Suppressing similar violations; results may be omitted ' '(set `--strict` to show everything)') inputs = arguments['<input>'] time_started_report = datetime.datetime.now() report = make_report(inputs, rules, reporter) should_emit_verbose_diagnostics = reporter.is_verbose and report.num_files > 0 if should_emit_verbose_diagnostics: print_rules_checked(rules, since_starting=time_started_report) if comply.PROFILING_IS_ENABLED: print_profiling_results(rules) if should_emit_verbose_diagnostics: print_report(report) if report.num_severe_violations > 0: # everything went fine; severe violations were encountered sys.exit(EXIT_CODE_SUCCESS_WITH_SEVERE_VIOLATIONS) else: # everything went fine; violations might have been encountered sys.exit(EXIT_CODE_SUCCESS)
https://github.com/jhauberg/comply Copyright 2018 Jacob Hauberg Hansen. License: MIT (see LICENSE) """ import sys import re from setuptools import setup, find_packages from comply import VERSION_PATTERN, EXIT_CODE_FAILURE, is_compatible from comply.printing import printdiag if not is_compatible(): printdiag('Python 3.5 or newer required') sys.exit(EXIT_CODE_FAILURE) def determine_version_or_exit() -> str: """ Determine version identifier or exit the program. """ with open('comply/version.py') as file: version_contents = file.read() version_match = re.search(VERSION_PATTERN, version_contents, re.M) if version_match: version = version_match.group(1) return version