Skip to content
This repository was archived by the owner on Aug 17, 2023. It is now read-only.

Commit cc7695b

Browse files
authored
Merge pull request #31 from Psycojoker/watcher_testing
Fix PEP issues in hamlpy_watcher and add tests
2 parents 13dbeeb + ad0feee commit cc7695b

File tree

3 files changed

+122
-24
lines changed

3 files changed

+122
-24
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
.DS_Store
77
.idea/
88
.tox/
9+
.watcher_test/
910
build/
1011
htmlcov/
1112
env/

hamlpy/hamlpy_watcher.py

+33-24
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@
1515
from . import hamlpy
1616
from . import nodes as hamlpynodes
1717

18-
try:
19-
str = unicode
20-
except NameError:
21-
pass
2218

2319
class Options(object):
2420
CHECK_INTERVAL = 3 # in seconds
@@ -29,8 +25,9 @@ class Options(object):
2925
# dict of compiled files [fullpath : timestamp]
3026
compiled = dict()
3127

28+
3229
class StoreNameValueTagPair(argparse.Action):
33-
def __call__(self, parser, namespace, values, option_string = None):
30+
def __call__(self, parser, namespace, values, option_string=None):
3431
tags = getattr(namespace, 'tags', {})
3532
if tags is None:
3633
tags = {}
@@ -41,16 +38,24 @@ def __call__(self, parser, namespace, values, option_string = None):
4138
setattr(namespace, 'tags', tags)
4239

4340
arg_parser = argparse.ArgumentParser()
44-
arg_parser.add_argument('-v', '--verbose', help = 'Display verbose output', action = 'store_true')
45-
arg_parser.add_argument('-i', '--input-extension', metavar = 'EXT', default = '.hamlpy', help = 'The file extensions to look for.', type = str, nargs = '+')
46-
arg_parser.add_argument('-ext', '--extension', metavar = 'EXT', default = Options.OUTPUT_EXT, help = 'The output file extension. Default is .html', type = str)
47-
arg_parser.add_argument('-r', '--refresh', metavar = 'S', default = Options.CHECK_INTERVAL, help = 'Refresh interval for files. Default is {} seconds. Ignored if the --once flag is set.'.format(Options.CHECK_INTERVAL), type = int)
48-
arg_parser.add_argument('input_dir', help = 'Folder to watch', type = str)
49-
arg_parser.add_argument('output_dir', help = 'Destination folder', type = str, nargs = '?')
50-
arg_parser.add_argument('--tag', help = 'Add self closing tag. eg. --tag macro:endmacro', type = str, nargs = 1, action = StoreNameValueTagPair)
51-
arg_parser.add_argument('--attr-wrapper', dest = 'attr_wrapper', type = str, choices = ('"', "'"), default = "'", action = 'store', help = "The character that should wrap element attributes. This defaults to ' (an apostrophe).")
52-
arg_parser.add_argument('--jinja', help = 'Makes the necessary changes to be used with Jinja2.', default = False, action = 'store_true')
53-
arg_parser.add_argument('--once', help = 'Runs the compiler once and exits on completion. Returns a non-zero exit code if there were any compile errors.', default = False, action = 'store_true')
41+
arg_parser.add_argument('-v', '--verbose', help='Display verbose output', action='store_true')
42+
arg_parser.add_argument('-i', '--input-extension', metavar='EXT', default='.hamlpy',
43+
help='The file extensions to look for.', type=str, nargs='+')
44+
arg_parser.add_argument('-ext', '--extension', metavar='EXT', default=Options.OUTPUT_EXT,
45+
help='The output file extension. Default is .html', type=str)
46+
arg_parser.add_argument('-r', '--refresh', metavar='S', default=Options.CHECK_INTERVAL, type=int,
47+
help='Refresh interval for files. Default is {} seconds. Ignored if the --once flag is set.'.format(Options.CHECK_INTERVAL))
48+
arg_parser.add_argument('input_dir', help='Folder to watch', type=str)
49+
arg_parser.add_argument('output_dir', help='Destination folder', type=str, nargs='?')
50+
arg_parser.add_argument('--tag', type=str, nargs=1, action=StoreNameValueTagPair,
51+
help='Add self closing tag. eg. --tag macro:endmacro')
52+
arg_parser.add_argument('--attr-wrapper', dest='attr_wrapper', type=str, choices=('"', "'"), default="'", action='store',
53+
help="The character that should wrap element attributes. This defaults to ' (an apostrophe).")
54+
arg_parser.add_argument('--jinja', default=False, action='store_true',
55+
help='Makes the necessary changes to be used with Jinja2.')
56+
arg_parser.add_argument('--once', default=False, action='store_true',
57+
help='Runs the compiler once and exits on completion. Returns a non-zero exit code if there were any compile errors.')
58+
5459

5560
def watched_extension(extension):
5661
"""Return True if the given extension is one of the watched extensions"""
@@ -59,9 +64,9 @@ def watched_extension(extension):
5964
return True
6065
return False
6166

67+
6268
def watch_folder():
6369
"""Main entry point. Expects one or two arguments (the watch folder + optional destination folder)."""
64-
argv = sys.argv[1:] if len(sys.argv) > 1 else []
6570
args = arg_parser.parse_args(sys.argv[1:])
6671
compiler_args = {}
6772

@@ -95,9 +100,9 @@ def watch_folder():
95100
hamlpynodes.TagNode.may_contain.pop(k, None)
96101

97102
hamlpynodes.TagNode.self_closing.update({
98-
'macro' : 'endmacro',
99-
'call' : 'endcall',
100-
'raw' : 'endraw'
103+
'macro': 'endmacro',
104+
'call': 'endcall',
105+
'raw': 'endraw'
101106
})
102107

103108
hamlpynodes.TagNode.may_contain['for'] = 'else'
@@ -120,6 +125,7 @@ def watch_folder():
120125
# allow graceful exit (no stacktrace output)
121126
sys.exit(0)
122127

128+
123129
def _watch_folder(folder, destination, compiler_args):
124130
"""Compares "modified" timestamps against the "compiled" dict, calls compiler
125131
if necessary. Returns a tuple of the number of files hit and the number
@@ -140,17 +146,19 @@ def _watch_folder(folder, destination, compiler_args):
140146
os.makedirs(compiled_folder)
141147

142148
compiled_path = _compiled_path(compiled_folder, filename)
143-
if not fullpath in compiled or compiled[fullpath] < mtime or not os.path.isfile(compiled_path):
149+
if fullpath not in compiled or compiled[fullpath] < mtime or not os.path.isfile(compiled_path):
144150
compiled[fullpath] = mtime
145151
total_files += 1
146152
if not compile_file(fullpath, compiled_path, compiler_args):
147153
num_failed += 1
148154

149-
return (total_files, num_failed)
155+
return total_files, num_failed
156+
150157

151158
def _compiled_path(destination, filename):
152159
return os.path.join(destination, filename[:filename.rfind('.')] + Options.OUTPUT_EXT)
153160

161+
154162
def compile_file(fullpath, outfile_name, compiler_args):
155163
"""Calls HamlPy compiler. Returns True if the file was compiled and
156164
written successfully."""
@@ -159,10 +167,10 @@ def compile_file(fullpath, outfile_name, compiler_args):
159167
try:
160168
if Options.DEBUG:
161169
print("Compiling %s -> %s" % (fullpath, outfile_name))
162-
haml_lines = codecs.open(fullpath, 'r', encoding = 'utf-8').read().splitlines()
170+
haml_lines = codecs.open(fullpath, 'r', encoding='utf-8').read().splitlines()
163171
compiler = hamlpy.Compiler(compiler_args)
164172
output = compiler.process_lines(haml_lines)
165-
outfile = codecs.open(outfile_name, 'w', encoding = 'utf-8')
173+
outfile = codecs.open(outfile_name, 'w', encoding='utf-8')
166174
outfile.write(output)
167175

168176
return True
@@ -173,5 +181,6 @@ def compile_file(fullpath, outfile_name, compiler_args):
173181

174182
return False
175183

176-
if __name__ == '__main__':
184+
185+
if __name__ == '__main__': # pragma: no cover
177186
watch_folder()

hamlpy/test/test_watcher.py

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from __future__ import print_function, unicode_literals
2+
3+
import os
4+
import shutil
5+
import sys
6+
import time
7+
import unittest
8+
9+
from mock import patch
10+
11+
from hamlpy.hamlpy_watcher import watch_folder
12+
13+
14+
WORKING_DIR = '.watcher_test'
15+
INPUT_DIR = WORKING_DIR + os.sep + 'input'
16+
OUTPUT_DIR = WORKING_DIR + os.sep + 'output'
17+
18+
19+
class ScriptExit(Exception):
20+
def __init__(self, exit_code):
21+
self.exit_code = exit_code
22+
23+
24+
class WatcherTest(unittest.TestCase):
25+
26+
def test_watch_folder(self):
27+
# remove working directory if it exists and re-create it
28+
if os.path.exists(WORKING_DIR):
29+
shutil.rmtree(WORKING_DIR)
30+
31+
os.makedirs(INPUT_DIR)
32+
os.makedirs(OUTPUT_DIR)
33+
34+
# create some haml files for testing
35+
self._write_file(INPUT_DIR + os.sep + 'test.haml', "%span{'class': 'test'}\n- macro\n")
36+
self._write_file(INPUT_DIR + os.sep + 'error.haml', "%div{")
37+
38+
# run as once off pass - should return 1 for number of failed conversions
39+
self._run_script([
40+
'hamlpy_watcher.py',
41+
INPUT_DIR, OUTPUT_DIR,
42+
'--once', '--input-extension=.haml', '--verbose', '--tag=macro:endmacro'
43+
], 1)
44+
45+
# check file without errors was converted
46+
self.assertFileContents(OUTPUT_DIR + os.sep + 'test.html',
47+
"<span class='test'></span>\n{% macro %}\n{% endmacro %}\n")
48+
49+
# run without output directory which should make it default to re-using the input directory
50+
self._run_script([
51+
'hamlpy_watcher.py',
52+
INPUT_DIR,
53+
'--once', '--input-extension=.haml', '--tag=macro:endmacro'
54+
], 1)
55+
56+
self.assertFileContents(INPUT_DIR + os.sep + 'test.html',
57+
"<span class='test'></span>\n{% macro %}\n{% endmacro %}\n")
58+
59+
# run in watch mode with 1 second refresh
60+
self._run_script([
61+
'hamlpy_watcher.py',
62+
INPUT_DIR,
63+
'--refresh=1', '--input-extension=.haml', '--tag=macro:endmacro'
64+
], 1)
65+
66+
def assertFileContents(self, path, contents):
67+
with open(path, 'r') as f:
68+
self.assertEqual(f.read(), contents)
69+
70+
def _write_file(self, path, text):
71+
with open(path, 'w') as f:
72+
f.write(text)
73+
74+
def _run_script(self, script_args, expected_exit_code):
75+
def raise_exception_with_code(code):
76+
raise ScriptExit(code)
77+
78+
# patch sys.exit so it throws an exception so we can return execution to this test
79+
# patch sys.argv to pass our arguments to the script
80+
# patch time.sleep to be interrupted
81+
with patch.object(sys, 'exit', side_effect=raise_exception_with_code), \
82+
patch.object(sys, 'argv', script_args), \
83+
patch.object(time, 'sleep', side_effect=KeyboardInterrupt), \
84+
self.assertRaises(ScriptExit) as raises:
85+
86+
watch_folder()
87+
88+
assert raises.exception.exit_code == expected_exit_code

0 commit comments

Comments
 (0)