From 5b60cde6d8046b9c6520ca2f7208b28b56535c18 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Thu, 2 Apr 2015 08:17:45 +0300 Subject: [PATCH 01/24] changed to python3 --- match.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/match.py b/match.py index 968743a..d5b6cb7 100644 --- a/match.py +++ b/match.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 From 759aa86c5f8ddef54ba30bd6bb0df48556a917a1 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Thu, 2 Apr 2015 18:20:35 +0300 Subject: [PATCH 02/24] universal_newlines=True added to test script --- test_match.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_match.py b/test_match.py index e09f772..df9db52 100644 --- a/test_match.py +++ b/test_match.py @@ -21,7 +21,7 @@ def call(*args, **kwargs): """ command = (sys.executable, os.path.join(HERE, 'match.py')) + args - p = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE) + p = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) out, err = p.communicate(kwargs.get('input')) return out, err, p.returncode From 51aca48811bf79496bc1fada912801f32418c49f Mon Sep 17 00:00:00 2001 From: codebicycle Date: Thu, 2 Apr 2015 18:26:56 +0300 Subject: [PATCH 03/24] all tests are green --- match.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/match.py b/match.py index d5b6cb7..4c2bbed 100644 --- a/match.py +++ b/match.py @@ -1,8 +1,34 @@ #!/usr/bin/env python3 - +import sys if __name__ == "__main__": - pass + try: + patern = sys.argv[1] + except: + exit(2) + + filenames = sys.argv[2:] + if len(filenames) < 1: + line = input() + if patern in line: + print(line) + else: + exit(1) + else: + match = False + for filename in filenames: + try: + f = open(filename) + except OSError: + exit(2) + else: + with f: + for line in f: + if patern in line: + match = True + print(line, end='') + if not match: + exit(1) From 148e0a23bd91cbc3533d44c3a5c46a471e97d8b7 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Fri, 3 Apr 2015 10:06:54 +0300 Subject: [PATCH 04/24] Revert "universal_newlines=True added to test script" This reverts commit 759aa86c5f8ddef54ba30bd6bb0df48556a917a1. --- test_match.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_match.py b/test_match.py index df9db52..e09f772 100644 --- a/test_match.py +++ b/test_match.py @@ -21,7 +21,7 @@ def call(*args, **kwargs): """ command = (sys.executable, os.path.join(HERE, 'match.py')) + args - p = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines=True) + p = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE) out, err = p.communicate(kwargs.get('input')) return out, err, p.returncode From 43824613b30c8fd81b68d8810243c660bea2b717 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 8 Apr 2015 14:56:59 +0300 Subject: [PATCH 05/24] fixed missing comma in test script --- test_match.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_match.py b/test_match.py index 2da4dcb..edc6d1a 100644 --- a/test_match.py +++ b/test_match.py @@ -84,7 +84,7 @@ def test_only_matching(): a matching line, with each such part on a separate output line. """ - assert call('-o', '' input='zero') == ('', '', 0) + assert call('-o', '', input='zero') == ('', '', 0) assert call('-o', 'e', input='one') == ('e\n', '', 0) assert call('-o', 'e', input='two') == ('', '', 1) assert call('-o', 'e', input='three') == ('e\ne\n', '', 0) From 0bc775b94c86c32023439de13a3abb13def5cfca Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 8 Apr 2015 16:05:18 +0300 Subject: [PATCH 06/24] newlines test passes. Reads text from sys.stdin --- match.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/match.py b/match.py index 4c2bbed..673e952 100644 --- a/match.py +++ b/match.py @@ -12,10 +12,12 @@ filenames = sys.argv[2:] if len(filenames) < 1: - line = input() - if patern in line: - print(line) - else: + match = False + for line in sys.stdin: + if patern in line: + match = True + print(line.rstrip('\n')) + if not match: exit(1) else: match = False From cd59cf2d8574e1a0668edb9219c9f60203295f02 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 8 Apr 2015 16:26:11 +0300 Subject: [PATCH 07/24] reads bytes --- match.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/match.py b/match.py index 673e952..70a061f 100644 --- a/match.py +++ b/match.py @@ -5,25 +5,25 @@ if __name__ == "__main__": try: - patern = sys.argv[1] - except: + patern = sys.argv[1].encode('utf-8') + except IndexError: exit(2) filenames = sys.argv[2:] if len(filenames) < 1: match = False - for line in sys.stdin: + for line in sys.stdin.buffer: if patern in line: match = True - print(line.rstrip('\n')) + print(line.decode().rstrip('\n')) if not match: exit(1) else: match = False for filename in filenames: try: - f = open(filename) + f = open(filename, mode="rb") except OSError: exit(2) else: @@ -31,6 +31,6 @@ for line in f: if patern in line: match = True - print(line, end='') + print(line.decode().rstrip('\n')) if not match: exit(1) From 8649cf43d48ba0c9ec0c3e7038f11417402a7db8 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 8 Apr 2015 20:00:57 +0300 Subject: [PATCH 08/24] small refactoring 1. separate function is_matched() 2. pattern spelled correctly --- match.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/match.py b/match.py index 70a061f..e2ad104 100644 --- a/match.py +++ b/match.py @@ -2,25 +2,30 @@ import sys +def is_matched(pattern, file): + matched = False + for line in file: + if pattern in line: + matched = True + print(line.decode().rstrip('\n')) + + return matched + + if __name__ == "__main__": try: - patern = sys.argv[1].encode('utf-8') + pattern = sys.argv[1].encode('utf-8') except IndexError: exit(2) filenames = sys.argv[2:] if len(filenames) < 1: - match = False - for line in sys.stdin.buffer: - if patern in line: - match = True - print(line.decode().rstrip('\n')) - if not match: + matched = is_matched(pattern, sys.stdin.buffer) + if not matched: exit(1) else: - match = False for filename in filenames: try: f = open(filename, mode="rb") @@ -28,9 +33,6 @@ exit(2) else: with f: - for line in f: - if patern in line: - match = True - print(line.decode().rstrip('\n')) - if not match: + matched = is_matched(pattern, f) + if not matched: exit(1) From 5d56fd36aa3f3cb60cb7d0fb24c92fb818fc1a19 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 8 Apr 2015 22:03:42 +0300 Subject: [PATCH 09/24] test only_matching passes. Naive command line options read --- match.py | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/match.py b/match.py index e2ad104..d64ecbe 100644 --- a/match.py +++ b/match.py @@ -2,27 +2,57 @@ import sys -def is_matched(pattern, file): +def opts_and_args(argv, supported_options): + args = argv[1:] + active_options = [opt for opt in supported_options if opt in args] + + for opt in active_options: + while opt in args: + args.remove(opt) + + return(active_options, args) + + +def is_matched(pattern, file, output_cb): matched = False for line in file: if pattern in line: matched = True - print(line.decode().rstrip('\n')) + output_cb(pattern, line) return matched +def default_output(pattern, line): + print(line.decode().rstrip('\n')) + + +def only_matching_output(pattern, line): + if not pattern: + return + for i in range(line.count(pattern)): + print(pattern.decode()) + + if __name__ == "__main__": + SUPPORTED_OPTIONS = ['-o', '--only-matching'] + options, args = opts_and_args(sys.argv, SUPPORTED_OPTIONS) + try: - pattern = sys.argv[1].encode('utf-8') + pattern = args[0].encode('utf-8') except IndexError: exit(2) - filenames = sys.argv[2:] + filenames = args[1:] + + if '-o' in options or '--only-matching' in options: + cb = only_matching_output + else: + cb = default_output if len(filenames) < 1: - matched = is_matched(pattern, sys.stdin.buffer) + matched = is_matched(pattern, sys.stdin.buffer, cb) if not matched: exit(1) else: @@ -33,6 +63,6 @@ def is_matched(pattern, file): exit(2) else: with f: - matched = is_matched(pattern, f) + matched = is_matched(pattern, f, cb) if not matched: exit(1) From 9fbba9548b48296bdc13652404cf547a6f07cecb Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 6 May 2015 19:18:14 +0300 Subject: [PATCH 10/24] Rewrite, parse args with click --- match.py | 113 ++++++++++++++++++++++++++----------------------------- 1 file changed, 54 insertions(+), 59 deletions(-) diff --git a/match.py b/match.py index d64ecbe..de7fbb4 100644 --- a/match.py +++ b/match.py @@ -1,68 +1,63 @@ #!/usr/bin/env python3 import sys +import click + + +def iter_files(paths): + for path in paths: + try: + yield open(path, 'rb') + except (IOError, OSError) as e: + print("error: {}".format(e), file=sys.stderr) + + +@click.command() +@click.option( + '-o', '--only-matching', is_flag=True, + help=('Print only the matched (non-empty) parts of a matching line, ' + 'with each such part on a separate output line.') +) +@click.argument('pattern') +@click.argument('file', nargs=-1, type=click.Path(exists=True)) +def main(pattern, file, **kwargs): + """Search for PATTERN in each FILE or standard input. + + """ + # print(kwargs) + # print('pattern:', pattern) + # print('file:', file) + + def output(): + if kwargs['only_matching']: + if not pattern: + return + out = b'' + for i in range(line.count(pattern)): + out += pattern + b'\n' + else: + out = line.rstrip(b'\n') + b'\n' + sys.stdout.buffer.write(out) + + + pattern = pattern.encode('utf8') + + if file: + files = iter_files(file) + else: + files = (sys.stdin.buffer,) -def opts_and_args(argv, supported_options): - args = argv[1:] - active_options = [opt for opt in supported_options if opt in args] - - for opt in active_options: - while opt in args: - args.remove(opt) - - return(active_options, args) - - -def is_matched(pattern, file, output_cb): matched = False - for line in file: - if pattern in line: - matched = True - output_cb(pattern, line) - - return matched - - -def default_output(pattern, line): - print(line.decode().rstrip('\n')) - - -def only_matching_output(pattern, line): - if not pattern: - return - for i in range(line.count(pattern)): - print(pattern.decode()) + for f in files: + for line in f: + if pattern in line: + output() + matched = True + f.close() + if not matched: + sys.exit(1) if __name__ == "__main__": - SUPPORTED_OPTIONS = ['-o', '--only-matching'] - options, args = opts_and_args(sys.argv, SUPPORTED_OPTIONS) - - try: - pattern = args[0].encode('utf-8') - except IndexError: - exit(2) - - filenames = args[1:] - - if '-o' in options or '--only-matching' in options: - cb = only_matching_output - else: - cb = default_output - - if len(filenames) < 1: - matched = is_matched(pattern, sys.stdin.buffer, cb) - if not matched: - exit(1) - else: - for filename in filenames: - try: - f = open(filename, mode="rb") - except OSError: - exit(2) - else: - with f: - matched = is_matched(pattern, f, cb) - if not matched: - exit(1) + main() From 6c95b1f08f3d33ed198189d82726359d5871e688 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 6 May 2015 19:22:02 +0300 Subject: [PATCH 11/24] Add .fuse_hiddenXXXX files to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index ba74660..12d1bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,5 @@ docs/_build/ # PyBuilder target/ + +.fuse_hidden* From de2ab6e89dfbadbc447cc5e36aefe0db51cadb0e Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 6 May 2015 20:10:52 +0300 Subject: [PATCH 12/24] File name output prefix --- match.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/match.py b/match.py index de7fbb4..4fdb9db 100644 --- a/match.py +++ b/match.py @@ -29,14 +29,19 @@ def main(pattern, file, **kwargs): # print('file:', file) def output(): + filename = b'' + if len(file) > 1: + filename = '{}:'.format(f.name).encode('utf8') + if kwargs['only_matching']: if not pattern: return out = b'' for i in range(line.count(pattern)): - out += pattern + b'\n' + out += filename + pattern + b'\n' else: - out = line.rstrip(b'\n') + b'\n' + out = filename + line.rstrip(b'\n') + b'\n' + sys.stdout.buffer.write(out) From 8b8d6a2a33ac26b1320c91f995e91d983381227f Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 6 May 2015 20:34:53 +0300 Subject: [PATCH 13/24] Add option -h --no-filename --- match.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/match.py b/match.py index 4fdb9db..370a0da 100644 --- a/match.py +++ b/match.py @@ -15,8 +15,11 @@ def iter_files(paths): @click.command() @click.option( '-o', '--only-matching', is_flag=True, - help=('Print only the matched (non-empty) parts of a matching line, ' - 'with each such part on a separate output line.') + help=('show only the part of a line matching PATTERN') +) +@click.option( + '-h', '--no-filename', is_flag=True, + help=('suppress the file name prefix on output') ) @click.argument('pattern') @click.argument('file', nargs=-1, type=click.Path(exists=True)) @@ -29,8 +32,9 @@ def main(pattern, file, **kwargs): # print('file:', file) def output(): - filename = b'' - if len(file) > 1: + if kwargs['no_filename'] or len(file) <= 1: + filename = b'' + else: filename = '{}:'.format(f.name).encode('utf8') if kwargs['only_matching']: From f37aea47b11e004a9506b27c1b63657d4e5ebf30 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 6 May 2015 20:53:56 +0300 Subject: [PATCH 14/24] Fix docstring to work with click The last empty line from the old docstring caused an error in click when trying ./match.py --help --- match.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/match.py b/match.py index 370a0da..197cb34 100644 --- a/match.py +++ b/match.py @@ -24,9 +24,7 @@ def iter_files(paths): @click.argument('pattern') @click.argument('file', nargs=-1, type=click.Path(exists=True)) def main(pattern, file, **kwargs): - """Search for PATTERN in each FILE or standard input. - - """ + '''Search for PATTERN in each FILE or standard input.''' # print(kwargs) # print('pattern:', pattern) # print('file:', file) From 3e4cb5bf3c8f4369740e9084ef1f1f17209a2f7d Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 6 May 2015 21:54:36 +0300 Subject: [PATCH 15/24] Add option -v --invert-match --- match.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/match.py b/match.py index 197cb34..544641b 100644 --- a/match.py +++ b/match.py @@ -21,6 +21,10 @@ def iter_files(paths): '-h', '--no-filename', is_flag=True, help=('suppress the file name prefix on output') ) +@click.option( + '-v', '--invert-match', is_flag=True, + help=('select non-matching lines') +) @click.argument('pattern') @click.argument('file', nargs=-1, type=click.Path(exists=True)) def main(pattern, file, **kwargs): @@ -57,7 +61,11 @@ def output(): matched = False for f in files: for line in f: - if pattern in line: + if kwargs['invert_match']: + if pattern not in line: + sys.stdout.buffer.write(line.rstrip(b'\n') + b'\n') + matched = True + elif pattern in line: output() matched = True f.close() From 70a5900fd8b9a9618069a1270c0f46b7aa909396 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Wed, 6 May 2015 22:56:21 +0300 Subject: [PATCH 16/24] Add option -i --ignore-case --- match.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/match.py b/match.py index 544641b..acff74c 100644 --- a/match.py +++ b/match.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import sys +import re import click @@ -25,6 +26,10 @@ def iter_files(paths): '-v', '--invert-match', is_flag=True, help=('select non-matching lines') ) +@click.option( + '-i', '--ignore-case', is_flag=True, + help=('ignore case distinctions') +) @click.argument('pattern') @click.argument('file', nargs=-1, type=click.Path(exists=True)) def main(pattern, file, **kwargs): @@ -52,6 +57,7 @@ def output(): pattern = pattern.encode('utf8') + flag = re.IGNORECASE if kwargs['ignore_case'] else 0 if file: files = iter_files(file) @@ -61,11 +67,12 @@ def output(): matched = False for f in files: for line in f: + search = re.search(pattern, line, flag) if kwargs['invert_match']: - if pattern not in line: + if not search: sys.stdout.buffer.write(line.rstrip(b'\n') + b'\n') matched = True - elif pattern in line: + elif search: output() matched = True f.close() From 49af69482d7e780c7bd2ee1fb80faf425f210b11 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Thu, 7 May 2015 09:37:22 +0300 Subject: [PATCH 17/24] Refactor just a bit --- match.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/match.py b/match.py index acff74c..294ed34 100644 --- a/match.py +++ b/match.py @@ -45,15 +45,13 @@ def output(): filename = '{}:'.format(f.name).encode('utf8') if kwargs['only_matching']: - if not pattern: - return - out = b'' - for i in range(line.count(pattern)): - out += filename + pattern + b'\n' + if pattern: + for m in re.findall(pattern, line, flag): + out = filename + m + b'\n' + sys.stdout.buffer.write(out) else: out = filename + line.rstrip(b'\n') + b'\n' - - sys.stdout.buffer.write(out) + sys.stdout.buffer.write(out) pattern = pattern.encode('utf8') @@ -67,12 +65,12 @@ def output(): matched = False for f in files: for line in f: - search = re.search(pattern, line, flag) + match = re.search(pattern, line, flag) if kwargs['invert_match']: - if not search: - sys.stdout.buffer.write(line.rstrip(b'\n') + b'\n') + if not match: + output() matched = True - elif search: + elif match: output() matched = True f.close() From 6fed31409c8b805839d33a4c749b2487dd53200e Mon Sep 17 00:00:00 2001 From: codebicycle Date: Sat, 9 May 2015 11:47:48 +0300 Subject: [PATCH 18/24] Add option -e --regexp --- match.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 17 deletions(-) diff --git a/match.py b/match.py index 294ed34..613ccac 100644 --- a/match.py +++ b/match.py @@ -30,23 +30,76 @@ def iter_files(paths): '-i', '--ignore-case', is_flag=True, help=('ignore case distinctions') ) -@click.argument('pattern') +@click.option( + '-e', '--regexp', + help=('use PATTERN for matching') +) +@click.argument('pattern', required=False) @click.argument('file', nargs=-1, type=click.Path(exists=True)) -def main(pattern, file, **kwargs): +def match(pattern, file, **kwargs): '''Search for PATTERN in each FILE or standard input.''' - # print(kwargs) - # print('pattern:', pattern) - # print('file:', file) + # print('match: pattern:', pattern) + # print('match: file:', file) + # print('match: kwargs:', kwargs) + + if kwargs['regexp']: + match_regexp() + else: + match_helper(pattern, file, **kwargs) + + +@click.command() +@click.option( + '-o', '--only-matching', is_flag=True, + help=('show only the part of a line matching PATTERN') +) +@click.option( + '-h', '--no-filename', is_flag=True, + help=('suppress the file name prefix on output') +) +@click.option( + '-v', '--invert-match', is_flag=True, + help=('select non-matching lines') +) +@click.option( + '-i', '--ignore-case', is_flag=True, + help=('ignore case distinctions') +) +@click.option( + '-e', '--regexp', multiple=True, + help=('use PATTERN for matching') +) +@click.argument('file', nargs=-1, type=click.Path(exists=True)) +def match_regexp(file, **kwargs): + # print('match-regexp: file:', file) + # print('match-regexp: kwargs:', kwargs) + + pattern = kwargs['regexp'] + match_helper(pattern, file, **kwargs) + + +def match_helper(pattern, file, **kwargs): + # print('match_helper: pattern:', pattern) + # print('match_helper: file:', file) + # print('match_helper: kwargs:', kwargs) + + if pattern is None: + sys.exit(2) + + opt_no_filename = kwargs.get('no_filename') + opt_only_matching = kwargs.get('only_matching') + opt_ignore_case = kwargs.get('ignore_case') + opt_invert_match = kwargs.get('invert_match') def output(): - if kwargs['no_filename'] or len(file) <= 1: + if opt_no_filename or len(file) <= 1: filename = b'' else: filename = '{}:'.format(f.name).encode('utf8') - if kwargs['only_matching']: - if pattern: - for m in re.findall(pattern, line, flag): + if opt_only_matching: + if pat: + for m in re.findall(pat, line, flag): out = filename + m + b'\n' sys.stdout.buffer.write(out) else: @@ -54,30 +107,39 @@ def output(): sys.stdout.buffer.write(out) - pattern = pattern.encode('utf8') - flag = re.IGNORECASE if kwargs['ignore_case'] else 0 + if type(pattern) is tuple: + patterns = [pat.encode('utf8') for pat in pattern[::-1]] + else: + patterns = [pattern.encode('utf8')] if file: files = iter_files(file) else: files = (sys.stdin.buffer,) + flag = re.IGNORECASE if opt_ignore_case else 0 + matched = False for f in files: for line in f: - match = re.search(pattern, line, flag) - if kwargs['invert_match']: - if not match: + for pat in patterns: + found = re.search(pat, line, flag) + if opt_invert_match: + if not found: + output() + matched = True + elif found: output() matched = True - elif match: - output() - matched = True f.close() if not matched: sys.exit(1) +def main(): + match() + + if __name__ == "__main__": main() From ad5247c545ff69406b36536f3846d704b5f74f53 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Sat, 9 May 2015 14:59:14 +0300 Subject: [PATCH 19/24] Group common options' decorators Order short options alphabetically --- match.py | 75 ++++++++++++++++++++++++-------------------------------- 1 file changed, 32 insertions(+), 43 deletions(-) diff --git a/match.py b/match.py index 613ccac..0c84690 100644 --- a/match.py +++ b/match.py @@ -13,29 +13,38 @@ def iter_files(paths): print("error: {}".format(e), file=sys.stderr) -@click.command() -@click.option( - '-o', '--only-matching', is_flag=True, - help=('show only the part of a line matching PATTERN') -) -@click.option( - '-h', '--no-filename', is_flag=True, - help=('suppress the file name prefix on output') -) -@click.option( - '-v', '--invert-match', is_flag=True, - help=('select non-matching lines') -) -@click.option( - '-i', '--ignore-case', is_flag=True, - help=('ignore case distinctions') -) -@click.option( - '-e', '--regexp', - help=('use PATTERN for matching') -) -@click.argument('pattern', required=False) +def common_options(f): + @click.command() + @click.option( + '-e', '--regexp', multiple=True, metavar='PATTERN', + help=('use PATTERN for matching') + ) + @click.option( + '-h', '--no-filename', is_flag=True, + help=('suppress the file name prefix on output') + ) + @click.option( + '-i', '--ignore-case', is_flag=True, + help=('ignore case distinctions') + ) + @click.option( + '-o', '--only-matching', is_flag=True, + help=('show only the part of a line matching PATTERN') + ) + @click.option( + '-v', '--invert-match', is_flag=True, + help=('select non-matching lines') + ) + def wrapper(**kwargs): + '''Search for PATTERN in each FILE or standard input.''' + f(**kwargs) + + return wrapper + + @click.argument('file', nargs=-1, type=click.Path(exists=True)) +@click.argument('pattern', required=False) +@common_options def match(pattern, file, **kwargs): '''Search for PATTERN in each FILE or standard input.''' # print('match: pattern:', pattern) @@ -48,28 +57,8 @@ def match(pattern, file, **kwargs): match_helper(pattern, file, **kwargs) -@click.command() -@click.option( - '-o', '--only-matching', is_flag=True, - help=('show only the part of a line matching PATTERN') -) -@click.option( - '-h', '--no-filename', is_flag=True, - help=('suppress the file name prefix on output') -) -@click.option( - '-v', '--invert-match', is_flag=True, - help=('select non-matching lines') -) -@click.option( - '-i', '--ignore-case', is_flag=True, - help=('ignore case distinctions') -) -@click.option( - '-e', '--regexp', multiple=True, - help=('use PATTERN for matching') -) @click.argument('file', nargs=-1, type=click.Path(exists=True)) +@common_options def match_regexp(file, **kwargs): # print('match-regexp: file:', file) # print('match-regexp: kwargs:', kwargs) From cfd3f39c993c0bf722eda6e1db0de832fbe8545d Mon Sep 17 00:00:00 2001 From: codebicycle Date: Sun, 30 Oct 2016 10:11:45 +0200 Subject: [PATCH 20/24] Add dependencies to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index c0cdcff..4549689 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ Fill in the gaps to make the tests pass (use pytest to run them). + +### Dependencies + + pip install click From 4045330c02ae217975d32e9baa488d7b68184355 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Sun, 30 Oct 2016 11:45:20 +0200 Subject: [PATCH 21/24] Replace debugging print statements with pprint locals() --- match.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/match.py b/match.py index 3e605e4..a3f88d8 100644 --- a/match.py +++ b/match.py @@ -3,6 +3,7 @@ import sys import re import click +from pprint import pprint def iter_files(paths): @@ -47,9 +48,8 @@ def wrapper(**kwargs): @common_options def match(pattern, file, **kwargs): '''Search for PATTERN in each FILE or standard input.''' - # print('match: pattern:', pattern) - # print('match: file:', file) - # print('match: kwargs:', kwargs) + # print('match:') + # pprint(locals()) if kwargs['regexp']: match_regexp() @@ -60,17 +60,16 @@ def match(pattern, file, **kwargs): @click.argument('file', nargs=-1, type=click.Path(exists=True)) @common_options def match_regexp(file, **kwargs): - # print('match-regexp: file:', file) - # print('match-regexp: kwargs:', kwargs) + # print('match-regexp:') + # pprint(locals()) pattern = kwargs['regexp'] match_helper(pattern, file, **kwargs) def match_helper(pattern, file, **kwargs): - # print('match_helper: pattern:', pattern) - # print('match_helper: file:', file) - # print('match_helper: kwargs:', kwargs) + # print('match_helper:') + # pprint(locals()) if pattern is None: sys.exit(2) From 30c043db9c59b004f393d35147904c2b8be267d1 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Mon, 31 Oct 2016 10:53:16 +0200 Subject: [PATCH 22/24] Pass iterator tests --- iterator.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/iterator.py b/iterator.py index b4c0c5f..bee3822 100644 --- a/iterator.py +++ b/iterator.py @@ -21,6 +21,39 @@ """ +class SquareIterator: + + def __init__(self, iterable): + self.iterable = iterable + self.position = 0 + + def __iter__(self): + return self + + def __next__(self): + try: + item = self.iterable[self.position] + except IndexError: + raise StopIteration + + self.position += 1 + return item * item + + +class FibIterator: + + def __init__(self): + self.preprev = 0 + self.prev = 1 + + def __iter__(self): + return self + + def __next__(self): + fib = self.prev + self.preprev, self.prev = self.prev, self.preprev + self.prev + return fib + def squares_generator(numbers): """Given an iterable of numbers, return an iterator that returns their @@ -47,6 +80,8 @@ def squares_iterator_protocol(numbers): # More specifically, return an object conforming to the iterator protocol # that is not a generator iterator. + return SquareIterator(numbers) + def fibonacci_generator(): """Return an iterator that returns all the Fibonacci numbers, forever.""" @@ -63,4 +98,4 @@ def fibonacci_iterator_protocol(): # More specifically, return an object conforming to the iterator protocol # that is not a generator iterator. - + return FibIterator() From e93d048c6324e0df05ced95d5d58a9e79bc98fbc Mon Sep 17 00:00:00 2001 From: codebicycle Date: Mon, 31 Oct 2016 14:26:39 +0200 Subject: [PATCH 23/24] Refactor fibonacci iterator to resemble fibonacci generator --- iterator.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/iterator.py b/iterator.py index bee3822..dda045b 100644 --- a/iterator.py +++ b/iterator.py @@ -40,19 +40,19 @@ def __next__(self): return item * item -class FibIterator: +class FibonacciIterator: def __init__(self): - self.preprev = 0 - self.prev = 1 + self.a = 1 + self.b = 1 def __iter__(self): return self def __next__(self): - fib = self.prev - self.preprev, self.prev = self.prev, self.preprev + self.prev - return fib + fibonacci = self.a + self.a, self.b = self.b, self.a + self.b + return fibonacci def squares_generator(numbers): @@ -98,4 +98,4 @@ def fibonacci_iterator_protocol(): # More specifically, return an object conforming to the iterator protocol # that is not a generator iterator. - return FibIterator() + return FibonacciIterator() From ece4060834f863a1e16d7bc328ddb606794d86e9 Mon Sep 17 00:00:00 2001 From: codebicycle Date: Mon, 31 Oct 2016 16:54:19 +0200 Subject: [PATCH 24/24] Refactor square iterator --- iterator.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/iterator.py b/iterator.py index dda045b..88070f1 100644 --- a/iterator.py +++ b/iterator.py @@ -24,20 +24,13 @@ class SquareIterator: def __init__(self, iterable): - self.iterable = iterable - self.position = 0 + self.iterator = iter(iterable) def __iter__(self): return self def __next__(self): - try: - item = self.iterable[self.position] - except IndexError: - raise StopIteration - - self.position += 1 - return item * item + return next(self.iterator) ** 2 class FibonacciIterator: