-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfile_watcher.py
executable file
·192 lines (156 loc) · 5.14 KB
/
file_watcher.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
from __future__ import print_function
from builtins import input
import sys
import os
import queue
import pexpect
import time
import yaml
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
if sys.version_info[0] == 2: # check for python major version
from backports.shutil_get_terminal_size import get_terminal_size
else:
from shutil import get_terminal_size
CONFIG_FILE_NAME = '.fw'
class UnknownAnswerException(Exception):
def __init__(self):
super(UnknownAnswerException, self).__init__()
class OnModifiedInformer(FileSystemEventHandler):
def __init__(self, changed_files):
super(OnModifiedInformer, self).__init__()
self.changed_files = changed_files
def on_modified(self, event):
self.changed_files.put(os.path.relpath(event.src_path))
def get_user_lines_until_empty(msg):
lines = []
print(msg)
while True:
line = input('> ')
if line:
lines.append(line)
else:
return lines
def start_watching_for_files(changed_files, path):
event_handler = OnModifiedInformer(changed_files)
observer = Observer()
observer.schedule(event_handler, path, recursive=True)
observer.start()
return observer
def load_config_file():
# TODO: maybe reload config on change
try:
with open(CONFIG_FILE_NAME, 'r') as config_file:
return yaml.load(config_file)
except (yaml.YAMLError, IOError) as exc: # IOError instead of FileNotFoundError to support Python 2.
return {'commands': [], 'paths': {}}
def reset_commands(config):
get_new_commands = True
if config['commands']:
while True:
print("Will execute following commands when files are changed:")
print(str(config['commands']))
answer = input("Change commands? (y/N) ")
if answer == 'y':
break
elif answer in ['n', '']:
get_new_commands = False
break
else:
pass
if get_new_commands:
command_string = get_user_lines_until_empty(
"Set commands to execute, each in one line. Empty line if done.")
config['commands'] = command_string
print("Will execute following commands when files are changed:")
print(str(config['commands']))
return config
def reset_paths(config):
reset_paths = True
print(str(config['paths']))
while True:
answer = input("Reset paths? (y/N) ")
if answer == 'y':
break
elif answer in ['n', '']:
reset_paths = False
break
else:
pass
if reset_paths:
config['paths'] = {}
return config
def user_init_config(config):
config = reset_commands(config)
config = reset_paths(config)
return config
def get_path_info(config, path):
try:
return config['paths'][path]
except KeyError:
return None
def convert_to_path_info(string):
if string == 'y':
return {'execute': True, 'cwd': os.getcwd()}
elif string == 'n':
return {'execute': None}
else:
return None
def set_path_info(config, path):
user_msg = ("Unknown file changed: " + path +
"\nRun commands for this path (y/n): ")
while True:
answer = input(user_msg)
path_info = convert_to_path_info(answer)
if path_info:
config['paths'][path] = path_info
break
return get_path_info(config, path)
def run_command(command):
child = pexpect.spawn(command)
child.stderr = sys.stdout
child.stdout = sys.stdout
child.interact()
def run_commands(config, path_info):
# TODO maybe current time and time measurement
term_columns = get_terminal_size((80, 20)).columns
sep_string = '='*term_columns
commands = config['commands']
print(sep_string)
for command in commands:
print(">>>", command, ">>>")
run_command(command)
print("<<<")
print(sep_string)
def act_on_changed_file(config, changed_files):
path = changed_files.get() # blocking
path_info = get_path_info(config, path)
if not path_info:
path_info = set_path_info(config, path)
if path_info['execute']:
run_commands(config, path_info)
def save_config_to_file(config):
with open(CONFIG_FILE_NAME, 'w') as config_file:
yaml.dump(config, config_file)
def main():
path = os.getcwd()
changed_files = queue.Queue()
observer = start_watching_for_files(changed_files, path)
config = load_config_file()
config = user_init_config(config)
print(config)
while True:
try:
print("Running ...:")
act_on_changed_file(config, changed_files) # blocking
# TODO: maybe user input when not running command
except KeyboardInterrupt:
break
# TODO: maybe interrupt command when running and don't stop
# but stop when no command is running
print("\nQuitting, saving config to file:", CONFIG_FILE_NAME)
observer.stop()
observer.join()
save_config_to_file(config)
if __name__ == '__main__':
main()