From f4d2af691b6b9f0d8b2452a62c336369d0735fbc Mon Sep 17 00:00:00 2001 From: Martin Di Paola Date: Wed, 11 Dec 2019 14:15:16 +0000 Subject: [PATCH] Reformat the source code following PEP 8 style --- .style.yapf | 350 ++++++++++++++++++++++++++++++++ Makefile | 18 +- byexample/__init__.py | 2 +- byexample/byexample.py | 8 +- byexample/cache.py | 56 +++-- byexample/cmdline.py | 304 +++++++++++++++------------ byexample/common.py | 14 +- byexample/concern.py | 37 ++-- byexample/differ.py | 124 ++++++----- byexample/executor.py | 68 +++++-- byexample/expected.py | 116 +++++++---- byexample/finder.py | 222 ++++++++++++-------- byexample/init.py | 273 +++++++++++++++---------- byexample/jobs.py | 14 +- byexample/log.py | 93 +++++---- byexample/log_level.py | 7 +- byexample/modules/clipboard.py | 29 ++- byexample/modules/cond.py | 31 +-- byexample/modules/cpp.py | 47 +++-- byexample/modules/delimiters.py | 28 ++- byexample/modules/elixir.py | 75 ++++--- byexample/modules/gdb.py | 57 +++--- byexample/modules/javascript.py | 36 ++-- byexample/modules/php.py | 47 +++-- byexample/modules/progress.py | 87 ++++---- byexample/modules/python.py | 168 ++++++++++----- byexample/modules/ruby.py | 63 ++++-- byexample/modules/shell.py | 123 ++++++----- byexample/options.py | 31 ++- byexample/parser.py | 82 ++++---- byexample/parser_sm.py | 57 ++++-- byexample/runner.py | 160 +++++++++------ 32 files changed, 1876 insertions(+), 951 deletions(-) create mode 100644 .style.yapf diff --git a/.style.yapf b/.style.yapf new file mode 100644 index 00000000..5edcb515 --- /dev/null +++ b/.style.yapf @@ -0,0 +1,350 @@ +[style] +# Align closing bracket with visual indentation. +align_closing_bracket_with_visual_indent=True + +# Allow dictionary keys to exist on multiple lines. For example: +# +# x = { +# ('this is the first element of a tuple', +# 'this is the second element of a tuple'): +# value, +# } +allow_multiline_dictionary_keys=False + +# Allow lambdas to be formatted on more than one line. +allow_multiline_lambdas=False + +# Allow splitting before a default / named assignment in an argument list. +allow_split_before_default_or_named_assigns=True + +# Allow splits before the dictionary value. +allow_split_before_dict_value=True + +# Let spacing indicate operator precedence. For example: +# +# a = 1 * 2 + 3 / 4 +# b = 1 / 2 - 3 * 4 +# c = (1 + 2) * (3 - 4) +# d = (1 - 2) / (3 + 4) +# e = 1 * 2 - 3 +# f = 1 + 2 + 3 + 4 +# +# will be formatted as follows to indicate precedence: +# +# a = 1*2 + 3/4 +# b = 1/2 - 3*4 +# c = (1+2) * (3-4) +# d = (1-2) / (3+4) +# e = 1*2 - 3 +# f = 1 + 2 + 3 + 4 +# +arithmetic_precedence_indication=False + +# Number of blank lines surrounding top-level function and class +# definitions. +blank_lines_around_top_level_definition=2 + +# Insert a blank line before a class-level docstring. +blank_line_before_class_docstring=False + +# Insert a blank line before a module docstring. +blank_line_before_module_docstring=False + +# Insert a blank line before a 'def' or 'class' immediately nested +# within another 'def' or 'class'. For example: +# +# class Foo: +# # <------ this blank line +# def method(): +# ... +blank_line_before_nested_class_or_def=False + +# Do not split consecutive brackets. Only relevant when +# dedent_closing_brackets is set. For example: +# +# call_func_that_takes_a_dict( +# { +# 'key1': 'value1', +# 'key2': 'value2', +# } +# ) +# +# would reformat to: +# +# call_func_that_takes_a_dict({ +# 'key1': 'value1', +# 'key2': 'value2', +# }) +coalesce_brackets=False + +# The column limit. +column_limit=79 + +# The style for continuation alignment. Possible values are: +# +# - SPACE: Use spaces for continuation alignment. This is default behavior. +# - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns +# (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs) for continuation +# alignment. +# - VALIGN-RIGHT: Vertically align continuation lines with indent +# characters. Slightly right (one more indent character) if cannot +# vertically align continuation lines with indent characters. +# +# For options FIXED, and VALIGN-RIGHT are only available when USE_TABS is +# enabled. +continuation_align_style=SPACE + +# Indent width used for line continuations. +continuation_indent_width=4 + +# Put closing brackets on a separate line, dedented, if the bracketed +# expression can't fit in a single line. Applies to all kinds of brackets, +# including function definitions and calls. For example: +# +# config = { +# 'key1': 'value1', +# 'key2': 'value2', +# } # <--- this bracket is dedented and on a separate line +# +# time_series = self.remote_client.query_entity_counters( +# entity='dev3246.region1', +# key='dns.query_latency_tcp', +# transform=Transformation.AVERAGE(window=timedelta(seconds=60)), +# start_ts=now()-timedelta(days=3), +# end_ts=now(), +# ) # <--- this bracket is dedented and on a separate line +dedent_closing_brackets=True + +# Disable the heuristic which places each list element on a separate line +# if the list is comma-terminated. +disable_ending_comma_heuristic=False + +# Place each dictionary entry onto its own line. +each_dict_entry_on_separate_line=True + +# The regex for an i18n comment. The presence of this comment stops +# reformatting of that line, because the comments are required to be +# next to the string they translate. +i18n_comment= + +# The i18n function call names. The presence of this function stops +# reformattting on that line, because the string it has cannot be moved +# away from the i18n comment. +i18n_function_call= + +# Indent blank lines. +indent_blank_lines=False + +# Put closing brackets on a separate line, indented, if the bracketed +# expression can't fit in a single line. Applies to all kinds of brackets, +# including function definitions and calls. For example: +# +# config = { +# 'key1': 'value1', +# 'key2': 'value2', +# } # <--- this bracket is indented and on a separate line +# +# time_series = self.remote_client.query_entity_counters( +# entity='dev3246.region1', +# key='dns.query_latency_tcp', +# transform=Transformation.AVERAGE(window=timedelta(seconds=60)), +# start_ts=now()-timedelta(days=3), +# end_ts=now(), +# ) # <--- this bracket is indented and on a separate line +indent_closing_brackets=False + +# Indent the dictionary value if it cannot fit on the same line as the +# dictionary key. For example: +# +# config = { +# 'key1': +# 'value1', +# 'key2': value1 + +# value2, +# } +indent_dictionary_value=False + +# The number of columns to use for indentation. +indent_width=4 + +# Join short lines into one line. E.g., single line 'if' statements. +join_multiple_lines=True + +# Do not include spaces around selected binary operators. For example: +# +# 1 + 2 * 3 - 4 / 5 +# +# will be formatted as follows when configured with "*,/": +# +# 1 + 2*3 - 4/5 +no_spaces_around_selected_binary_operators= + +# Use spaces around default or named assigns. +spaces_around_default_or_named_assign=False + +# Use spaces around the power operator. +spaces_around_power_operator=False + +# The number of spaces required before a trailing comment. +# This can be a single value (representing the number of spaces +# before each trailing comment) or list of values (representing +# alignment column values; trailing comments within a block will +# be aligned to the first column value that is greater than the maximum +# line length within the block). For example: +# +# With spaces_before_comment=5: +# +# 1 + 1 # Adding values +# +# will be formatted as: +# +# 1 + 1 # Adding values <-- 5 spaces between the end of the statement and comment +# +# With spaces_before_comment=15, 20: +# +# 1 + 1 # Adding values +# two + two # More adding +# +# longer_statement # This is a longer statement +# short # This is a shorter statement +# +# a_very_long_statement_that_extends_beyond_the_final_column # Comment +# short # This is a shorter statement +# +# will be formatted as: +# +# 1 + 1 # Adding values <-- end of line comments in block aligned to col 15 +# two + two # More adding +# +# longer_statement # This is a longer statement <-- end of line comments in block aligned to col 20 +# short # This is a shorter statement +# +# a_very_long_statement_that_extends_beyond_the_final_column # Comment <-- the end of line comments are aligned based on the line length +# short # This is a shorter statement +# +spaces_before_comment=2 + +# Insert a space between the ending comma and closing bracket of a list, +# etc. +space_between_ending_comma_and_closing_bracket=True + +# Split before arguments +split_all_comma_separated_values=False + +# Split before arguments, but do not split all subexpressions recursively +# (unless needed). +split_all_top_level_comma_separated_values=False + +# Split before arguments if the argument list is terminated by a +# comma. +split_arguments_when_comma_terminated=False + +# Set to True to prefer splitting before '+', '-', '*', '/', '//', or '@' +# rather than after. +split_before_arithmetic_operator=False + +# Set to True to prefer splitting before '&', '|' or '^' rather than +# after. +split_before_bitwise_operator=True + +# Split before the closing bracket if a list or dict literal doesn't fit on +# a single line. +split_before_closing_bracket=True + +# Split before a dictionary or set generator (comp_for). For example, note +# the split before the 'for': +# +# foo = { +# variable: 'Hello world, have a nice day!' +# for variable in bar if variable != 42 +# } +split_before_dict_set_generator=True + +# Split before the '.' if we need to split a longer expression: +# +# foo = ('This is a really long string: {}, {}, {}, {}'.format(a, b, c, d)) +# +# would reformat to something like: +# +# foo = ('This is a really long string: {}, {}, {}, {}' +# .format(a, b, c, d)) +split_before_dot=False + +# Split after the opening paren which surrounds an expression if it doesn't +# fit on a single line. +split_before_expression_after_opening_paren=False + +# If an argument / parameter list is going to be split, then split before +# the first argument. +split_before_first_argument=False + +# Set to True to prefer splitting before 'and' or 'or' rather than +# after. +split_before_logical_operator=True + +# Split named assignments onto individual lines. +split_before_named_assigns=True + +# Set to True to split list comprehensions and generators that have +# non-trivial expressions and multiple clauses before each of these +# clauses. For example: +# +# result = [ +# a_long_var + 100 for a_long_var in xrange(1000) +# if a_long_var % 10] +# +# would reformat to something like: +# +# result = [ +# a_long_var + 100 +# for a_long_var in xrange(1000) +# if a_long_var % 10] +split_complex_comprehension=False + +# The penalty for splitting right after the opening bracket. +split_penalty_after_opening_bracket=300 + +# The penalty for splitting the line after a unary operator. +split_penalty_after_unary_operator=10000 + +# The penalty of splitting the line around the '+', '-', '*', '/', '//', +# ``%``, and '@' operators. +split_penalty_arithmetic_operator=300 + +# The penalty for splitting right before an if expression. +split_penalty_before_if_expr=0 + +# The penalty of splitting the line around the '&', '|', and '^' +# operators. +split_penalty_bitwise_operator=300 + +# The penalty for splitting a list comprehension or generator +# expression. +split_penalty_comprehension=80 + +# The penalty for characters over the column limit. +split_penalty_excess_character=7000 + +# The penalty incurred by adding a line split to the unwrapped line. The +# more line splits added the higher the penalty. +split_penalty_for_added_line_split=30 + +# The penalty of splitting a list of "import as" names. For example: +# +# from a_very_long_or_indented_module_name_yada_yad import (long_argument_1, +# long_argument_2, +# long_argument_3) +# +# would reformat to something like: +# +# from a_very_long_or_indented_module_name_yada_yad import ( +# long_argument_1, long_argument_2, long_argument_3) +split_penalty_import_names=0 + +# The penalty of splitting the line around the 'and' and 'or' +# operators. +split_penalty_logical_operator=300 + +# Use the Tab character for indentation. +use_tabs=False + diff --git a/Makefile b/Makefile index f3fff46b..fb531ad2 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,11 @@ all: @echo "Usage: make dist|upload" @echo "Package byexample (dist) and upload it to pypi (upload)" @echo + @echo "Usage: make format[-test]" + @echo "Format the source code following the PEP 8 style." + @echo "Use format-test to verify the complaince without touching" + @echo "the code" + @echo @echo "Usage: make coverage" @echo "Run several times variants of 'make test' with the coverage" @echo "activated and show the results." @@ -79,7 +84,7 @@ examples-test: clean_test index-links-test: clean_test @./test/idx.sh -test: lib-test modules-test docs-test examples-test index-links-test +test: lib-test modules-test docs-test examples-test index-links-test format-test # ## @@ -145,6 +150,17 @@ coverage: clean_test # ## +## Formatting +# ========== + +format: + yapf -vv -i --style=.style.yapf --recursive byexample/ + +format-test: + yapf -vv --style=.style.yapf --diff --recursive byexample/ +# +## + ## Packaging and clean up # ====================== diff --git a/byexample/__init__.py b/byexample/__init__.py index fddb3c5b..3cd4d767 100644 --- a/byexample/__init__.py +++ b/byexample/__init__.py @@ -2,7 +2,7 @@ __version__ = "9.0.0" -_author = 'Di Paola Martin' +_author = 'Di Paola Martin' _license = 'GNU GPLv3' _url = 'https://byexamples.github.io' diff --git a/byexample/byexample.py b/byexample/byexample.py index ab20030e..a631dd22 100644 --- a/byexample/byexample.py +++ b/byexample/byexample.py @@ -2,13 +2,16 @@ import os, sys if sys.version_info < (3, 0): - print("Are you using Python 2.x? Byexample no longer runs in that version. Please upgrade your Python environment.") + print( + "Are you using Python 2.x? Byexample no longer runs in that version. Please upgrade your Python environment." + ) sys.exit(1) from .cache import RegexCache from .jobs import Jobs, Status, allow_sigint from .log import init_log_system + def execute_examples(filename, sigint_handler): global cache, harvester, executor, options, dry from .common import human_exceptions @@ -26,13 +29,14 @@ def execute_examples(filename, sigint_handler): error = not user_aborted return True, True, user_aborted, error + def main(args=None): global cache, harvester, executor, options, dry init_log_system() cache_disabled = os.getenv('BYEXAMPLE_CACHE_DISABLED', "1") != "0" - cache_verbose = os.getenv('BYEXAMPLE_CACHE_VERBOSE', "0") != "0" + cache_verbose = os.getenv('BYEXAMPLE_CACHE_VERBOSE', "0") != "0" cache = RegexCache('0', cache_disabled, cache_verbose) with cache.activated(auto_sync=True, label="0"): diff --git a/byexample/cache.py b/byexample/cache.py index 2cba0d1d..c1a1d543 100644 --- a/byexample/cache.py +++ b/byexample/cache.py @@ -18,10 +18,11 @@ try: # See https://docs.python.org/3.6/library/msvcrt.html import msvcrt + def _at_begin(file): fpos = file.tell() try: - file.seek(0,0) + file.seek(0, 0) yield f finally: file.seek(fpos, 0) @@ -54,14 +55,18 @@ def _release_flock_os(file): try: # See https://docs.python.org/3/library/fcntl.html#fcntl.flock import fcntl + def _acquire_flock_os(file, read_lock): - fcntl.lockf(file.fileno(), fcntl.LOCK_SH if read_lock else fcntl.LOCK_EX) + fcntl.lockf( + file.fileno(), fcntl.LOCK_SH if read_lock else fcntl.LOCK_EX + ) def _release_flock_os(file): fcntl.lockf(file.fileno(), fcntl.LOCK_UN) except ImportError: pass + @contextlib.contextmanager def flock(file, read_lock=False): _acquire_flock_os(file, read_lock) @@ -87,6 +92,7 @@ def create_file_new_or_fail(name): >>> warnings.filterwarnings('ignore', module='byexample.cache') ''' + class RegexCache(object): def __init__(self, filename, disabled=False, cache_verbose=False): self.disabled = disabled @@ -94,8 +100,10 @@ def __init__(self, filename, disabled=False, cache_verbose=False): if self.disabled: return - warnings.warn("Cache is enabled. This is an *experimental* feature", - RuntimeWarning) + warnings.warn( + "Cache is enabled. This is an *experimental* feature", + RuntimeWarning + ) try: _acquire_flock_os @@ -103,8 +111,10 @@ def __init__(self, filename, disabled=False, cache_verbose=False): except NameError: # if we cannot prevent race condition due a lack of file locks, # disable the cache - warnings.warn("Cache will be disabled because the current OS/file system does not support file locks", - RuntimeWarning) + warnings.warn( + "Cache will be disabled because the current OS/file system does not support file locks", + RuntimeWarning + ) self.disabled = True if filename: @@ -219,8 +229,10 @@ def _create_empty_cache_in_disk(self): ''' Create an empty cache in disk if doesn't exist yet. ''' cache = self._new_cache() try: - clog().info("Cache file '%s' does not exist. Creating a new one...", - self.filename) + clog().info( + "Cache file '%s' does not exist. Creating a new one...", + self.filename + ) with create_file_new_or_fail(self.filename) as f, flock(f): # the open didn't fail, so it *must* be new: # save an empty cache @@ -230,14 +242,15 @@ def _create_empty_cache_in_disk(self): return cache - @log_context('byexample.cache') def _sync(self, label=""): misses = len(self._cache) - self._nkeys nohits = self._nkeys - self._hits - clog().chat("[%s] Cache stats: %i entries %i hits %i misses %i nohits.", - label, len(self._cache), self._hits, misses, nohits) + clog().chat( + "[%s] Cache stats: %i entries %i hits %i misses %i nohits.", label, + len(self._cache), self._hits, misses, nohits + ) if misses and self.filename != None: clog().chat("[%s] Cache require sync.", label) with open(self.filename, 'rb+') as f, flock(f): @@ -253,7 +266,7 @@ def _sync(self, label=""): # write to disk the new updated cache, truncate # and shrink it if the new is smaller than the original. - f.seek(0,0) + f.seek(0, 0) # XXX pickletools.optimize ?? pickle.dump(cache, f) f.truncate() @@ -292,8 +305,10 @@ def get(self, pattern, flags=0): ''' if not isinstance(pattern, (str, bytes)): - raise ValueError("Regex pattern must be a string or bytes but it is %s" - % type(pattern)) + raise ValueError( + "Regex pattern must be a string or bytes but it is %s" % + type(pattern) + ) key = (pattern, flags) try: @@ -307,8 +322,10 @@ def get(self, pattern, flags=0): def _pattern_to_bytecode(self, pattern, flags=0): if not isinstance(pattern, (str, bytes)): - raise ValueError("Regex pattern must be a string or bytes but it is %s" - % type(pattern)) + raise ValueError( + "Regex pattern must be a string or bytes but it is %s" % + type(pattern) + ) p = sre_parse.parse(pattern, flags) code = [i.real for i in sre_compile._code(p, flags)] @@ -326,11 +343,9 @@ def _bytecode_to_regex(self, pattern, bytecode): for k, i in groupindex.items(): indexgroup[i] = k - indexgroup = tuple(indexgroup) # required in Python 3.7 to be a tuple + indexgroup = tuple(indexgroup) # required in Python 3.7 to be a tuple return sre_compile._sre.compile( - pattern, flags, code, - ngroups-1, - groupindex, indexgroup + pattern, flags, code, ngroups - 1, groupindex, indexgroup ) @log_context('byexample.cache') @@ -345,4 +360,3 @@ def _patch(self): @log_context('byexample.cache') def _unpatch(self, *args, **kargs): sre_compile.compile = self._original__sre_compile__compile - diff --git a/byexample/cmdline.py b/byexample/cmdline.py index d7362625..cd8a3625 100644 --- a/byexample/cmdline.py +++ b/byexample/cmdline.py @@ -4,6 +4,7 @@ from .log_level import str_to_level + class _CSV(argparse.Action): r'''Transform an argument of the form 'a,b' into a list of arguments [a, b] @@ -24,19 +25,23 @@ def __init__(self, *args, **kargs): def __call__(self, parser, namespace, values, option_string=None): parser.exit(message=self.message) + def _key_val_type(item): try: key, val = [i.strip() for i in item.split(":", 1)] except: raise argparse.ArgumentTypeError( - "Invalid format '%s'. Use key:val instead." % item) + "Invalid format '%s'. Use key:val instead." % item + ) if not key or not val: raise argparse.ArgumentTypeError( - "Neither the key nor the value can be empty in '%s'." % item) + "Neither the key nor the value can be empty in '%s'." % item + ) return (key, val) + def _jobs_type(item): jobs_str = item.strip() ncpus = 1 @@ -56,10 +61,13 @@ def _jobs_type(item): assert jobs_num > 0 except: raise argparse.ArgumentTypeError( - "Invalid jobs specification '%s'. Use 'cpu', 'cpu' or (a positive number)." % item) + "Invalid jobs specification '%s'. Use 'cpu', 'cpu' or (a positive number)." + % item + ) return jobs_num * ncpus + class HelpExtraFormatter(argparse.HelpFormatter): __hide = True EPILOG = "==EPILOG==" @@ -105,7 +113,12 @@ def add_arguments(self, *args, **kargs): def add_usage(self, usage, actions, *args, **kargs): if self.__hide: - actions = list(filter(lambda a: a.option_strings and not a.option_strings[0].startswith('-x-'), actions)) + actions = list( + filter( + lambda a: a.option_strings and not a.option_strings[0]. + startswith('-x-'), actions + ) + ) argparse.HelpFormatter.add_usage(self, usage, actions, *args, **kargs) def __add_examples(self): @@ -114,10 +127,18 @@ def raw_print(text): self.start_section("Examples") self._add_item(raw_print, [" byexample -l python file.py\n"]) - self._add_item(raw_print, [" byexample -l python,ruby --ff --timeout=8 file.md\n"]) - self._add_item(raw_print, [" byexample -l python,ruby --show-options\n"]) + self._add_item( + raw_print, + [" byexample -l python,ruby --ff --timeout=8 file.md\n"] + ) + self._add_item( + raw_print, [" byexample -l python,ruby --show-options\n"] + ) self._add_item(raw_print, ["\n"]) - self._add_item(raw_print, ["See %s for the full documentation\nand more examples.\n" % _url]) + self._add_item( + raw_print, + ["See %s for the full documentation\nand more examples.\n" % _url] + ) self.end_section() @@ -134,42 +155,48 @@ def parse_args(args=None): If args is None, parse the sys.argv[1:]. ''' - search_default = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'modules') + search_default = os.path.join( + os.path.dirname(os.path.abspath(__file__)), 'modules' + ) python_version = sys.version.split(' ', 1)[0] parser = argparse.ArgumentParser( - fromfile_prefix_chars='@', - add_help=False, - formatter_class=HelpExtraFormatter, - description=__doc__, - epilog=HelpExtraFormatter.EPILOG) + fromfile_prefix_chars='@', + add_help=False, + formatter_class=HelpExtraFormatter, + description=__doc__, + epilog=HelpExtraFormatter.EPILOG + ) parser.add_argument( - "files", - nargs='*', - metavar='', - help="files that have the examples to run.") + "files", + nargs='*', + metavar='', + help="files that have the examples to run." + ) g = parser.add_argument_group("Language Selection") g.add_argument( - "-l", - "--language", - "--languages", - metavar='', - dest='languages', - action=_CSV, - required=True, - default=[], - help='select which languages to parse and run. '+ - 'Comma separated syntax is also accepted.') + "-l", + "--language", + "--languages", + metavar='', + dest='languages', + action=_CSV, + required=True, + default=[], + help='select which languages to parse and run. ' + + 'Comma separated syntax is also accepted.' + ) g = parser.add_argument_group("Execution Options") g.add_argument( - "--ff", - "--fail-fast", - action='store_true', - dest='fail_fast', - help="if an example fails, fail and stop all the execution.") + "--ff", + "--fail-fast", + action='store_true', + dest='fail_fast', + help="if an example fails, fail and stop all the execution." + ) g.add_argument( "--timeout", metavar='', @@ -188,23 +215,26 @@ def parse_args(args=None): '"cpu" means use all the cpus available; ' +\ '"cpu" multiply it by the cpus available.') g.add_argument( - "--dry", - action='store_true', - help="do not run any example, only parse them.") + "--dry", + action='store_true', + help="do not run any example, only parse them." + ) g.add_argument( - "--skip", - nargs='+', - metavar='', - default=[], - help='skip these files') + "--skip", + nargs='+', + metavar='', + default=[], + help='skip these files' + ) g = parser.add_argument_group("Diff Options") g.add_argument( - "-d", - "--diff", - choices=['none', 'unified', 'ndiff', 'context', 'tool'], - default='none', - help='select diff algorithm (%(default)s by default).') + "-d", + "--diff", + choices=['none', 'unified', 'ndiff', 'context', 'tool'], + default='none', + help='select diff algorithm (%(default)s by default).' + ) g.add_argument( "--difftool", metavar='', @@ -225,79 +255,91 @@ def parse_args(args=None): g = parser.add_argument_group("Miscellaneous Options") g.add_argument( - "-o", - "--options", - metavar='', - dest='options_str', - default="", - help='add additional options; see --show-options to list them.') + "-o", + "--options", + metavar='', + dest='options_str', + default="", + help='add additional options; see --show-options to list them.' + ) g.add_argument( - "--show-options", - action='store_true', - help="show the available options for the selected languages (with -l)") + "--show-options", + action='store_true', + help="show the available options for the selected languages (with -l)" + ) g.add_argument( - "-m", - "--modules", - action='append', - metavar='', - dest='modules_dirs', - default=[search_default], - help='append a directory for searching modules there.') + "-m", + "--modules", + action='append', + metavar='', + dest='modules_dirs', + default=[search_default], + help='append a directory for searching modules there.' + ) g.add_argument( - "--encoding", - metavar='', - default=sys.stdout.encoding, - help='select the encoding (default: %(default)s).') + "--encoding", + metavar='', + default=sys.stdout.encoding, + help='select the encoding (default: %(default)s).' + ) g.add_argument( - "--pretty", - choices=['none', 'all'], - default='all', - help="control how to pretty print the output.") + "--pretty", + choices=['none', 'all'], + default='all', + help="control how to pretty print the output." + ) g.add_argument( - '-V', - '--version', - nargs=0, - action=_Print, - message='{prog} {version} (Python {python_version}) - {license}\n\n{doc}' - '\n\n{license_disclaimer}'.format( - prog=parser.prog, - doc=__doc__, - version=__version__, - python_version=python_version, - license=_license, - license_disclaimer=_license_disclaimer.format( - author=_author, - url=_url)), - help='show %(prog)s\'s version and license, then exit') + '-V', + '--version', + nargs=0, + action=_Print, + message='{prog} {version} (Python {python_version}) - {license}\n\n{doc}' + '\n\n{license_disclaimer}'.format( + prog=parser.prog, + doc=__doc__, + version=__version__, + python_version=python_version, + license=_license, + license_disclaimer=_license_disclaimer.format( + author=_author, url=_url + ) + ), + help='show %(prog)s\'s version and license, then exit' + ) g = parser.add_argument_group("Logging") mutexg = g.add_mutually_exclusive_group() mutexg.add_argument( - "-v", - action='count', - dest='verbosity', - default=0, - help="verbosity level, add more flags to increase the level.") + "-v", + action='count', + dest='verbosity', + default=0, + help="verbosity level, add more flags to increase the level." + ) mutexg.add_argument( - "-q", - "--quiet", - action='store_true', - help="quiet mode, do not print anything even if an example fails; " - "suppress the progress output.") + "-q", + "--quiet", + action='store_true', + help="quiet mode, do not print anything even if an example fails; " + "suppress the progress output." + ) g = parser.add_argument_group("Help Options") mutexg = g.add_mutually_exclusive_group() mutexg.add_argument( - '-h', - '--help', - nargs=0, - action=_HelpExtraAction, - help='show this help message and exit') + '-h', + '--help', + nargs=0, + action=_HelpExtraAction, + help='show this help message and exit' + ) mutexg.add_argument( - '-xh', - nargs=0, - action=_HelpExtraAction, - help="show this help message plus the one for the advanced options and exit") + '-xh', + nargs=0, + action=_HelpExtraAction, + help= + "show this help message plus the one for the advanced options and exit" + ) g = parser.add_argument_group("Advanced Options") g.add_argument( @@ -313,27 +355,34 @@ def parse_args(args=None): 'and arguments (however no all ' + \ 'the runners will honor this and some may break).') g.add_argument( - "-x-dfl-timeout", - metavar="", - default=8, - type=float, - help='timeout in seconds for internal operations (default: %(default)s).') + "-x-dfl-timeout", + metavar="", + default=8, + type=float, + help='timeout in seconds for internal operations (default: %(default)s).' + ) g.add_argument( - "-x-delaybeforesend", - metavar="", - default=None, - type=lambda n: None if n == 0 else float(n), - help="delay in seconds before sending a line to an runner/interpreter; 0 disable this (default).") + "-x-delaybeforesend", + metavar="", + default=None, + type=lambda n: None if n == 0 else float(n), + help= + "delay in seconds before sending a line to an runner/interpreter; 0 disable this (default)." + ) g.add_argument( - "-x-min-rcount", - metavar="", - default=16, - type=int, - help="minimum match length around a capture tag to perform a guess (default: %(default)s).") + "-x-min-rcount", + metavar="", + default=16, + type=int, + help= + "minimum match length around a capture tag to perform a guess (default: %(default)s)." + ) g.add_argument( - "-x-not-recover-timeout", - action='store_true', - help="do not try to recover from a timeout; abort the execution immediately (beware, this could leave some resources without the proper clean up).") + "-x-not-recover-timeout", + action='store_true', + help= + "do not try to recover from a timeout; abort the execution immediately (beware, this could leave some resources without the proper clean up)." + ) g.add_argument( "-x-log-mask", action='append', @@ -350,7 +399,7 @@ def parse_args(args=None): # ----------------- # the languages must be uniq - copy = set(namespace.languages) # copy of uniqs + copy = set(namespace.languages) # copy of uniqs for l in namespace.languages: if l not in copy: parser.error("argument --languages: '%s' is duplicated." % l) @@ -375,11 +424,14 @@ def parse_args(args=None): copy.remove(k) try: - namespace.log_masks = {k: str_to_level(v) for k, v in namespace.log_masks} + namespace.log_masks = { + k: str_to_level(v) + for k, v in namespace.log_masks + } except KeyError as err: k, = err.args - parser.error("argument --x-log-mask: '%s' is an unknown log level." % k) + parser.error( + "argument --x-log-mask: '%s' is an unknown log level." % k + ) return namespace - - diff --git a/byexample/common.py b/byexample/common.py index 639528cb..f697ccc5 100644 --- a/byexample/common.py +++ b/byexample/common.py @@ -1,15 +1,16 @@ from __future__ import unicode_literals import pprint, traceback, contextlib, os, re, string, shlex, logging - ''' >>> from byexample.common import tohuman ''' + def indent(s, indent=4): ''' Indent the given text. See doctest._indent for the code that inspired this. ''' - return re.sub('(?m)^(?!$)', indent*' ', s) + return re.sub('(?m)^(?!$)', indent * ' ', s) + def build_where_msg(where, owner, msg=None, use_colors=False): tmp = [] @@ -33,6 +34,7 @@ def build_where_msg(where, owner, msg=None, use_colors=False): return ''.join(tmp) + def colored(s, color, use_colors): if use_colors: if color == 'none': @@ -42,6 +44,7 @@ def colored(s, color, use_colors): else: return s + try: import pygments import pygments.lexers @@ -81,6 +84,7 @@ def highlight_syntax(example, use_colors): def highlight_syntax(example, use_colors): return example.snippet + def tohuman(s): ''' Simple but quite human representation of . @@ -111,6 +115,7 @@ def tohuman(s): def constant(argumentless_method): placeholder = '_saved_constant_result_of_%s' % argumentless_method.__name__ + def wrapped(self): try: return getattr(self, placeholder) @@ -160,6 +165,7 @@ def human_exceptions(where_default): rlog.exception(msg=None, where=where_default) o['exc'] = e + @contextlib.contextmanager def enhance_exceptions(where, owner, use_colors=False): try: @@ -169,6 +175,7 @@ def enhance_exceptions(where, owner, use_colors=False): e.where = build_where_msg(where, owner, use_colors=use_colors) raise e + def abspath(*args): ''' Return the absolute path from the join of . The first item of can be a filename which it will @@ -178,11 +185,13 @@ def abspath(*args): path = os.path.join(base, *args[1:]) return os.path.abspath(path) + try: from shlex import quote as shlex_quote except ImportError: from pipes import quote as shlex_quote + class ShebangTemplate(string.Template): delimiter = '%' @@ -245,4 +254,3 @@ def quote_and_substitute(self, tokens): cmd.append(x) return ' '.join(cmd) - diff --git a/byexample/concern.py b/byexample/concern.py index ebfeb866..1175841c 100644 --- a/byexample/concern.py +++ b/byexample/concern.py @@ -2,6 +2,7 @@ from .common import tohuman from .options import ExtendOptionParserMixin + class Concern(ExtendOptionParserMixin): ''' Cross-cutting Concern interface. @@ -45,7 +46,6 @@ class Concern(ExtendOptionParserMixin): See each method's documentation to get an idea of the capabilities of this interface. ''' - def __init__(self, **kargs): ''' Called once when the concern is loaded. @@ -81,7 +81,7 @@ def start(self, examples, runners, filepath, options): If you want to customize the example *after* the parsing stage use start_example. ''' - pass # pragma: no cover + pass # pragma: no cover def start_parse(self, example, options): ''' @@ -133,14 +133,14 @@ def finish(self, failed, user_aborted, crashed, broken, timedout): You could perform some clean up task injecting some code in the runners. ''' - pass # pragma: no cover + pass # pragma: no cover def skip_example(self, example, options): ''' It is the turn for the example to be executed but it will not. No other concern's method will be called with this example. ''' - pass # pragma: no cover + pass # pragma: no cover def start_example(self, example, options): ''' @@ -164,7 +164,7 @@ def start_example(self, example, options): And may be, start_interact and finish_interact. ''' - pass # pragma: no cover + pass # pragma: no cover def finish_example(self, example, options): ''' @@ -178,13 +178,13 @@ def finish_example(self, example, options): or more concern objects edit the same example, you may not always will get the same effect. ''' - pass # pragma: no cover + pass # pragma: no cover def finally_example(self, example, options): ''' Called when the given example finished its execution regardless of how. ''' - pass # pragma: no cover + pass # pragma: no cover def start_interact(self, example, options): ''' @@ -194,14 +194,14 @@ def start_interact(self, example, options): You may use this opportunity to perform some maintenance tasks in the runner/interpreter to make the user life easier. ''' - pass # pragma: no cover + pass # pragma: no cover def finish_interact(self, exception): ''' Called after the interactive session. If something went wrong, the exception will be passed, None if everything went fine. ''' - pass # pragma: no cover + pass # pragma: no cover def timedout(self, example, exception): ''' @@ -209,7 +209,7 @@ def timedout(self, example, exception): just ran too slow or because it was syntactically incorrect and hang the interpreter. ''' - pass # pragma: no cover + pass # pragma: no cover def aborted(self, example, by_the_user, options): ''' @@ -223,7 +223,7 @@ def aborted(self, example, by_the_user, options): Keep in mind that the abort could happen before the start or after finishing the execution (Concern.start and Concern.finish) ''' - pass # pragma: no cover + pass # pragma: no cover def crashed(self, example, exception): ''' @@ -235,7 +235,7 @@ def crashed(self, example, exception): You could use this to try to debug why the runner crashed. Most probably is due a bug in the runner or in byexample (sorry). ''' - pass # pragma: no cover + pass # pragma: no cover def success(self, example, got, differ): ''' @@ -244,30 +244,34 @@ def success(self, example, got, differ): Hurra! ''' - pass # pragma: no cover + pass # pragma: no cover def failure(self, example, got, differ): ''' Called when an example execution finish but its output wasn't expected. ''' - pass # pragma: no cover + pass # pragma: no cover def event(self, what, **data): ''' Called on arbitrary moments, for arbitrary reasons defined in and optionally in . ''' - pass # pragma: no cover + pass # pragma: no cover + class ConcernComposite(Concern): def __init__(self, registry, **unused): self.concerns = registry['concerns'].values() + # Patch ConcernComposite overriding all its methods # For a given method X, ConcernComposite will call X on all of # its sub-concerns. import inspect + + def _patch(cls, method_name): def for_each_concern_do(self, *args, **kargs): for concern in self.concerns: @@ -275,12 +279,13 @@ def for_each_concern_do(self, *args, **kargs): setattr(cls, method_name, for_each_concern_do) + def _patchable(obj): # In Python 3, the class's methods are actually functions # so we need to check for both types return (inspect.isfunction(obj) or inspect.ismethod(obj)) \ and not obj.__name__.startswith("_") + for method_name, _ in inspect.getmembers(Concern, predicate=_patchable): _patch(ConcernComposite, method_name) - diff --git a/byexample/differ.py b/byexample/differ.py index 63dcad77..7badb30e 100644 --- a/byexample/differ.py +++ b/byexample/differ.py @@ -10,8 +10,12 @@ # 0 <= i <= 31 or 127 <= i <= 159 # # whitespace control codes?: 9 <= i <= 13 -ctrl_tr = {i:u'?' if (0 <= i <= 31 or 127 <= i <= 159) and not (9 <= i <= 13) - else chr(i) for i in range(160)} +ctrl_tr = { + i: u'?' if + (0 <= i <= 31 or 127 <= i <= 159) and not (9 <= i <= 13) else chr(i) + for i in range(160) +} + class Differ(object): def __init__(self, verbosity, encoding, **unused): @@ -142,11 +146,11 @@ def output_difference(self, example, got, flags, use_colors): if flags['enhance_diff']: expected, replaced_captures = example.expected.get_captures( - example, got, - flags, self.verbosity) + example, got, flags, self.verbosity + ) else: - expected = example # aka literal string, mostly for internal testing + expected = example # aka literal string, mostly for internal testing self._diff = [] @@ -155,7 +159,7 @@ def output_difference(self, example, got, flags, use_colors): # it should be safe too because expected_regexs should have # a '\n*' regex at the end to match any possible empty line expected = self._remove_last_empty_lines(expected) - got = self._remove_last_empty_lines(got) + got = self._remove_last_empty_lines(got) if flags['enhance_diff']: namedcap, r1, p1 = self._human_named_captures(replaced_captures) @@ -170,7 +174,9 @@ def output_difference(self, example, got, flags, use_colors): if diff_type not in ('none', 'tool'): self.print_diff(expected, got, diff_type, use_colors) elif diff_type == 'tool': - self.use_external_tool(expected, got, flags['difftool'], use_colors) + self.use_external_tool( + expected, got, flags['difftool'], use_colors + ) else: self.just_print(expected, got, use_colors) @@ -185,14 +191,14 @@ def _print_human_replacement_table(self, wrepl, prepl): return HUMAN_EXPL = { - '$' : 'trailing spaces', - '?' : 'ctrl character ', - '^n': 'new line ', - '^t': 'tab ', - '^v': 'vertical tab ', - '^r': 'carriage return', - '^f': 'form feed ', - } + '$': 'trailing spaces', + '?': 'ctrl character ', + '^n': 'new line ', + '^t': 'tab ', + '^v': 'vertical tab ', + '^r': 'carriage return', + '^f': 'form feed ', + } assert wrepl.issubset(set(HUMAN_EXPL.keys())) assert prepl.issubset(set(HUMAN_EXPL.keys())) @@ -202,15 +208,17 @@ def _print_human_replacement_table(self, wrepl, prepl): prepl = wrepl & prepl wrepl = list(sorted(wrepl)) - self._write("Some non-printable characters were replaced by printable ones.") - for tmp in [wrepl[i:i+3] for i in range(0, len(wrepl), 3)]: + self._write( + "Some non-printable characters were replaced by printable ones." + ) + for tmp in [wrepl[i:i + 3] for i in range(0, len(wrepl), 3)]: line = ['%02s: %s' % (r, HUMAN_EXPL[r]) for r in tmp] line = ' '.join(line) self._write(" %s" % line) if prepl: prepl = list(sorted(prepl)) - one = prepl[0] + one = prepl[0] what = HUMAN_EXPL[one] self._write(("Warning, the characters %s were present *before* the replacement.\n" + \ "That means that if you see a '%s' it could mean '%s' or\n" + \ @@ -228,19 +236,19 @@ def _human_named_captures(self, replaced_captures): out = [] max_len = 36 + def _format(k, v): if k is None or v is None: return "" - _mlen = max_len - len(k) + 2 # plus the : and the space + _mlen = max_len - len(k) + 2 # plus the : and the space v, w, p = self._human(v, replace_newlines=True) wrepl.update(w) prepl.update(p) if len(v) > _mlen: - _mlen -= 5 # minus the ' ... ' - v = v[:int(_mlen/2)] + " ... " + v[-int(_mlen/2):] - + _mlen -= 5 # minus the ' ... ' + v = v[:int(_mlen / 2)] + " ... " + v[-int(_mlen / 2):] return "%s: %s" % (k, v) @@ -252,7 +260,7 @@ def _format(k, v): out.append("Tags replaced by the captured output:") if len(k_vs) % 2 != 0: - k_vs.append((None, None)) # make the list even + k_vs.append((None, None)) # make the list even for k_v1, k_v2 in zip(k_vs[::2], k_vs[1::2]): left, right = _format(*k_v1), _format(*k_v2) @@ -263,20 +271,18 @@ def _format(k, v): out.append("(You can disable this with '--no-enhance-diff')") return '\n'.join(out), wrepl, prepl - - def _write(self, s, end_with_newline=True): self._diff.append(s) if end_with_newline and not s.endswith('\n'): self._diff.append('\n') - WS = re.compile(r"[ ]+$", flags=re.MULTILINE) - NLs = re.compile(r"\n+", flags=re.MULTILINE) + WS = re.compile(r"[ ]+$", flags=re.MULTILINE) + NLs = re.compile(r"\n+", flags=re.MULTILINE) last_NLs = re.compile(r"\n+\Z", flags=re.MULTILINE) def _human(self, s, replace_newlines=False): - ws = '\t\x0b\x0c\r' - tr_ws = ['^t', '^v', '^f', '^r', '^s'] + ws = '\t\x0b\x0c\r' + tr_ws = ['^t', '^v', '^f', '^r', '^s'] present_before_repl = set() wrepl = set() @@ -355,15 +361,14 @@ def use_external_tool(self, expected, got, cmdline, use_colors): gfile.write(got) tokens = { - 'e': efilename, - 'g': gfilename, - } + 'e': efilename, + 'g': gfilename, + } cmdline = ShebangTemplate(cmdline).quote_and_substitute(tokens) try: out = subprocess.check_output( - cmdline, - shell=True, - stderr=subprocess.STDOUT) + cmdline, shell=True, stderr=subprocess.STDOUT + ) returncode = 0 except subprocess.CalledProcessError as err: out = err.output @@ -375,8 +380,10 @@ def use_external_tool(self, expected, got, cmdline, use_colors): self._write(out) if returncode != 0: - self._write(colored('Return code: ', 'red', use_colors), - end_with_newline=False) + self._write( + colored('Return code: ', 'red', use_colors), + end_with_newline=False + ) self._write(str(returncode)) finally: @@ -385,35 +392,41 @@ def use_external_tool(self, expected, got, cmdline, use_colors): if gfilename: os.remove(gfilename) - def print_diff(self, expected, got, diff_type, use_colors): expected_lines = expected.split('\n') got_lines = got.split('\n') if diff_type == 'unified': - diff_lines = difflib.unified_diff(expected_lines, got_lines, - n=2, lineterm="") + diff_lines = difflib.unified_diff( + expected_lines, got_lines, n=2, lineterm="" + ) diff_lines = list(diff_lines) - diff_lines = diff_lines[2:] # drop diff's header - diff_lines = self.colored_diff_lines(diff_lines, use_colors, - green='+', red='-', - yellow=['@']) + diff_lines = diff_lines[2:] # drop diff's header + diff_lines = self.colored_diff_lines( + diff_lines, use_colors, green='+', red='-', yellow=['@'] + ) elif diff_type == 'context': - diff_lines = difflib.context_diff(expected_lines, got_lines, - n=2, lineterm="") + diff_lines = difflib.context_diff( + expected_lines, got_lines, n=2, lineterm="" + ) diff_lines = list(diff_lines) - diff_lines = diff_lines[3:] # drop diff's header - diff_lines = self.colored_diff_lines(diff_lines, use_colors, - green='+ ', red='! ', - yellow=['*', '-']) + diff_lines = diff_lines[3:] # drop diff's header + diff_lines = self.colored_diff_lines( + diff_lines, + use_colors, + green='+ ', + red='! ', + yellow=['*', '-'] + ) elif diff_type == 'ndiff': - diff_lines = difflib.ndiff(expected_lines, got_lines, - charjunk=difflib.IS_CHARACTER_JUNK) - diff_lines = self.colored_diff_lines(diff_lines, use_colors, - green='+ ', red='- ', - yellow=[]) + diff_lines = difflib.ndiff( + expected_lines, got_lines, charjunk=difflib.IS_CHARACTER_JUNK + ) + diff_lines = self.colored_diff_lines( + diff_lines, use_colors, green='+ ', red='- ', yellow=[] + ) else: raise ValueError("Unknow diff report type '%s'" % diff_type) @@ -434,4 +447,3 @@ def colored_line(line): return line return [colored_line(line) for line in lines] - diff --git a/byexample/executor.py b/byexample/executor.py index 169e3aec..21a6fe0b 100644 --- a/byexample/executor.py +++ b/byexample/executor.py @@ -2,17 +2,21 @@ from .common import enhance_exceptions from .log import clog, log_context, log_with + class TimeoutException(Exception): def __init__(self, msg, output): - Exception.__init__(self, msg); + Exception.__init__(self, msg) self.output = output + class FileExecutor(object): - def __init__(self, concerns, differ, verbosity, use_colors, options, **unused): - self.concerns = concerns - self.differ = differ + def __init__( + self, concerns, differ, verbosity, use_colors, options, **unused + ): + self.concerns = concerns + self.differ = differ self.use_colors = use_colors - self.verbosity = verbosity + self.verbosity = verbosity self.options = options @@ -68,9 +72,12 @@ def execute(self, examples, filepath): self.initialize_runners(runners, options) try: self.concerns.start(examples, runners, filepath, options) - failed, user_aborted, crashed, broken, timedout = self._exec(examples, filepath, - options, runners) - self.concerns.finish(failed, user_aborted, crashed, broken, timedout) + failed, user_aborted, crashed, broken, timedout = self._exec( + examples, filepath, options, runners + ) + self.concerns.finish( + failed, user_aborted, crashed, broken, timedout + ) finally: self.shutdown_runners(runners) @@ -90,7 +97,7 @@ def _exec(self, examples, filepath, options, runners): if example == None: broken = True - break # cancel if an example couldn't get parsed + break # cancel if an example couldn't get parsed with enhance_exceptions(example, self, self.use_colors), \ log_with(example.runner.language): @@ -113,16 +120,22 @@ def _exec(self, examples, filepath, options, runners): self.concerns.skip_example(example, options) continue - clog().chat('ex:', example=example, disable_prefix=True) + clog().chat( + 'ex:', example=example, disable_prefix=True + ) self.concerns.start_example(example, options) try: - with enhance_exceptions(example, example.runner, self.use_colors): - example.got = example.runner.run(example, options) + with enhance_exceptions( + example, example.runner, self.use_colors + ): + example.got = example.runner.run( + example, options + ) self.concerns.finish_example(example, options) except TimeoutException as e: # pragma: no cover self.concerns.timedout(example, e) timedout = True - except Exception as e: # pragma: no cover + except Exception as e: # pragma: no cover self.concerns.crashed(example, e) crashed = True finally: @@ -131,7 +144,10 @@ def _exec(self, examples, filepath, options, runners): recovered = False if timedout and not options['x']['not_recover_timeout']: # try to recover the control of the runner - clog().warn('Example timed out. Trying to recovering the control (%s)...', example.runner.language) + clog().warn( + 'Example timed out. Trying to recovering the control (%s)...', + example.runner.language + ) recovered = example.runner.cancel(example, options) clog().warn('Recovering control of %s %s', example.runner.language, @@ -141,7 +157,7 @@ def _exec(self, examples, filepath, options, runners): if crashed or (timedout and not recovered): failed = True self.concerns.aborted(example, False, options) - break # cancel, the runner is in an undefined state + break # cancel, the runner is in an undefined state if timedout and recovered: # we did not get the output from the example, @@ -158,18 +174,26 @@ def _exec(self, examples, filepath, options, runners): force_pass = options['pass'] if force_pass or \ example.expected.check_got_output(example, got, options, self.verbosity): - self.concerns.success(example, got, self.differ) + self.concerns.success( + example, got, self.differ + ) else: - self.concerns.failure(example, got, self.differ) + self.concerns.failure( + example, got, self.differ + ) failed = True # start an interactive session if the example fails # and the user wanted this if options['interact']: - self.concerns.start_interact(example, options) + self.concerns.start_interact( + example, options + ) ex = None try: - example.runner.interact(example, options) + example.runner.interact( + example, options + ) except Exception as e: ex = e @@ -179,7 +203,9 @@ def _exec(self, examples, filepath, options, runners): # example failed if fail_fast and failed: failing_fast = True - options.up({'skip': True}) # dummy, but it allows a symmetric relationship between failing_fast and an extra up + options.up( + {'skip': True} + ) # dummy, but it allows a symmetric relationship between failing_fast and an extra up finally: if failing_fast: options.down() @@ -189,7 +215,7 @@ def _exec(self, examples, filepath, options, runners): if hasattr(example, 'got'): del example.got options.down() - except KeyboardInterrupt: # pragma: no cover + except KeyboardInterrupt: # pragma: no cover self.concerns.aborted(example, True, options) failed = user_aborted = True break diff --git a/byexample/expected.py b/byexample/expected.py index 19141364..cc9abfd3 100644 --- a/byexample/expected.py +++ b/byexample/expected.py @@ -1,15 +1,16 @@ from __future__ import unicode_literals from .log import clog, log_context import string, re, time - ''' >>> from byexample.log import init_log_system >>> init_log_system() ''' + def regex_name_as_tag_name(name): return name.replace('_', '-') + class Expected(object): def __init__(self, expected_str, regexs, charnos, rcounts, tags_by_idx): self.str = expected_str @@ -18,6 +19,7 @@ def __init__(self, expected_str, regexs, charnos, rcounts, tags_by_idx): self.rcounts = rcounts self.tags_by_idx = tags_by_idx + class _LinearExpected(Expected): ''' Assume that all the example's tags are of the form .* Then we can just apply a quicker and more efficient algorithm @@ -165,7 +167,6 @@ def __init__(self, *args, **kargs): self._check_got_output_called = False - def check_got_output(self, example, got, options, verbosity): self.check_good = False self.verbosity = verbosity @@ -175,7 +176,9 @@ def check_got_output(self, example, got, options, verbosity): expected_str = example.expected.str charnos = example.expected.charnos - self.check_good = self._linear_matching(regexs, tags_by_idx, charnos, expected_str, got) + self.check_good = self._linear_matching( + regexs, tags_by_idx, charnos, expected_str, got + ) self._check_got_output_called = True return self.check_good @@ -191,15 +194,21 @@ def get_captures(self, example, got, options, verbosity): # it is more complex and less safer than _LinearExpected but # yield results of much better quality if self.check_good: - captured = self._regex_expected._get_all_capture_or_none(example, got, options) + captured = self._regex_expected._get_all_capture_or_none( + example, got, options + ) assert captured != None return got, captured else: - return self._regex_expected._get_all_capture_as_possible(example, got, options) + return self._regex_expected._get_all_capture_as_possible( + example, got, options + ) - def _linear_matching(self, regexs, tags_by_idx, charnos, expected_str, got): + def _linear_matching( + self, regexs, tags_by_idx, charnos, expected_str, got + ): ''' Assume that all (if any) example's capture tags are regex of the form '.*'. If that's true, then the example will pass if all the literal @@ -240,6 +249,7 @@ def _linear_matching(self, regexs, tags_by_idx, charnos, expected_str, got): return True + class _RegexExpected(Expected): def __init__(self, *args, **kargs): Expected.__init__(self, *args, **kargs) @@ -247,28 +257,36 @@ def __init__(self, *args, **kargs): self._check_got_output_called = False def _get_all_capture_or_none(self, example, got, options): - r = re.compile(''.join(example.expected.regexs), re.MULTILINE | re.DOTALL) + r = re.compile( + ''.join(example.expected.regexs), re.MULTILINE | re.DOTALL + ) m = r.match(got) if m: replaced_captures = m.groupdict('') - return {regex_name_as_tag_name(n): v - for n, v in replaced_captures.items()} + return { + regex_name_as_tag_name(n): v + for n, v in replaced_captures.items() + } def _get_all_capture_as_possible(self, example, got, options): expected = example.expected if not expected.tags_by_idx: return expected.str, {} - captures = list(sorted(n for n in expected.tags_by_idx.values() if n != None)) - return self._get_captures_by_incremental_match(captures, - expected.regexs, - expected.charnos, - expected.rcounts, - expected.str, - got, - min_rcount = options['x']['min_rcount'], - timeout = options['x']['dfl_timeout']) + captures = list( + sorted(n for n in expected.tags_by_idx.values() if n != None) + ) + return self._get_captures_by_incremental_match( + captures, + expected.regexs, + expected.charnos, + expected.rcounts, + expected.str, + got, + min_rcount=options['x']['min_rcount'], + timeout=options['x']['dfl_timeout'] + ) def check_got_output(self, example, got, options, verbosity): self.check_good = False @@ -299,8 +317,10 @@ def get_captures(self, example, got, options, verbosity): return self._get_all_capture_as_possible(example, got, options) @log_context('byexample.match') - def _get_captures_by_incremental_match(self, captures, expected_regexs, - charnos, rcounts, expected, got, min_rcount, timeout): + def _get_captures_by_incremental_match( + self, captures, expected_regexs, charnos, rcounts, expected, got, + min_rcount, timeout + ): r''' Try to replace all the capture groups in the expected by the strings found in got. @@ -444,18 +464,21 @@ def _get_captures_by_incremental_match(self, captures, expected_regexs, ''' regs = expected_regexs + def _compile(regexs): return re.compile(''.join(regexs), re.MULTILINE | re.DOTALL) assert len(regs) == len(charnos) == len(rcounts) best_left_index = 0 - best_right_index = len(regs)-1 + best_right_index = len(regs) - 1 timeout_left = timeout / 2.0 timeout_right = timeout - timeout_left - clog().debug("Partial Matching:\nGot string to target:\n%s\n", repr(got)) + clog().debug( + "Partial Matching:\nGot string to target:\n%s\n", repr(got) + ) # from left to right, find the left most regex that match # a prefix of got by doing an incremental compile/matching accum = 0 @@ -473,17 +496,15 @@ def _compile(regexs): accum += rcounts[i] clog().debug( - "|--> | best at index % 3i (accum rcount % 3i/%i):\nTrying partial left regex: %s", - i, accum, min_rcount, - repr(''.join(regs[:i+1]))) - + "|--> | best at index % 3i (accum rcount % 3i/%i):\nTrying partial left regex: %s", + i, accum, min_rcount, repr(''.join(regs[:i + 1])) + ) begin = time.time() - m = _compile(regs[:i+1]).match(got) + m = _compile(regs[:i + 1]).match(got) timeout_left -= (time.time() - begin) if m: - clog().debug("Match\n% 4i: %s\n", - charnos[i], m.group(0)) + clog().debug("Match\n% 4i: %s\n", charnos[i], m.group(0)) if accum >= min_rcount: best_left_index = i @@ -493,11 +514,11 @@ def _compile(regexs): # in deed will reduce the available timeout on the right timeout_right += timeout_left - left_side = regs[:best_left_index+1] + left_side = regs[:best_left_index + 1] r = _compile(left_side) got_left = r.match(got).group(0) - left_ends_at = charnos[best_left_index+1] + left_ends_at = charnos[best_left_index + 1] # a 'capture anything' regex between the left and the right side # to hold all the rest of the string @@ -508,14 +529,17 @@ def _compile(regexs): buffer_tag_name = buffer_tag_name % i if buffer_tag_name in captures: - raise Exception("Invalid state. Weird.... After several tries, the buffer tag is still not uniq. Last try was '%s'" % buffer_tag_name) + raise Exception( + "Invalid state. Weird.... After several tries, the buffer tag is still not uniq. Last try was '%s'" + % buffer_tag_name + ) buffer_re = '(?P<%s>.*?)' % buffer_tag_name # now go from the right to the left, add a regex each time # to see where the whole regex doesn't match accum = 0 - for i in range(len(regs)-1, best_left_index, -1): + for i in range(len(regs) - 1, best_left_index, -1): try: if not rcounts[i]: accum = 0 @@ -528,17 +552,19 @@ def _compile(regexs): accum += rcounts[i] clog().debug( - "| <--| best at index % 3i (accum rcount % 3i/%i):\nTrying partial regex: %s", - i, accum, min_rcount, - repr(''.join(left_side + [buffer_re] + regs[i:]))) + "| <--| best at index % 3i (accum rcount % 3i/%i):\nTrying partial regex: %s", + i, accum, min_rcount, + repr(''.join(left_side + [buffer_re] + regs[i:])) + ) begin = time.time() m = _compile(left_side + [buffer_re] + regs[i:]).match(got) timeout_right -= (time.time() - begin) if m: clog().debug( - "Matched; Buffer between left and right:\n%s\n", - m.group(buffer_tag_name)) + "Matched; Buffer between left and right:\n%s\n", + m.group(buffer_tag_name) + ) if accum >= min_rcount: best_right_index = i @@ -575,12 +601,14 @@ def _compile(regexs): elapsed = timeout - timeout_right - replaced_captures = {regex_name_as_tag_name(n): v - for n, v in replaced_captures.items()} + replaced_captures = { + regex_name_as_tag_name(n): v + for n, v in replaced_captures.items() + } clog().debug( - "Incremental Match:\n##Elapsed: %0.2f secs\n##Left: %s\n\n##Middle: %s\n\n##Right: %s\n\n##Captured: %s\n\n##Buffer: %s", - elapsed, got_left, middle_part, got_right, repr(replaced_captures), buffer_captured) + "Incremental Match:\n##Elapsed: %0.2f secs\n##Left: %s\n\n##Middle: %s\n\n##Right: %s\n\n##Captured: %s\n\n##Buffer: %s", + elapsed, got_left, middle_part, got_right, repr(replaced_captures), + buffer_captured + ) return got_left + middle_part + got_right, replaced_captures - - diff --git a/byexample/finder.py b/byexample/finder.py index 18f522cd..ef6093fa 100644 --- a/byexample/finder.py +++ b/byexample/finder.py @@ -7,12 +7,12 @@ from .options import Options from .log import clog, log_context, DEBUG, CHAT, getLogger, log_with import pprint - ''' >>> from byexample.log import init_log_system >>> init_log_system() ''' + class Where(object): def __init__(self, start_lineno, end_lineno, filepath, zdelimiter): self.start_lineno = start_lineno @@ -21,7 +21,9 @@ def __init__(self, start_lineno, end_lineno, filepath, zdelimiter): self.zdelimiter = zdelimiter def as_tuple(self): - return (self.start_lineno, self.end_lineno, self.filepath, self.zdelimiter) + return ( + self.start_lineno, self.end_lineno, self.filepath, self.zdelimiter + ) def __iter__(self): return iter(self.as_tuple()) @@ -29,12 +31,14 @@ def __iter__(self): def __repr__(self): return repr(self.as_tuple()) + class Zone(object): def __init__(self, zdelimiter, zone_str, where): self.zdelimiter = zdelimiter self.str = zone_str self.where = where + class Example(object): ''' The unit work of byexample: the example. @@ -84,7 +88,9 @@ class Example(object): {'norm_ws': False, 'rm': [], 'tags': True} ''' - def __init__(self, finder, runner, parser, snippet, expected_str, indent, where): + def __init__( + self, finder, runner, parser, snippet, expected_str, indent, where + ): self.finder, self.runner, self.parser = finder, runner, parser self.snippet, self.expected_str, self.indentation = snippet, expected_str, indent @@ -97,7 +103,9 @@ def parse_yourself(self, concerns=None): raise ValueError("You cannot parse/build an example twice: " + \ repr(self)) - where = Where(self.start_lineno, self.end_lineno, self.filepath, self.zdelimiter) + where = Where( + self.start_lineno, self.end_lineno, self.filepath, self.zdelimiter + ) self.parser.parse(self, concerns) self.fully_parsed = True @@ -106,27 +114,23 @@ def parse_yourself(self, concerns=None): def __repr__(self): f = "" if self.fully_parsed else "(not parsed yet) " return "Example %s[%s] in file %s, lines %i-%i" % ( - f, - self.runner.language, - self.filepath, self.start_lineno, self.end_lineno) + f, self.runner.language, self.filepath, self.start_lineno, + self.end_lineno + ) def pretty_print(self): log = clog() # header - log.chat('%s (lines %s-%s:%s) [%s|%s]%s', - self.filepath, - self.start_lineno, - self.end_lineno, - self.runner.language, - self.finder, - self.runner, - "" if self.fully_parsed else " (not parsed yet)" - ) + log.chat( + '%s (lines %s-%s:%s) [%s|%s]%s', self.filepath, self.start_lineno, + self.end_lineno, self.runner.language, self.finder, self.runner, + "" if self.fully_parsed else " (not parsed yet)" + ) # source use_colors = getLogger('byexample').use_colors - log.chat(highlight_syntax(self, use_colors), extra={'no_marker':True}) + log.chat(highlight_syntax(self, use_colors), extra={'no_marker': True}) if log.isEnabledFor(DEBUG): tmp = [' len: expected line'] @@ -136,40 +140,60 @@ def pretty_print(self): _l += len(e) + 1 tmp = '\n'.join(tmp) - log.debug(tmp, extra={'no_marker':True}) + log.debug(tmp, extra={'no_marker': True}) else: - log.chat(self.expected_str, extra={'no_marker':True}) + log.chat(self.expected_str, extra={'no_marker': True}) if not self.fully_parsed: return - log.debug('Indentation: |%s| (%i bytes)', - self.indentation, len(self.indentation)) + log.debug( + 'Indentation: |%s| (%i bytes)', self.indentation, + len(self.indentation) + ) - capture_tag_names = list(sorted(n for n in self.expected.tags_by_idx.values() if n != None)) - log.chat('Capture tags: %s', pprint.pformat(capture_tag_names, width=50)) + capture_tag_names = list( + sorted(n for n in self.expected.tags_by_idx.values() if n != None) + ) + log.chat( + 'Capture tags: %s', pprint.pformat(capture_tag_names, width=50) + ) opts = pprint.pformat(self.options.as_dict(), width=50) log.chat('Options: %s', opts) if len(self.expected.regexs) != len(self.expected.charnos): - log.warn('Inconsistent regexs: %i regexs versus %i char-numbers', - len(self.expected.regexs), len(self.expected.charnos)) + log.warn( + 'Inconsistent regexs: %i regexs versus %i char-numbers', + len(self.expected.regexs), len(self.expected.charnos) + ) tmp = [] tmp.append("Regexs:") for p, r in zip(self.expected.charnos, self.expected.regexs): tmp.append("% 4i: %s" % (p, repr(r))) - log.debug('\n'.join(tmp), extra={'no_marker':True}) + log.debug('\n'.join(tmp), extra={'no_marker': True}) -def _build_fake_example(snippet, expected, language='python', start_lineno=0, - specific=False, fully_parsed=True, opts=None): - class R: pass # <- fake runner instance - R.language = language # <- language of the example - class F: pass # <- fake finder instance - F.specific = specific # <- is finder specific? +def _build_fake_example( + snippet, + expected, + language='python', + start_lineno=0, + specific=False, + fully_parsed=True, + opts=None +): + class R: + pass # <- fake runner instance + + R.language = language # <- language of the example + + class F: + pass # <- fake finder instance + + F.specific = specific # <- is finder specific? # fake a parser parser = ExampleParser(0, 'utf8', Options()) @@ -194,6 +218,7 @@ class F: pass # <- fake finder instance return e + class ExampleHarvest(object): ''' Finding process Parsing process @@ -220,8 +245,10 @@ class ExampleHarvest(object): \ reporter \ . ''' - def __init__(self, allowed_languages, registry, verbosity, - options, use_colors, encoding, **unused): + def __init__( + self, allowed_languages, registry, verbosity, options, use_colors, + encoding, **unused + ): self.allowed_languages = allowed_languages self.verbosity = verbosity self.use_colors = use_colors @@ -250,17 +277,15 @@ def _get_zones(self, string, filepath=''): _, ext = os.path.splitext(filepath) zdelimiter = self.zdelimiter_by_file_extension.get( - ext, - self.zdelimiter_by_file_extension['no-delimiter'] - ) + ext, self.zdelimiter_by_file_extension['no-delimiter'] + ) zones = self.get_zones_using( - zdelimiter, - string, - filepath, - start_lineno=1) + zdelimiter, string, filepath, start_lineno=1 + ) - clog().chat("File '%s': %i zones [%s]", - filepath, len(zones), str(zdelimiter)) + clog().chat( + "File '%s': %i zones [%s]", filepath, len(zones), str(zdelimiter) + ) if clog().isEnabledFor(DEBUG): for zone in zones: @@ -277,26 +302,31 @@ def get_examples_from_string(self, string, filepath=''): nexamples = 0 for zone in zones: examples = self.get_examples_using( - finder, - zone.str, - zone.where.filepath, - zone.where.start_lineno) + finder, zone.str, zone.where.filepath, + zone.where.start_lineno + ) all_examples.extend(examples) nexamples += len(examples) - clog().chat("File '%s': %i examples [%s]", - filepath, nexamples, str(finder)) + clog().chat( + "File '%s': %i examples [%s]", filepath, nexamples, + str(finder) + ) # sort the examples in the same order # that they were found in the file/string; # see check_example_overlap - all_examples.sort(key=lambda this: (this.start_lineno, -this.end_lineno)) + all_examples.sort( + key=lambda this: (this.start_lineno, -this.end_lineno) + ) all_examples = self.check_example_overlap(all_examples, filepath) tmp = set(e.runner.language for e in all_examples) - clog().chat("Findings in file '%s': %i examples written in %i different languages in %i zones were found.", - filepath, len(all_examples), len(tmp), len(zones)) + clog().chat( + "Findings in file '%s': %i examples written in %i different languages in %i zones were found.", + filepath, len(all_examples), len(tmp), len(zones) + ) return all_examples def check_example_overlap(self, examples, filepath): @@ -367,7 +397,7 @@ def check_example_overlap(self, examples, filepath): while not collision_free: collision_free = True if not examples: - return examples # pragma: no cover + return examples # pragma: no cover prev = examples[0] for i, example in enumerate(examples[1:], 1): @@ -384,8 +414,14 @@ def check_example_overlap(self, examples, filepath): continue collision_free = not any_collision - curr_where = Where(example.start_lineno, example.end_lineno, filepath, example.zdelimiter) - prev_where = Where(prev.start_lineno, prev.end_lineno, filepath, prev.zdelimiter) + curr_where = Where( + example.start_lineno, example.end_lineno, filepath, + example.zdelimiter + ) + prev_where = Where( + prev.start_lineno, prev.end_lineno, filepath, + prev.zdelimiter + ) self._log_debug(" * Collision Type (1/2/3): %s/%s/%s\n" \ " * Languages (prev/current): %s/%s\n" \ @@ -402,18 +438,21 @@ def check_example_overlap(self, examples, filepath): msg = "In %s, examples at lines %i-%i (found by %s) and " +\ "at lines %i-%i (found by %s) overlap each other." - msg = msg % (filepath, example.start_lineno, example.end_lineno, - example.finder, prev.start_lineno, prev.end_lineno, - prev.finder) + msg = msg % ( + filepath, example.start_lineno, example.end_lineno, + example.finder, prev.start_lineno, prev.end_lineno, + prev.finder + ) raise ValueError(msg) if clog().isEnabledFor(CHAT): clog().debug("Examples after removing any overlapping") for finder in set(e.finder for e in examples): - clog().chat("File '%s': %i examples [%s]", - filepath, - len([e for e in examples if e.finder==finder]), - str(finder)) + clog().chat( + "File '%s': %i examples [%s]", filepath, + len([e for e in examples if e.finder == finder]), + str(finder) + ) if True or clog().isEnabledFor(DEBUG): for e in examples: @@ -428,23 +467,14 @@ def _log_debug(self, what, where): def get_examples_using(self, finder, string, filepath, start_lineno): return self.from_string_get_items_using( - finder, - string, - self.get_example, - 'examples', - filepath, - start_lineno - ) + finder, string, self.get_example, 'examples', filepath, + start_lineno + ) def get_zones_using(self, zdelimiter, string, filepath, start_lineno): return self.from_string_get_items_using( - zdelimiter, - string, - self.get_zone, - 'zones', - filepath, - start_lineno - ) + zdelimiter, string, self.get_zone, 'zones', filepath, start_lineno + ) def get_example(self, finder, match, where): with enhance_exceptions(where, finder): @@ -463,14 +493,18 @@ def get_example(self, finder, match, where): # who can parse it? parser = self.parser_by_language.get(language) if not parser: - self._log_drop('no parser found for %s language' % language, where) - return # TODO should be an error? + self._log_drop( + 'no parser found for %s language' % language, where + ) + return # TODO should be an error? # who can execute it? runner = self.runner_by_language.get(language) if not runner: - self._log_drop('no runner found for %s language' % language, where) - return # TODO should be an error? + self._log_drop( + 'no runner found for %s language' % language, where + ) + return # TODO should be an error? # save the indentation here indent = match.group('indent') @@ -483,8 +517,9 @@ def get_example(self, finder, match, where): with enhance_exceptions(where, parser), log_with(language): # perfect, we have everything to build an example - example = Example(finder, runner, parser, - snippet, expected, indent, where) + example = Example( + finder, runner, parser, snippet, expected, indent, where + ) return example def get_zone(self, zdelimiter, match, where): @@ -492,8 +527,16 @@ def get_zone(self, zdelimiter, match, where): zone_str = zdelimiter.get_zone(match, where) return Zone(zdelimiter, zone_str, where) - def from_string_get_items_using(self, matcher, string, getter, what, - filepath='', start_lineno=1, zdelimiter=None): + def from_string_get_items_using( + self, + matcher, + string, + getter, + what, + filepath='', + start_lineno=1, + zdelimiter=None + ): charno = 0 items = [] @@ -517,6 +560,7 @@ def from_string_get_items_using(self, matcher, string, getter, what, items.append(item) return items + class ExampleFinder(object): specific = True @@ -525,13 +569,13 @@ def __init__(self, verbosity, encoding, **unused): self.encoding = encoding def example_regex(self): - raise NotImplementedError() # pragma: no cover + raise NotImplementedError() # pragma: no cover def get_matches(self, string): return self.example_regex().finditer(string) def get_language_of(self, options, match, where): - raise NotImplementedError() # pragma: no cover + raise NotImplementedError() # pragma: no cover def __repr__(self): return '%s Finder' % tohuman(self.target if self.target else self) @@ -676,7 +720,7 @@ def __init__(self, verbosity, encoding, **unused): self.encoding = encoding def zone_regex(self): - raise NotImplementedError() # pragma: no cover + raise NotImplementedError() # pragma: no cover def get_matches(self, string): return self.zone_regex().finditer(string) @@ -685,4 +729,6 @@ def get_zone(self, match, where): return match.group('zone') def __repr__(self): - return '%s Zone Delimiter' % tohuman(self.target if self.target else self) + return '%s Zone Delimiter' % tohuman( + self.target if self.target else self + ) diff --git a/byexample/init.py b/byexample/init.py index 0c73f074..a7d8e848 100644 --- a/byexample/init.py +++ b/byexample/init.py @@ -11,6 +11,7 @@ from .common import enhance_exceptions from .log import clog, log_context, configure_log_system, setLogLevels, TRACE, DEBUG, CHAT, INFO, NOTE, ERROR, CRITICAL + def are_tty_colors_supported(output): def get_colors(): try: @@ -36,6 +37,7 @@ def get_colors(): 'm' not in os.getenv('TERM', '').split('-') and \ get_colors() >= 8 + def is_a(target_class, key_attr, warn_missing_key_attr): ''' Returns a function that will return True if its argument @@ -56,22 +58,24 @@ def _is_X(obj): if class_ok and not attr_ok: clog().warn( - "Class '%s' has not attribute '%s'.", - obj.__name__, key_attr) + "Class '%s' has not attribute '%s'.", obj.__name__, key_attr + ) return class_ok and attr_ok return _is_X + @log_context('byexample.load') def load_modules(dirnames, cfg): verbosity = cfg['verbosity'] - registry = {'runners': {}, - 'finders': {}, - 'parsers': {}, - 'concerns': {}, - 'zdelimiters': {}, - } + registry = { + 'runners': {}, + 'finders': {}, + 'parsers': {}, + 'concerns': {}, + 'zdelimiters': {}, + } for importer, name, is_pkg in pkgutil.iter_modules(dirnames): path = importer.path @@ -81,24 +85,28 @@ def load_modules(dirnames, cfg): module = importer.find_module(name).load_module(name) except Exception as e: clog().info( - "From '%s' loading '%s'...failed: %s", - path, name, str(e)) + "From '%s' loading '%s'...failed: %s", path, name, str(e) + ) continue stability = getattr(module, 'stability', 'undefined') - if stability not in ('experimental', 'provisional', 'unstable', 'stable', 'deprecated'): + if stability not in ( + 'experimental', 'provisional', 'unstable', 'stable', 'deprecated' + ): stability = 'experimental/%s?' % str(stability) clog().chat("From '%s' loaded '%s' (%s)", path, name, stability) - for klass, key, what in [(ExampleRunner, 'language', 'runners'), - (ExampleParser, 'language', 'parsers'), - (ExampleFinder, 'target', 'finders'), - (ZoneDelimiter, 'target', 'zdelimiters'), - (Concern, 'target', 'concerns')]: + for klass, key, what in [ + (ExampleRunner, 'language', 'runners'), + (ExampleParser, 'language', 'parsers'), + (ExampleFinder, 'target', 'finders'), + (ZoneDelimiter, 'target', 'zdelimiters'), + (Concern, 'target', 'concerns') + ]: # we are interested in any class that is a subclass of 'klass' # and it has an attribute 'key' - predicate = is_a(klass, key, verbosity-2) + predicate = is_a(klass, key, verbosity - 2) container = registry[what] klasses_found = inspect.getmembers(module, predicate) @@ -109,9 +117,10 @@ def load_modules(dirnames, cfg): klasses_found = set(klasses_found) - set(container.values()) if klasses_found: - clog().debug("Classes found for '%s': %s", - what, - ', '.join(k.__name__ for k in klasses_found)) + clog().debug( + "Classes found for '%s': %s", what, + ', '.join(k.__name__ for k in klasses_found) + ) objs = [klass(**cfg) for klass in klasses_found] if objs: @@ -126,12 +135,15 @@ def load_modules(dirnames, cfg): container[k] = obj loaded_objs.append(obj) - clog().chat("\n".join((" - %s" % repr(i)) for i in loaded_objs)) + clog().chat( + "\n".join((" - %s" % repr(i)) for i in loaded_objs) + ) else: clog().chat("No classes found for '%s'.", what) return registry + def get_allowed_languages(registry, selected): available = set([obj.language for obj in registry['runners'].values()] + \ [obj.language for obj in registry['parsers'].values()]) @@ -149,6 +161,7 @@ def get_allowed_languages(registry, selected): not_found) return selected + def verify_encodings(input_encoding, verbosity): if sys.stdout.encoding is None: try: @@ -159,14 +172,16 @@ def verify_encodings(input_encoding, verbosity): clog().error( "The encoding of your terminal is unset.\n" + - "Try to set the environment variable PYTHONIOENCODING='%s' first\n" + - "or run 'byexample' like this:\n" + - " PYTHONIOENCODING='%s' byexample ...\n", e, e) + "Try to set the environment variable PYTHONIOENCODING='%s' first\n" + + "or run 'byexample' like this:\n" + + " PYTHONIOENCODING='%s' byexample ...\n", e, e + ) sys.exit(-1) clog().chat("Encoding (input): %s.", input_encoding) clog().chat("Encoding (output): %s.", sys.stdout.encoding) + def geometry(x): lines, columns = [int(v.strip()) for v in str(x).split('x')] if lines < 0 or columns < 0: @@ -174,57 +189,65 @@ def geometry(x): return (lines, columns) + def get_default_options_parser(cmdline_args): options_parser = OptionParser() options_parser.add_flag( - "fail-fast", - default=cmdline_args.fail_fast, - help="if an example fails, fail and stop all the execution.") + "fail-fast", + default=cmdline_args.fail_fast, + help="if an example fails, fail and stop all the execution." + ) options_parser.add_flag( - "norm-ws", - default=False, - help="ignore the amount of whitespaces.") + "norm-ws", default=False, help="ignore the amount of whitespaces." + ) options_parser.add_flag( - "pass", - default=False, - help="run the example but do not check its output.") + "pass", + default=False, + help="run the example but do not check its output." + ) options_parser.add_flag( - "skip", - default=False, - help="do not run the example.") + "skip", default=False, help="do not run the example." + ) options_parser.add_flag( - "tags", - default=True, - help="enable the tags <...>.") + "tags", default=True, help="enable the tags <...>." + ) options_parser.add_flag( - "enhance-diff", - default=cmdline_args.enhance_diff, - help="improve how the diff are shown.") + "enhance-diff", + default=cmdline_args.enhance_diff, + help="improve how the diff are shown." + ) options_parser.add_argument( - "+rm", - default=[], - action='append', - help="remove a character from the got and expected strings.") + "+rm", + default=[], + action='append', + help="remove a character from the got and expected strings." + ) options_parser.add_argument( - "+timeout", - default=cmdline_args.timeout, - type=float, - help="timeout in seconds to complete the example.") + "+timeout", + default=cmdline_args.timeout, + type=float, + help="timeout in seconds to complete the example." + ) options_parser.add_argument( - "+diff", - default=cmdline_args.diff, - choices=['none', 'unified', 'ndiff', 'context', 'tool'], - help="select diff algorithm.") + "+diff", + default=cmdline_args.diff, + choices=['none', 'unified', 'ndiff', 'context', 'tool'], + help="select diff algorithm." + ) options_parser.add_argument( - "+geometry", - default=(24, 80), - type=geometry, - help="number of lines and columns of the terminal of the form LxC (default to 24x80).") + "+geometry", + default=(24, 80), + type=geometry, + help= + "number of lines and columns of the terminal of the form LxC (default to 24x80)." + ) options_parser.add_argument( - "+term", - default='dumb', - choices=['as-is', 'dumb', 'ansi'], - help="select a terminal emulator to interpret the output (default to 'dumb').") + "+term", + default='dumb', + choices=['as-is', 'dumb', 'ansi'], + help= + "select a terminal emulator to interpret the output (default to 'dumb')." + ) return options_parser @@ -233,11 +256,13 @@ def get_default_options_parser(cmdline_args): def get_options(args, cfg): # the options object should have a set of default values based # on the flags and values from the command line. - options = Options({ - 'interact': False, - 'shebangs': args.shebangs, - 'difftool': args.difftool, - }) + options = Options( + { + 'interact': False, + 'shebangs': args.shebangs, + 'difftool': args.difftool, + } + ) clog().chat("Options (cmdline): %s", options) # create a parser for the rest of the options. @@ -266,9 +291,11 @@ def get_options(args, cfg): # # Because this, we pass these to the rest of the system to be used and # completed later - cfg['options']= options + cfg['options'] = options - clog().chat("Options (cmdline + byexample's defaults + --options): %s", options) + clog().chat( + "Options (cmdline + byexample's defaults + --options): %s", options + ) options['x'] = {} for k, v in vars(args).items(): @@ -277,9 +304,12 @@ def get_options(args, cfg): return options + def show_options(cfg, registry, allowed_languages): - parsers = [p for p in registry['parsers'].values() - if p.language in allowed_languages] + parsers = [ + p for p in registry['parsers'].values() + if p.language in allowed_languages + ] concerns = [c for c in registry['concerns'].values()] @@ -292,14 +322,19 @@ def _title(t, nl=True): for concern in concerns: _title("%s's specific options" % concern.target) - with enhance_exceptions('Extending the options', concern, cfg['use_colors']): + with enhance_exceptions( + 'Extending the options', concern, cfg['use_colors'] + ): concern.get_extended_option_parser(parent_parser=None).print_help() for parser in parsers: _title("%s's specific options" % parser.language) - with enhance_exceptions('Extending the options', parser, cfg['use_colors']): + with enhance_exceptions( + 'Extending the options', parser, cfg['use_colors'] + ): parser.get_extended_option_parser(parent_parser=None).print_help() + def extend_option_parser_with_concerns(cfg, registry): concerns = [c for c in registry['concerns'].values()] @@ -307,11 +342,15 @@ def extend_option_parser_with_concerns(cfg, registry): # starting from the byexample's one optparser = cfg['options']['optparser'] for concern in concerns: - with enhance_exceptions('Extending the options', concern, cfg['use_colors']): + with enhance_exceptions( + 'Extending the options', concern, cfg['use_colors'] + ): # update the options with the concerns's default ones and # only with them; only after this merge concerns's parser with # the previous parser - concern_optparser = concern.get_extended_option_parser(parent_parser=None) + concern_optparser = concern.get_extended_option_parser( + parent_parser=None + ) cfg['options'].update(concern_optparser.defaults()) optparser = concern.get_extended_option_parser(optparser) @@ -319,15 +358,22 @@ def extend_option_parser_with_concerns(cfg, registry): cfg['options']['optparser'] = optparser return optparser + @log_context('byexample.options') def extend_options_with_language_specific(cfg, registry, allowed_languages): - parsers = [p for p in registry['parsers'].values() - if p.language in allowed_languages] + parsers = [ + p for p in registry['parsers'].values() + if p.language in allowed_languages + ] # update the defaults for all the parsers for parser in parsers: - with enhance_exceptions('Extending the options', parser, cfg['use_colors']): - parser_optparser = parser.get_extended_option_parser(parent_parser=None) + with enhance_exceptions( + 'Extending the options', parser, cfg['use_colors'] + ): + parser_optparser = parser.get_extended_option_parser( + parent_parser=None + ) cfg['options'].update(parser_optparser.defaults()) # update again but with the options that *may* had set in the command line @@ -335,23 +381,44 @@ def extend_options_with_language_specific(cfg, registry, allowed_languages): opts = parser.extract_cmdline_options(cfg['opts_from_cmdline']) cfg['options'].update(opts) - clog().chat("Options (cmdline + --options + language specific): %s", cfg['options']) + clog().chat( + "Options (cmdline + --options + language specific): %s", cfg['options'] + ) + def verbosity_to_log_levels(verbosity, quiet): if quiet: - return {'byexample':CRITICAL} + return {'byexample': CRITICAL} tmp = [ - {'byexample':NOTE}, - {'byexample':NOTE, 'byexample.exec':INFO}, # -v - {'byexample':NOTE, 'byexample.exec':CHAT}, # -vv - {'byexample':INFO, 'byexample.exec':CHAT}, # -vvv - {'byexample':CHAT}, # -vvvv - {'byexample':DEBUG}, # -vvvvv - {'byexample':TRACE}, # -vvvvvv - ] + { + 'byexample': NOTE + }, + { + 'byexample': NOTE, + 'byexample.exec': INFO + }, # -v + { + 'byexample': NOTE, + 'byexample.exec': CHAT + }, # -vv + { + 'byexample': INFO, + 'byexample.exec': CHAT + }, # -vvv + { + 'byexample': CHAT + }, # -vvvv + { + 'byexample': DEBUG + }, # -vvvvv + { + 'byexample': TRACE + }, # -vvvvvv + ] + + return tmp[min(verbosity, len(tmp) - 1)] - return tmp[min(verbosity, len(tmp)-1)] @log_context('byexample.init') def init(args): @@ -361,15 +428,15 @@ def init(args): verify_encodings(args.encoding, args.verbosity) cfg = { - 'use_progress_bar': args.pretty == 'all', - 'use_colors': args.pretty == 'all', - 'quiet': args.quiet, - 'verbosity': args.verbosity, - 'encoding': args.encoding, - 'output': sys.stdout, - 'interact': False, - 'opts_from_cmdline': args.options_str, - } + 'use_progress_bar': args.pretty == 'all', + 'use_colors': args.pretty == 'all', + 'quiet': args.quiet, + 'verbosity': args.verbosity, + 'encoding': args.encoding, + 'output': sys.stdout, + 'interact': False, + 'opts_from_cmdline': args.options_str, + } allowed_files = set(args.files) - set(args.skip) testfiles = list(sorted(f for f in args.files if f in allowed_files)) @@ -392,8 +459,10 @@ def init(args): if not testfiles: if not cfg['quiet']: - clog().error("No files were found (you passed %i files and %i were skipped)", - (len(set(args.files)), len(set(args.files) - allowed_files))) + clog().error( + "No files were found (you passed %i files and %i were skipped)", + (len(set(args.files)), len(set(args.files) - allowed_files)) + ) sys.exit(1) # extend the option parser with all the parsers of the concerns. @@ -419,7 +488,7 @@ def init(args): differ = Differ(**cfg) harvester = ExampleHarvest(allowed_languages, registry, **cfg) - executor = FileExecutor(concerns, differ, **cfg) + executor = FileExecutor(concerns, differ, **cfg) configure_log_system(use_colors=cfg['use_colors'], concerns=concerns) return testfiles, harvester, executor, options diff --git a/byexample/jobs.py b/byexample/jobs.py index edde0832..b2182d40 100644 --- a/byexample/jobs.py +++ b/byexample/jobs.py @@ -3,12 +3,14 @@ import signal, contextlib from .log import clog, CHAT + class Status: ok = 0 failed = 1 aborted = 2 error = 3 + def worker(func, sigint_handler, input, output): ''' Generic worker: call for each item pulled from the queue until a None gets pulled. @@ -23,6 +25,7 @@ def worker(func, sigint_handler, input, output): output.close() output.join_thread() + class Jobs(object): def __init__(self, njobs): self.njobs = njobs @@ -46,9 +49,13 @@ def spawn_jobs(self, func, items): self.input = Queue() self.output = Queue() - self.processes = [Process(target=worker, name=str(n), - args=(func, self.sigint_handler, self.input, self.output)) - for n in range(njobs)] + self.processes = [ + Process( + target=worker, + name=str(n), + args=(func, self.sigint_handler, self.input, self.output) + ) for n in range(njobs) + ] for p in self.processes: p.start() @@ -132,6 +139,7 @@ def run(self, func, items, fail_fast): rest = self.spawn_jobs(func, items) return self.loop(len(items), rest, fail_fast) + @contextlib.contextmanager def allow_sigint(handler): try: diff --git a/byexample/log.py b/byexample/log.py index fe91854e..7f6a8f3d 100644 --- a/byexample/log.py +++ b/byexample/log.py @@ -5,6 +5,7 @@ from .common import colored, highlight_syntax, indent from .log_level import TRACE, DEBUG, CHAT, INFO, NOTE, WARNING, ERROR, CRITICAL + class XFormatter(Formatter): def format(self, record): self._cur_record = record @@ -26,49 +27,49 @@ def formatMessage(self, record): logger = getLogger(name) color = { - DEBUG: 'none', - CHAT: 'cyan', - INFO: 'cyan', - NOTE: 'cyan', - WARNING: 'yellow', - ERROR: 'red', - CRITICAL: 'red', - }[lvlno] + DEBUG: 'none', + CHAT: 'cyan', + INFO: 'cyan', + NOTE: 'cyan', + WARNING: 'yellow', + ERROR: 'red', + CRITICAL: 'red', + }[lvlno] if lvlno == DEBUG or getattr(record, 'no_marker', False): marker = '' elif logger.isEnabledFor(DEBUG): marker = { - DEBUG: '', - CHAT: 'chat', - INFO: 'info', - NOTE: 'note', - WARNING: 'warn', - ERROR: 'error', - CRITICAL: 'crit', - }[lvlno] + DEBUG: '', + CHAT: 'chat', + INFO: 'info', + NOTE: 'note', + WARNING: 'warn', + ERROR: 'error', + CRITICAL: 'crit', + }[lvlno] marker = "%s:%s" % (marker, self.shorter_name(name)) elif logger.isEnabledFor(CHAT): marker = { - DEBUG: '', - CHAT: '[i:%s]', - INFO: '[i:%s]', - NOTE: '[i:%s]', - WARNING: '[w:%s]', - ERROR: '[!:%s]', - CRITICAL: '[!:%s]', - }[lvlno] + DEBUG: '', + CHAT: '[i:%s]', + INFO: '[i:%s]', + NOTE: '[i:%s]', + WARNING: '[w:%s]', + ERROR: '[!:%s]', + CRITICAL: '[!:%s]', + }[lvlno] marker = marker % self.shorter_name(name) else: marker = { - DEBUG: '', - CHAT: '[i]', - INFO: '[i]', - NOTE: '[i]', - WARNING: '[w]', - ERROR: '[!]', - CRITICAL: '[!]', - }[lvlno] + DEBUG: '', + CHAT: '[i]', + INFO: '[i]', + NOTE: '[i]', + WARNING: '[w]', + ERROR: '[!]', + CRITICAL: '[!]', + }[lvlno] use_colors = getLogger('byexample').use_colors if marker and not getattr(record, 'disable_prefix', False): @@ -98,9 +99,9 @@ def formatException(self, ei): if self._cur_logger.isEnabledFor(INFO): return Formatter.formatException(self, ei) else: - return '%s\n\n%s' % (str(ei[1]), - "Rerun with -vvv to get a full stack trace." - ) + return '%s\n\n%s' % ( + str(ei[1]), "Rerun with -vvv to get a full stack trace." + ) class XLogger(Logger): @@ -173,17 +174,22 @@ def exception(self, msg, *args, **kwargs): msg += ', {where}' msg += ':' - msg = msg.format(where_default=where_default, - where=where) + msg = msg.format(where_default=where_default, where=where) return self.error(msg, exc_info=True, **kwargs) else: - return Logger.exception(self, msg, *args, exc_info=exc_info, **kwargs) + return Logger.exception( + self, msg, *args, exc_info=exc_info, **kwargs + ) + _logger_stack = [] + + def clog(): return _logger_stack[-1] + def log_context(logger_name): global _logger_stack @@ -197,9 +203,12 @@ def wrapped(*args, **kargs): return func(*args, **kargs) finally: _logger_stack.pop() + return wrapped + return decorator + @contextlib.contextmanager def log_with(logger_name, child=True): global _logger_stack @@ -215,6 +224,7 @@ def log_with(logger_name, child=True): finally: _logger_stack.pop() + class XStreamHandler(logging.StreamHandler): def __init__(self, *args, **kargs): logging.StreamHandler.__init__(self, *args, **kargs) @@ -239,6 +249,7 @@ def acquire(self): def release(self): return + def init_log_system(level=NOTE, use_colors=False): global _logger_stack @@ -253,7 +264,7 @@ def init_log_system(level=NOTE, use_colors=False): logging.TRACE = TRACE logging.addLevelName(TRACE, 'TRACE') - rlog = getLogger(name='byexample') # root + rlog = getLogger(name='byexample') # root ch = XStreamHandler(stream=sys.stdout) @@ -273,8 +284,9 @@ def init_log_system(level=NOTE, use_colors=False): configure_log_system(default_level=level, use_colors=use_colors) rlog.xstream_handler.concerns = None + def configure_log_system(default_level=None, use_colors=None, concerns=None): - rlog = getLogger(name='byexample') # root + rlog = getLogger(name='byexample') # root if default_level is not None: rlog.setLevel(default_level) @@ -284,6 +296,7 @@ def configure_log_system(default_level=None, use_colors=None, concerns=None): if concerns is not None: rlog.xstream_handler.concerns = concerns + def setLogLevels(levels): prev_lvls = {} for name, lvl in levels.items(): diff --git a/byexample/log_level.py b/byexample/log_level.py index 4f00b51e..22b1a1b9 100644 --- a/byexample/log_level.py +++ b/byexample/log_level.py @@ -1,8 +1,9 @@ from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL -NOTE = INFO+1 -CHAT = INFO-1 -TRACE = DEBUG-1 +NOTE = INFO + 1 +CHAT = INFO - 1 +TRACE = DEBUG - 1 + def str_to_level(s): s = s.upper() diff --git a/byexample/modules/clipboard.py b/byexample/modules/clipboard.py index 7f289d25..d45f5741 100644 --- a/byexample/modules/clipboard.py +++ b/byexample/modules/clipboard.py @@ -5,6 +5,7 @@ stability = 'provisional' + class PasteError(Exception): def __init__(self, example, missing): n = "tag is" if len(missing) == 1 else "tags are" @@ -14,6 +15,7 @@ def __init__(self, example, missing): Exception.__init__(self, msg) + class Clipboard(Concern): target = 'clipboard' @@ -21,7 +23,11 @@ def __init__(self, verbosity, encoding, **unused): pass def extend_option_parser(self, parser): - parser.add_flag("paste", default=False, help="enable the paste mode of captured texts.") + parser.add_flag( + "paste", + default=False, + help="enable the paste mode of captured texts." + ) return parser def start(self, examples, runners, filepath, options): @@ -35,16 +41,20 @@ def repl_from_clipboard(m, clipboard, missing): return clipboard[tag_name] except KeyError: missing.append(tag_name) - return m.group(0) # replace it by itself. + return m.group(0) # replace it by itself. PASTE_RE = re.compile(r"<(?P(?:\w|-|\.)+)>") + def before_build_regex(self, example, options): if not options['paste']: return - repl = partial(self.repl_from_clipboard, clipboard=self.clipboard, - missing=[]) - example.expected_str = re.sub(self.PASTE_RE, repl, example.expected_str) + repl = partial( + self.repl_from_clipboard, clipboard=self.clipboard, missing=[] + ) + example.expected_str = re.sub( + self.PASTE_RE, repl, example.expected_str + ) # do not check for missings: we assume that they are capture tags @@ -60,18 +70,19 @@ def finish_parse(self, example, options, exception): return missing = [] - repl = partial(self.repl_from_clipboard, clipboard=self.clipboard, - missing=missing) + repl = partial( + self.repl_from_clipboard, + clipboard=self.clipboard, + missing=missing + ) example.source = re.sub(self.PASTE_RE, repl, example.source) if missing: raise PasteError(example, missing) - def finally_example(self, example, options): got = getattr(example, 'got', None) if got == None: return # probably the example failed so we didn't get any output _, captured = example.expected.get_captures(example, got, options, 0) self.clipboard.update(captured) - diff --git a/byexample/modules/cond.py b/byexample/modules/cond.py index 77930555..63fc007b 100644 --- a/byexample/modules/cond.py +++ b/byexample/modules/cond.py @@ -5,6 +5,7 @@ stability = 'experimental' + class UnknownConditionTag(Exception): def __init__(self, example, missing): msg = "You enabled the conditional execution of this example " \ @@ -15,24 +16,27 @@ def __init__(self, example, missing): Exception.__init__(self, msg) + class Conditional(Concern): target = 'conditional' def extend_option_parser(self, parser): mutexg = parser.add_mutually_exclusive_group() mutexg.add_argument( - "+if", - "+on", - nargs=1, - default=False, - help="run the example only if the condition matches; skip the example otherwise." - ) + "+if", + "+on", + nargs=1, + default=False, + help= + "run the example only if the condition matches; skip the example otherwise." + ) mutexg.add_argument( - "+unless", - nargs=1, - default=True, - help="run the example unless the condition matches; skip the example otherwise." - ) + "+unless", + nargs=1, + default=True, + help= + "run the example unless the condition matches; skip the example otherwise." + ) return parser def finish_parse(self, example, options, exception): @@ -57,10 +61,11 @@ def finish_parse(self, example, options, exception): if cond not in clipboard: raise UnknownConditionTag(example, cond) - skip = bool(clipboard[cond]) # TODO emptiness is enough?: what about strings like '0' and 'false'? + skip = bool( + clipboard[cond] + ) # TODO emptiness is enough?: what about strings like '0' and 'false'? if neg: skip = not skip if skip: example.options['skip'] = True - diff --git a/byexample/modules/cpp.py b/byexample/modules/cpp.py index 6227d065..d3bf5af9 100644 --- a/byexample/modules/cpp.py +++ b/byexample/modules/cpp.py @@ -38,12 +38,14 @@ stability = 'experimental' + class CppPromptFinder(ExampleFinder): target = 'cpp-prompt' @constant def example_regex(self): - return re.compile(r''' + return re.compile( + r''' (?P (?:^(?P [ ]*) (?:\?:)[ ] .*) # PS1 line (?:\n [ ]* :: .*)*) # PS2 lines @@ -53,13 +55,16 @@ def example_regex(self): (?![ ]*(?:\?:)) # Not a line starting with PS1 .+$\n? # But any other line )*) - ''', re.MULTILINE | re.VERBOSE) + ''', re.MULTILINE | re.VERBOSE + ) def get_language_of(self, *args, **kargs): return 'cpp' def get_snippet_and_expected(self, match, where): - snippet, expected = ExampleFinder.get_snippet_and_expected(self, match, where) + snippet, expected = ExampleFinder.get_snippet_and_expected( + self, match, where + ) snippet = self._remove_prompts(snippet) return snippet, expected @@ -68,6 +73,7 @@ def _remove_prompts(self, snippet): lines = snippet.split("\n") return '\n'.join(line[3:] for line in lines) + class CPPParser(ExampleParser): language = 'cpp' @@ -75,8 +81,8 @@ class CPPParser(ExampleParser): def example_options_string_regex(self): # anything of the form: # // byexample: +FOO -BAR +ZAZ=42 - return re.compile(r'//\s*byexample:\s*([^\n\'"]*)$', - re.MULTILINE) + return re.compile(r'//\s*byexample:\s*([^\n\'"]*)$', re.MULTILINE) + class CPPInterpreter(ExampleRunner, PexpectMixin): language = 'cpp' @@ -84,19 +90,21 @@ class CPPInterpreter(ExampleRunner, PexpectMixin): def __init__(self, verbosity, encoding, **unused): self.encoding = encoding - PexpectMixin.__init__(self, - PS1_re = r'\[cling\]\$', # [cling]$ - any_PS_re = r'\[cling\][$!](?: \?)?') # [cling]! - # [cling]$ ? + PexpectMixin.__init__( + self, + PS1_re=r'\[cling\]\$', # [cling]$ + any_PS_re=r'\[cling\][$!](?: \?)?' + ) # [cling]! + # [cling]$ ? def get_default_cmd(self, *args, **kargs): - return "%e %p %a", { - 'e': "/usr/bin/env", - 'p': "cling", - 'a': [ - "--nologo", # do not output the banner - ] - } + return "%e %p %a", { + 'e': "/usr/bin/env", + 'p': "cling", + 'a': [ + "--nologo", # do not output the banner + ] + } def run(self, example, options): # the algorithm to filter the echos from the cling's output @@ -123,11 +131,12 @@ def initialize(self, options): cmd = ShebangTemplate(shebang).quote_and_substitute(tokens) options.up() - options['geometry'] = (max(options['geometry'][0], 128), max(options['geometry'][1], 128)) + options['geometry'] = ( + max(options['geometry'][0], 128), max(options['geometry'][1], 128) + ) self._spawn_interpreter(cmd, options) options.down() - def _change_terminal_geometry(self, rows, cols, options): raise Exception("This should never happen") @@ -138,4 +147,4 @@ def _get_output(self, options): return self._get_output_echo_filtered(options) def cancel(self, example, options): - return False # not supported by cling + return False # not supported by cling diff --git a/byexample/modules/delimiters.py b/byexample/modules/delimiters.py index 8cc3eb57..27bbc1e2 100644 --- a/byexample/modules/delimiters.py +++ b/byexample/modules/delimiters.py @@ -5,6 +5,7 @@ stability = 'provisional' + class NoDelimiter(ZoneDelimiter): target = 'no-delimiter' @@ -21,7 +22,8 @@ class CppCommentDelimiter(ZoneDelimiter): @constant def zone_regex(self): - return re.compile(r''' + return re.compile( + r''' # Begin with a /* marker ^[ ]* /\* @@ -31,7 +33,8 @@ def zone_regex(self): # and the close marker \*/ - ''', re.DOTALL | re.MULTILINE | re.VERBOSE) + ''', re.DOTALL | re.MULTILINE | re.VERBOSE + ) @constant def leading_asterisk(self): @@ -44,12 +47,14 @@ def get_zone(self, match, where): def __repr__(self): return "/* ... */" + class HashCommentDelimiter(ZoneDelimiter): target = {'.rb'} @constant def zone_regex(self): - return re.compile(r''' + return re.compile( + r''' # Begin with a # marker ^[ ]* \# @@ -59,7 +64,8 @@ def zone_regex(self): (?P .*$\n? # first line (?:[ ]* \# .*$\n?)* # the rest of the lines ) - ''', re.MULTILINE | re.VERBOSE) + ''', re.MULTILINE | re.VERBOSE + ) @constant def leading_sharp(self): @@ -72,12 +78,14 @@ def get_zone(self, match, where): def __repr__(self): return "# ..." + class DocStringDelimiter(ZoneDelimiter): target = {'.py'} @constant def zone_regex(self): - return re.compile(r''' + return re.compile( + r''' # Begin with a triple single or double quote ^[ ]* [bBuU]?[rR]?(?P(?:\'\'\') | (?:""")) @@ -87,17 +95,20 @@ def zone_regex(self): # finally, the end marker [^\\](?P=marker) # then we must match the same kind of quotes - ''', re.DOTALL | re.MULTILINE | re.VERBOSE) + ''', re.DOTALL | re.MULTILINE | re.VERBOSE + ) def __repr__(self): return "''' ... ''' or \"\"\" ... \"\"\"" + class MarkdownFencedCodeDelimiter(ZoneDelimiter): target = {'.md'} @constant def zone_regex(self): - return re.compile(r''' + return re.compile( + r''' # Begin with a markdown fenced-code marker or a html comment marker ^[ ]* (?: @@ -113,7 +124,8 @@ def zone_regex(self): ^[ ]*(?P=marker) # then we must match the same amount of backticks |(?:-->) # otherwise, we must match the close of the html comment ) - ''', re.DOTALL | re.MULTILINE | re.VERBOSE) + ''', re.DOTALL | re.MULTILINE | re.VERBOSE + ) def __repr__(self): return "``` ... ``` or " diff --git a/byexample/modules/elixir.py b/byexample/modules/elixir.py index 8f99c03f..3626bef8 100644 --- a/byexample/modules/elixir.py +++ b/byexample/modules/elixir.py @@ -58,12 +58,14 @@ stability = 'experimental' + class ElixirPromptFinder(ExampleFinder): target = 'elixir-prompt' @constant def example_regex(self): - return re.compile(r''' + return re.compile( + r''' # Snippet consists of one PS1 line iex> and zero or more PS2 lines (?P (?:^(?P [ ]*) iex>[ ] .*) # PS1 line @@ -75,13 +77,16 @@ def example_regex(self): (?![ ]* iex>) # Not a line starting with PS1 .+$\n? # But any other line )*) - ''', re.MULTILINE | re.VERBOSE) + ''', re.MULTILINE | re.VERBOSE + ) def get_language_of(self, *args, **kargs): return 'elixir' def get_snippet_and_expected(self, match, where): - snippet, expected = ExampleFinder.get_snippet_and_expected(self, match, where) + snippet, expected = ExampleFinder.get_snippet_and_expected( + self, match, where + ) snippet, expected = self._remove_prompts(snippet, expected) @@ -98,16 +103,20 @@ def _remove_prompts(self, snippet, expected): lines = snippet.split("\n") return '\n'.join(line[5:] for line in lines), expected + class ElixirParser(ExampleParser): language = 'elixir' @constant def example_options_string_regex(self): - return re.compile(r'#\s*byexample:\s*([^\n\'"]*)$', - re.MULTILINE) + return re.compile(r'#\s*byexample:\s*([^\n\'"]*)$', re.MULTILINE) def extend_option_parser(self, parser): - parser.add_flag("elixir-dont-display-hack", default=False, help="required for IEx < 1.9.") + parser.add_flag( + "elixir-dont-display-hack", + default=False, + help="required for IEx < 1.9." + ) parser.add_argument("+elixir-expr-print", choices=['auto', 'true', 'false'], default='auto', help='print the expression\'s value (true); ' +\ @@ -138,7 +147,9 @@ def process_snippet_and_expected(self, snippet, expected_str): if self._elixir_print_expected: expected_str = self._EXPR_RESULT_RE.sub('', expected_str, count=1) - return ExampleParser.process_snippet_and_expected(self, snippet, expected_str) + return ExampleParser.process_snippet_and_expected( + self, snippet, expected_str + ) def parse(self, example, concerns): example = ExampleParser.parse(self, example, concerns) @@ -152,13 +163,16 @@ def parse(self, example, concerns): _EXPR_RESULT_RE = re.compile(r'^=>([ ]*\n| |$)', re.MULTILINE | re.DOTALL) + class ElixirInterpreter(ExampleRunner, PexpectMixin): language = 'elixir' def __init__(self, verbosity, encoding, **unused): - PexpectMixin.__init__(self, - PS1_re = r'_byexample iex _byexample/iex> ', - any_PS_re = r'_byexample (iex|\.\.\.) _byexample/iex> ') + PexpectMixin.__init__( + self, + PS1_re=r'_byexample iex _byexample/iex> ', + any_PS_re=r'_byexample (iex|\.\.\.) _byexample/iex> ' + ) self.encoding = encoding @@ -194,10 +208,16 @@ def _run_impl(self, example, options): # We keep a state in _print_expre_activated so we know if we # need to switch or not the display suppression. if not example._elixir_print_expected and self._print_expre_activated: - self._exec_and_wait(r'IEx.configure(inspect: [inspect_fun: fn a,b -> "" end])', options) + self._exec_and_wait( + r'IEx.configure(inspect: [inspect_fun: fn a,b -> "" end])', + options + ) self._print_expre_activated = False elif example._elixir_print_expected and not self._print_expre_activated: - self._exec_and_wait(r'IEx.configure(inspect: [inspect_fun: fn a,b -> Inspect.inspect(a,b) end])', options) + self._exec_and_wait( + r'IEx.configure(inspect: [inspect_fun: fn a,b -> Inspect.inspect(a,b) end])', + options + ) self._print_expre_activated = True return self._exec_and_wait(src, options) @@ -206,14 +226,14 @@ def interact(self, example, options): PexpectMixin.interact(self) def get_default_cmd(self, *args, **kargs): - return "%e %p %a", { - 'e': '/usr/bin/env', - 'p': 'iex', - 'a': [ - '--dot-iex', - '""', # do not load any conf file - ] - } + return "%e %p %a", { + 'e': '/usr/bin/env', + 'p': 'iex', + 'a': [ + '--dot-iex', + '""', # do not load any conf file + ] + } def initialize(self, options): shebang, tokens = self.get_default_cmd() @@ -226,18 +246,25 @@ def initialize(self, options): # run! options.up() - options['geometry'] = (max(options['geometry'][0], 128), max(options['geometry'][1], 128)) - self._spawn_interpreter(cmd, options, initial_prompt=r'iex\(\d+\)> ' ) + options['geometry'] = ( + max(options['geometry'][0], 128), max(options['geometry'][1], 128) + ) + self._spawn_interpreter(cmd, options, initial_prompt=r'iex\(\d+\)> ') options.down() self._drop_output() - self._exec_and_wait(r'IEx.configure(default_prompt: "_byexample %prefix _byexample/iex>")', options) + self._exec_and_wait( + r'IEx.configure(default_prompt: "_byexample %prefix _byexample/iex>")', + options + ) # Set a smaller width to force the pretty print of IEx to put some # new lines self._exec_and_wait(r'IEx.configure(inspect: [width: 32])', options) - self._exec_and_wait(r'IEx.configure(colors: [enabled: false])', options) + self._exec_and_wait( + r'IEx.configure(colors: [enabled: false])', options + ) def _change_terminal_geometry(self, rows, cols, options): raise Exception("This should never happen") diff --git a/byexample/modules/gdb.py b/byexample/modules/gdb.py index 9031706e..8f2946a1 100644 --- a/byexample/modules/gdb.py +++ b/byexample/modules/gdb.py @@ -18,12 +18,14 @@ stability = 'experimental' + class GDBPromptFinder(ExampleFinder): target = 'gdb-prompt' @constant def example_regex(self): - return re.compile(r''' + return re.compile( + r''' # Snippet consists of a single prompt line (gdb) (?P (?:^(?P [ ]*) \(gdb\)[ ] .*) @@ -35,19 +37,23 @@ def example_regex(self): (?![ ]*\(gdb\)) # Not a line starting with the prompt .+$\n? # But any other line )*) - ''', re.MULTILINE | re.VERBOSE) + ''', re.MULTILINE | re.VERBOSE + ) def get_language_of(self, *args, **kargs): return 'gdb' def get_snippet_and_expected(self, match, where): - snippet, expected = ExampleFinder.get_snippet_and_expected(self, match, where) + snippet, expected = ExampleFinder.get_snippet_and_expected( + self, match, where + ) snippet = self._remove_prompts(snippet) return snippet, expected def _remove_prompts(self, snippet): - return snippet[6:] # remove the (gdb) prompt + return snippet[6:] # remove the (gdb) prompt + class GDBParser(ExampleParser): language = 'gdb' @@ -56,12 +62,12 @@ class GDBParser(ExampleParser): def example_options_string_regex(self): # anything of the form: # # byexample: +FOO -BAR +ZAZ=42 - return re.compile(r'#\s*byexample:\s*([^\n\'"]*)$', - re.MULTILINE) + return re.compile(r'#\s*byexample:\s*([^\n\'"]*)$', re.MULTILINE) def process_snippet_and_expected(self, snippet, expected): - snippet, expected = ExampleParser.process_snippet_and_expected(self, - snippet, expected) + snippet, expected = ExampleParser.process_snippet_and_expected( + self, snippet, expected + ) # remove any option string, gdb does not support # comments. If we do not do this, gdb will complain snippet = self.example_options_string_regex().sub('', snippet) @@ -78,20 +84,22 @@ def __init__(self, verbosity, encoding, **unused): # --nh do not read ~/.gdbinit # --nx do not read any .gdbinit # --quiet do not print version number on startup - PexpectMixin.__init__(self, - PS1_re = r'\(gdb\)[ ]', - any_PS_re = r'\(gdb\)[ ]') + PexpectMixin.__init__( + self, PS1_re=r'\(gdb\)[ ]', any_PS_re=r'\(gdb\)[ ]' + ) def get_default_cmd(self, *args, **kargs): - return "%e %p %a", { - 'e': "/usr/bin/env", - 'p': "gdb", - 'a': [ - "--nh", # do not read ~/.gdbinit. - "--nx", # do not read any .gdbinit files in any directory - "--quiet", # do not print version on startup - ] - } + return "%e %p %a", { + 'e': + "/usr/bin/env", + 'p': + "gdb", + 'a': [ + "--nh", # do not read ~/.gdbinit. + "--nx", # do not read any .gdbinit files in any directory + "--quiet", # do not print version on startup + ] + } def run(self, example, options): return PexpectMixin._run(self, example, options) @@ -120,10 +128,14 @@ def initialize(self, options): dfl_timeout = options['x']['dfl_timeout'] # gdb will not print the address of a variable by default - self._exec_and_wait('set print address off\n', options, timeout=dfl_timeout) + self._exec_and_wait( + 'set print address off\n', options, timeout=dfl_timeout + ) # gdb will stop at the first null when printing an array - self._exec_and_wait('set print null-stop on\n', options, timeout=dfl_timeout) + self._exec_and_wait( + 'set print null-stop on\n', options, timeout=dfl_timeout + ) # gdb will not ask for "yes or no" confirmation self._exec_and_wait('set confirm off\n', options, timeout=dfl_timeout) @@ -133,4 +145,3 @@ def shutdown(self): def cancel(self, example, options): return self._abort(example, options) - diff --git a/byexample/modules/javascript.py b/byexample/modules/javascript.py index d3076fe4..32e86af4 100644 --- a/byexample/modules/javascript.py +++ b/byexample/modules/javascript.py @@ -41,12 +41,14 @@ stability = 'experimental' + class JavascriptPromptFinder(ExampleFinder): target = 'javascript-prompt' @constant def example_regex(self): - return re.compile(r''' + return re.compile( + r''' (?P (?:^(?P [ ]*) (?:>)[ ] .*) # PS1 line (?:\n [ ]* \.[ ] .*)*) # PS2 lines @@ -56,13 +58,16 @@ def example_regex(self): (?![ ]*(?:>)) # Not a line starting with PS1 .+$\n? # But any other line )*) - ''', re.MULTILINE | re.VERBOSE) + ''', re.MULTILINE | re.VERBOSE + ) def get_language_of(self, *args, **kargs): return 'javascript' def get_snippet_and_expected(self, match, where): - snippet, expected = ExampleFinder.get_snippet_and_expected(self, match, where) + snippet, expected = ExampleFinder.get_snippet_and_expected( + self, match, where + ) snippet = self._remove_prompts(snippet) return snippet, expected @@ -71,24 +76,25 @@ def _remove_prompts(self, snippet): lines = snippet.split("\n") return '\n'.join(line[2:] for line in lines) + class JavascriptParser(ExampleParser): language = 'javascript' @constant def example_options_string_regex(self): - return re.compile(r'//\s*byexample:\s*([^\n\'"]*)$', - re.MULTILINE) + return re.compile(r'//\s*byexample:\s*([^\n\'"]*)$', re.MULTILINE) def extend_option_parser(self, parser): pass + class JavascriptInterpreter(ExampleRunner, PexpectMixin): language = 'javascript' def __init__(self, verbosity, encoding, **unused): - PexpectMixin.__init__(self, - PS1_re = r'node > ', - any_PS_re = r'(?:node > )|(?:\.\.\. )') + PexpectMixin.__init__( + self, PS1_re=r'node > ', any_PS_re=r'(?:node > )|(?:\.\.\. )' + ) self.encoding = encoding @@ -102,11 +108,11 @@ def interact(self, example, options): PexpectMixin.interact(self) def get_default_cmd(self, *args, **kargs): - return "%e %p %a", { - 'e': '/usr/bin/env', - 'p': 'nodejs', - 'a': [abspath(__file__, 'byexample-repl.js')] - } + return "%e %p %a", { + 'e': '/usr/bin/env', + 'p': 'nodejs', + 'a': [abspath(__file__, 'byexample-repl.js')] + } def initialize(self, options): shebang, tokens = self.get_default_cmd() @@ -117,10 +123,10 @@ def initialize(self, options): # run! self._spawn_interpreter(cmd, options) - self._drop_output() # discard banner and things like that + self._drop_output() # discard banner and things like that def shutdown(self): self._shutdown_interpreter() def cancel(self, example, options): - return False # not supported by nodejs + return False # not supported by nodejs diff --git a/byexample/modules/php.py b/byexample/modules/php.py index 56f64ce6..4c888d5f 100644 --- a/byexample/modules/php.py +++ b/byexample/modules/php.py @@ -45,12 +45,14 @@ stability = 'experimental' + class PHPPromptFinder(ExampleFinder): target = 'php-prompt' @constant def example_regex(self): - return re.compile(r''' + return re.compile( + r''' (?P (?:^(?P [ ]*) (?:php>)[ ] .*) # PS1 line (?:\n [ ]* \.\.\.>[ ] .*)*) # PS2 lines @@ -60,13 +62,16 @@ def example_regex(self): (?![ ]*(?:php>)) # Not a line starting with PS1 .+$\n? # But any other line )*) - ''', re.MULTILINE | re.VERBOSE) + ''', re.MULTILINE | re.VERBOSE + ) def get_language_of(self, *args, **kargs): return 'php' def get_snippet_and_expected(self, match, where): - snippet, expected = ExampleFinder.get_snippet_and_expected(self, match, where) + snippet, expected = ExampleFinder.get_snippet_and_expected( + self, match, where + ) snippet = self._remove_prompts(snippet) return snippet, expected @@ -75,29 +80,31 @@ def _remove_prompts(self, snippet): lines = snippet.split("\n") return '\n'.join(line[5:] for line in lines) + class PHPParser(ExampleParser): language = 'php' @constant def example_options_string_regex(self): - return re.compile(r'//\s*byexample:\s*([^\n\'"]*)$', - re.MULTILINE) + return re.compile(r'//\s*byexample:\s*([^\n\'"]*)$', re.MULTILINE) def extend_option_parser(self, parser): pass + class PHPInterpreter(ExampleRunner, PexpectMixin): language = 'php' def __init__(self, verbosity, encoding, **unused): - PexpectMixin.__init__(self, - # the format is '/byexample/php/ blk chr >' - # where blk indicates in which block we are - # and chr indicates in which unterminated block - # we are - PS1_re = r'/byexample/php/\s+php\s+>\s+> ', - any_PS_re = r'/byexample/php/\s+[^ ]+\s+[^ ]+\s+> ' - ) + PexpectMixin.__init__( + self, + # the format is '/byexample/php/ blk chr >' + # where blk indicates in which block we are + # and chr indicates in which unterminated block + # we are + PS1_re=r'/byexample/php/\s+php\s+>\s+> ', + any_PS_re=r'/byexample/php/\s+[^ ]+\s+[^ ]+\s+> ' + ) self.encoding = encoding @@ -120,11 +127,7 @@ def interact(self, example, options): PexpectMixin.interact(self) def get_default_cmd(self, *args, **kargs): - return "%e %p %a", { - 'e': '/usr/bin/env', - 'p': 'php', - 'a': ['-a'] - } + return "%e %p %a", {'e': '/usr/bin/env', 'p': 'php', 'a': ['-a']} def _get_output(self, options): return self._get_output_echo_filtered(options) @@ -137,11 +140,13 @@ def initialize(self, options): # run! options.up() - options['geometry'] = (max(options['geometry'][0], 128), max(options['geometry'][1], 128)) + options['geometry'] = ( + max(options['geometry'][0], 128), max(options['geometry'][1], 128) + ) self._spawn_interpreter(cmd, options, initial_prompt=r'php >') options.down() - self._drop_output() # discard banner and things like that + self._drop_output() # discard banner and things like that # change the prompts # the \b indicates which block PHP is in (/* for comments, php @@ -158,4 +163,4 @@ def shutdown(self): self._shutdown_interpreter() def cancel(self, example, options): - return False # not supported by php + return False # not supported by php diff --git a/byexample/modules/progress.py b/byexample/modules/progress.py index c72051ca..8295c372 100644 --- a/byexample/modules/progress.py +++ b/byexample/modules/progress.py @@ -11,9 +11,11 @@ stability = 'provisional' + class _DummyLock(object): def __enter__(self): return + def __exit__(self, *args): pass @@ -23,13 +25,14 @@ def acquire(self, *args, **kargs): def release(self, *args, **kargs): pass + class SimpleReporter(Concern): - target = None # progress + target = None # progress def __init__(self, verbosity, encoding, jobs, **unused): if 'use_progress_bar' in unused and unused['use_progress_bar'] \ and progress_bar_available: - self.target = None # disable ourselves + self.target = None # disable ourselves else: self.target = 'progress' @@ -72,17 +75,19 @@ def finish(self, failed, user_aborted, crashed, broken, timedout): msg = '\n' - elapsed = max(time.time() - self.begin, 0) + elapsed = max(time.time() - self.begin, 0) if elapsed < 300: elapsed_str = "%0.2f seconds" % elapsed elif elapsed < 3600: - elapsed_str = "%i minutes, %i seconds" % (elapsed / 60, - elapsed % 60) + elapsed_str = "%i minutes, %i seconds" % ( + elapsed / 60, elapsed % 60 + ) else: # if your examples run in terms of hours you may have # a real problem... I desire to you the best of the luck - elapsed_str = "%i hours, %i minutes" % ( elapsed / 3600, - (elapsed % 3600) / 60) + elapsed_str = "%i hours, %i minutes" % ( + elapsed / 3600, (elapsed % 3600) / 60 + ) ran_number = self.examplenro tot_number = self.num_examples @@ -94,11 +99,9 @@ def finish(self, failed, user_aborted, crashed, broken, timedout): status_str = colored("[PASS]", 'green', self.use_colors) msg += "File %s, %i/%i test ran in %s\n%s Pass: %i Fail: %i Skip: %i\n" % ( - self.filepath, - ran_number, tot_number, - elapsed_str, - status_str, - self.good, self.fail, self.skipped) + self.filepath, ran_number, tot_number, elapsed_str, status_str, + self.good, self.fail, self.skipped + ) self._write(msg) def skip_example(self, example, options): @@ -122,14 +125,16 @@ def timedout(self, example, exception): msg += self._error_header(example) msg += '%s Execution timedout at example %i of %i.\n' % ( - self._bullet('red'), - self.examplenro, self.num_examples) + self._bullet('red'), self.examplenro, self.num_examples + ) msg += 'This could be because the example just ran too slow (try add more time\n' + \ 'with +timeout=) or the example is "syntactically incorrect" and\n' + \ 'the interpreter hang (may be you forgot a parenthesis or something like that?).\n' if exception.output: - msg += 'This is the last output obtained:\n%s\n' % str(exception.output) + msg += 'This is the last output obtained:\n%s\n' % str( + exception.output + ) self._write(msg) self.fail += 1 @@ -142,8 +147,7 @@ def aborted(self, example, by_the_user, options): msg += 'Execution aborted ' if by_the_user: msg += 'by the user ' - msg += 'at example %i of %i.\n' % ( - self.examplenro, self.num_examples) + msg += 'at example %i of %i.\n' % (self.examplenro, self.num_examples) msg += 'Some resources may had not been cleaned.\n' self._write(msg) @@ -156,8 +160,8 @@ def crashed(self, example, exception): ex = '%s: %s' % (str(exception.__class__.__name__), str(exception)) msg += self._bullet('red') + ' ' msg += 'Execution of example %i of %i crashed.\n%s\n%s\n' % ( - self.examplenro, self.num_examples, - tb, ex) + self.examplenro, self.num_examples, tb, ex + ) self._write(msg) def start_parse(self, example, options): @@ -183,8 +187,8 @@ def finish_parse(self, example, options, exception): msg += self._bullet('red') + ' ' msg += 'Parse of example %i of %i failed.\n%s\n' % ( - self.examplenro, self.num_examples, - ex) + self.examplenro, self.num_examples, ex + ) self._write(msg) def finish_interact(self, exception): @@ -211,8 +215,9 @@ def failure(self, example, got, differ): msg = "\n" msg += self._error_header(example) - msg += differ.output_difference(example, got, self.current_merged_flags, - self.use_colors) + msg += differ.output_difference( + example, got, self.current_merged_flags, self.use_colors + ) msg += '\n' self._write(msg) @@ -224,7 +229,6 @@ def event(self, what, **data): self._write(data['msg'], nl=True) - def _error_header(self, example): if self.header_printed: return '' @@ -250,14 +254,15 @@ def _get_traceback(self, exception): else: return sys.exc_info()[2] + class ProgressBarReporter(SimpleReporter): - target = None # progress + target = None # progress def __init__(self, verbosity, encoding, jobs, **unused): SimpleReporter.__init__(self, verbosity, encoding, jobs, **unused) if ('use_progress_bar' in unused and not unused['use_progress_bar']) \ or not progress_bar_available: - self.target = None # disable ourselves + self.target = None # disable ourselves else: self.target = 'progress' @@ -274,10 +279,14 @@ def _clear_all_bars(self): if not hasattr(self.bar, 'fp'): return - for pos in range(1, self.jobs+1): + for pos in range(1, self.jobs + 1): self.bar.moveto(pos) - self.bar.fp.write('\r' + (' ' * self.bar.ncols)) # clear printing spaces - self.bar.fp.write('\r') # place cursor back at the beginning of line + self.bar.fp.write( + '\r' + (' ' * self.bar.ncols) + ) # clear printing spaces + self.bar.fp.write( + '\r' + ) # place cursor back at the beginning of line self.bar.moveto(-pos) def _write(self, msg, nl=False): @@ -304,16 +313,21 @@ def start(self, examples, runners, filepath, options): SimpleReporter.start(self, examples, runners, filepath, options) bar_format = '{desc} |{bar}| [{n_fmt}/{total_fmt}{postfix}]' - self.bar = tqdm(total=len(examples), file=self.output, - desc=filepath, leave=False, - bar_format=bar_format, - position=position, - disable=None # means disable if the output is not TTY - ) + self.bar = tqdm( + total=len(examples), + file=self.output, + desc=filepath, + leave=False, + bar_format=bar_format, + position=position, + disable=None # means disable if the output is not TTY + ) self.bar.set_lock(self.write_lock) def finish(self, failed, user_aborted, crashed, broken, timedout): - SimpleReporter.finish(self, failed, user_aborted, crashed, broken, timedout) + SimpleReporter.finish( + self, failed, user_aborted, crashed, broken, timedout + ) self.bar.close() self.bar = None @@ -324,4 +338,3 @@ def start_example(self, example, options): def skip_example(self, example, options): SimpleReporter.skip_example(self, example, options) self._update(1) - diff --git a/byexample/modules/python.py b/byexample/modules/python.py index e2a7a340..822ff759 100644 --- a/byexample/modules/python.py +++ b/byexample/modules/python.py @@ -32,12 +32,14 @@ stability = 'stable' + class PythonPromptFinder(ExampleFinder): target = 'python-prompt' @constant def example_regex(self): - return re.compile(r''' + return re.compile( + r''' # Snippet consists of a PS1 line >>> # followed by zero or more PS2 lines. (?P @@ -50,13 +52,16 @@ def example_regex(self): (?![ ]*>>>) # Not a line starting with PS1 .+$\n? # But any other line )*) - ''', re.MULTILINE | re.VERBOSE) + ''', re.MULTILINE | re.VERBOSE + ) def get_language_of(self, *args, **kargs): return 'python' def get_snippet_and_expected(self, match, where): - snippet, expected = ExampleFinder.get_snippet_and_expected(self, match, where) + snippet, expected = ExampleFinder.get_snippet_and_expected( + self, match, where + ) snippet = self._remove_prompts(snippet) return snippet, expected @@ -95,7 +100,6 @@ def _remove_prompts(self, snippet): # # I copied the license too for the records. - # Module doctest. # Released to the public domain 16-Jan-2001, by Tim Peters (tim@python.org). # Major enhancements and refactoring by: @@ -104,7 +108,6 @@ def _remove_prompts(self, snippet): # Provided as-is; use at your own risk; no warranty; no promises; enjoy! - # A regular expression for handling `want` strings that contain # expected exceptions. It divides `want` into three pieces: # - the traceback header line (`hdr`) @@ -114,7 +117,8 @@ def _remove_prompts(self, snippet): # `msg` may have multiple lines. We assume/require that the # exception message is the first non-indented line starting with a word # character following the traceback header line. -_EXCEPTION_RE = re.compile(r""" +_EXCEPTION_RE = re.compile( + r""" # Grab the traceback header. Different versions of Python have # said different things on the first traceback line. ^(?P Traceback\ \( @@ -125,7 +129,8 @@ def _remove_prompts(self, snippet): \s* $ # toss trailing whitespace on the header. (?P .*?) # don't blink: absorb stuff until... ^ (?P \w+ .*) # a line *starts* with alphanum. - """, re.VERBOSE | re.MULTILINE | re.DOTALL) + """, re.VERBOSE | re.MULTILINE | re.DOTALL +) # # @@ -135,6 +140,7 @@ def _remove_prompts(self, snippet): # : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : # ############################################################################### + def _example_options_string_regex_for(compatibility_mode): # anything of the form: if compatibility_mode: @@ -146,13 +152,14 @@ def _example_options_string_regex_for(compatibility_mode): # # byexample: +FOO -BAR +ZAZ=42 keyword = r'byexample' - return re.compile(r'#\s*%s:\s*([^\n\'"]*)$' % keyword, - re.MULTILINE) + return re.compile(r'#\s*%s:\s*([^\n\'"]*)$' % keyword, re.MULTILINE) + + class PythonParser(ExampleParser): language = 'python' # make this to cache the regexs - _blankline_tag_re = re.compile(r'^$', re.MULTILINE|re.DOTALL) + _blankline_tag_re = re.compile(r'^$', re.MULTILINE | re.DOTALL) _opts_re_for_noncomp = _example_options_string_regex_for(False) _opts_re_for_comp = _example_options_string_regex_for(True) @@ -169,20 +176,66 @@ def extend_option_parser(self, parser): Add a few extra options and if self.compatibility_mode is True, add all the Python doctest's options. ''' - parser.add_flag("py-doctest", default=False, help="enable the compatibility with doctest.") - parser.add_flag("py-pretty-print", default=True, help="enable the pretty print enhancement.") - parser.add_flag("py-remove-empty-lines", default=True, help="enable the deletion of empty lines (enabled by default).") + parser.add_flag( + "py-doctest", + default=False, + help="enable the compatibility with doctest." + ) + parser.add_flag( + "py-pretty-print", + default=True, + help="enable the pretty print enhancement." + ) + parser.add_flag( + "py-remove-empty-lines", + default=True, + help="enable the deletion of empty lines (enabled by default)." + ) if getattr(self, 'compatibility_mode', True): - parser.add_flag("NORMALIZE_WHITESPACE", default=False, help="[doctest] alias for +norm-ws.") - parser.add_flag("SKIP", default=False, help="[doctest] alias for +skip.") - parser.add_flag("ELLIPSIS", default=False, help="[doctest] enables the ... wildcard.") - parser.add_flag("DONT_ACCEPT_BLANKLINE", default=False, help="[doctest] take as literal.") - parser.add_flag("DONT_ACCEPT_TRUE_FOR_1", default=False, help="[doctest] ignored.") - parser.add_flag("IGNORE_EXCEPTION_DETAIL", default=False, help="[doctest] ignore the exception details.") - parser.add_flag("REPORT_UDIFF", default=False, help="[doctest] alias for +diff unified.") - parser.add_flag("REPORT_CDIFF", default=False, help="[doctest] alias for +diff context.") - parser.add_flag("REPORT_NDIFF", default=False, help="[doctest] alias for +diff ndiff.") + parser.add_flag( + "NORMALIZE_WHITESPACE", + default=False, + help="[doctest] alias for +norm-ws." + ) + parser.add_flag( + "SKIP", default=False, help="[doctest] alias for +skip." + ) + parser.add_flag( + "ELLIPSIS", + default=False, + help="[doctest] enables the ... wildcard." + ) + parser.add_flag( + "DONT_ACCEPT_BLANKLINE", + default=False, + help="[doctest] take as literal." + ) + parser.add_flag( + "DONT_ACCEPT_TRUE_FOR_1", + default=False, + help="[doctest] ignored." + ) + parser.add_flag( + "IGNORE_EXCEPTION_DETAIL", + default=False, + help="[doctest] ignore the exception details." + ) + parser.add_flag( + "REPORT_UDIFF", + default=False, + help="[doctest] alias for +diff unified." + ) + parser.add_flag( + "REPORT_CDIFF", + default=False, + help="[doctest] alias for +diff context." + ) + parser.add_flag( + "REPORT_NDIFF", + default=False, + help="[doctest] alias for +diff ndiff." + ) return parser @@ -198,12 +251,16 @@ def get_extended_option_parser(self, parent_parser, **kw): # fake the two compatibility mode (True and False) # and build an extended parser for each mode self.compatibility_mode = True - tmp[self.compatibility_mode] = ExtendOptionParserMixin.get_extended_option_parser( - self, parent_parser, **kw) + tmp[self.compatibility_mode + ] = ExtendOptionParserMixin.get_extended_option_parser( + self, parent_parser, **kw + ) self.compatibility_mode = False - tmp[self.compatibility_mode] = ExtendOptionParserMixin.get_extended_option_parser( - self, parent_parser, **kw) + tmp[self.compatibility_mode + ] = ExtendOptionParserMixin.get_extended_option_parser( + self, parent_parser, **kw + ) # restore the compatibility mode (even if it was unset) if original_compatibility_mode == None: @@ -304,19 +361,21 @@ def _double_parse(self, parse_method, args, kwargs): return options def extract_cmdline_options(self, opts_from_cmdline): - return self._double_parse(ExampleParser.extract_cmdline_options, - args=(self, opts_from_cmdline), - kwargs={}) - + return self._double_parse( + ExampleParser.extract_cmdline_options, + args=(self, opts_from_cmdline), + kwargs={} + ) def extract_options(self, snippet): - return self._double_parse(ExampleParser.extract_options, - args=(self, snippet), - kwargs={}) + return self._double_parse( + ExampleParser.extract_options, args=(self, snippet), kwargs={} + ) def process_snippet_and_expected(self, snippet, expected): - snippet, expected = ExampleParser.process_snippet_and_expected(self, - snippet, expected) + snippet, expected = ExampleParser.process_snippet_and_expected( + self, snippet, expected + ) expected = self._mutate_expected_based_on_doctest_flags(expected) snippet = self._remove_empty_line_if_enabled(snippet) @@ -335,7 +394,9 @@ def _mutate_expected_based_on_doctest_flags(self, expected_str): # contains strings like