-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinit.moon
338 lines (304 loc) · 10.3 KB
/
init.moon
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
-- load the core
require "vendor"
require "lib"
lpeg = require "lpeglj"
package.loaded.lpeg = lpeg
require "globals"
S = require 'syscall'
:execute, :children = require 'process'
:readline = require 'utils'
fs = require 'fs'
lfs = require 'syscall.lfs'
getcwd = getcwd
gettimeofday = gettimeofday
insert: append, :concat, remove: pop, :clear = table
:max = math
-- add some default load paths
for base in *{getcwd!, os.getenv('HOME')}
package.path = package.path ..
";#{base}/.spook/lib/?.lua" ..
";#{base}/.spook/lib/?/init.lua"
-- setup additional requirements
require "moonscript"
_G.log = require 'log'
_G.notify = require('notify')!
moonscript = require "moonscript.base"
colors = require 'ansicolors'
{:index_of} = table
arg = arg
original_args = {k, v for k, v in pairs arg}
log = _G.log
timer = _G.timer
log.level log.INFO
loadfail = (file, result) ->
print colors "%{red}FATAL: Failed to load '#{file}'"
if fs.is_file file
print colors "%{white}#{result}" if result
print ""
print colors "%{dim}This may indicate a syntax error"
else
print colors "%{white}No such file '#{file}'"
os.exit 1
-- if there is an argument "-f" on the commandline
-- we completely skip the default behavior and run
-- whatever script path given within the context of
-- spook. Quite similar to running the file with just
-- luajit path/to/some/file, with the obvious difference
-- that here it is run within the spook context and all
-- that comes built-in is available (including moonscript, so
-- moonscript files can be run as well as lua files).
if fi = index_of arg, "-f"
file = arg[fi + 1]
new_args = [a for i, a in ipairs arg when i>(fi + 1)]
unless file
log.error "The -f option requires an argument"
os.exit 1
_G.arg = new_args
success, chunk = if file\match("[^.]%.lua$")
pcall loadfile, file
else
pcall moonscript.loadfile, file
loadfail file, chunk unless success
return chunk!
if pi = index_of arg, "-p"
pidfile = arg[pi + 1]
if not pidfile or pidfile\match('^-')
log.error "The -p option requires an argument"
os.exit 1
p = io.open(pidfile, "w")
unless p
log.error "Couldn't open given pidfile '#{pidfile}'"
os.exit 1
p\write S.getpid!
cli = require "arguments"
:run, :signalreset, :epoll_fd = require 'event_loop'
Spook = require 'spook'
local spook, fs_events
-- to prevent multiple events happening very quickly
-- on a specific file we need to run a handler on some
-- interval which coalesces the events into one (here it's
-- just the latest event, disregarding any previous ones).
event_handler = =>
seen_paths = {}
while #fs_events > 0
event = pop fs_events
continue unless event.path -- ignore events without a path
continue if seen_paths[event.path] -- ignore events we've already seen
seen_paths[event.path] = true
matching = spook\match event
if matching and #matching > 0
for handler in *matching
success, result = pcall handler
unless success
log.debug "An error occurred in change_handler: #{result}"
break if spook.first_match_only -- the default
@again!
kill_children = ->
killed = 0
for pid in pairs children
S.kill -pid, "KILL"
S.waitpid pid
children[pid] == nil
killed += 1
dead_children = (num) -> num == 1 and "#{num} child" or "#{num} children"
if killed > 0
io.stderr\write colors "[ %{red}#{dead_children(killed)} killed%{reset} ]\n"
signaled_at = 0
start = ->
print = print
tostring = tostring
os = os
io = io
for sig in *{'int', 'term', 'quit', 'pipe'}
spook\on_signal sig, (s) ->
killall = gettimeofday! - signaled_at < 500
signaled_at = gettimeofday!
unless killall
killed = 0
for pid in pairs children
S.kill -pid, "term"
killed += 1
dead_children = (num) -> num == 1 and "#{num} child" or "#{num} children"
if killed > 0
io.stderr\write colors "[ %{red}#{dead_children(killed)} terminated%{reset} ]\n"
return if S.stdin\isatty!
kill_children!
s\stop!
spook\stop!
io.stderr\write colors "Killed by SIG#{sig\upper!}.\n"
os.exit(1)
-- 0.35 interval is something I've found works
-- reasonably well. So we collect events every interval.
spook\after 0.35, event_handler
spook\start!
-- this is finally setting up spook from the Spookfile
-- this function is also made available globally which
-- makes it possible to reload the Spookfile from the Spookfile
-- itself (probably based on some event like a change to the
-- Spookfile).
load_spookfile = ->
args = cli\parse!
spookfile_path = args.c or os.getenv('SPOOKFILE') or "Spookfile"
spook\stop! if spook
success, result = pcall moonscript.loadfile, spookfile_path
loadfail spookfile_path, result unless success
spookfile = result
spook = Spook.new!
if args.l
spook.log_level = args.l\upper!
_G.spook = spook
_G.notify.clear!
fs_events = spook.fs_events
success, result = pcall -> spook spookfile
loadfail spookfile_path, result unless success
dir_or_dirs = (num) ->
num == 1 and 'directory' or 'directories'
file_or_files = (num) ->
num == 1 and 'file' or 'files'
if log[spook.log_level] > log.WARN
_G.notify.info colors "%{blue}Watching #{spook.num_dirs} #{dir_or_dirs(spook.num_dirs)}%{reset}"
_G.notify.info colors "%{blue}Watching #{spook.file_watches} single #{file_or_files(spook.file_watches)}%{reset}"
start!
_G.load_spookfile = load_spookfile
-- this reexecutes spook which means doing a full reload of everything as
-- the old process is replaced by a new one.
_G.reload_spook = ->
signalreset!
epoll_fd\close! if epoll_fd
args = {"/bin/sh", "-c"}
sargs = {original_args[0]}
append sargs, a for a in *original_args
cmd = args[1]
append args, "exec #{concat(sargs, ' ')}"
S.execve cmd, args, ["#{k}=#{v}" for k, v in pairs S.environ!]
stdin_input = ->
-- if we have a controlling terminal, this doesn't apply
return if S.isatty(S.stdin)
-- wait for a specified time for input on stdin
wait = 2.0
take = take_while (item) -> item != '--'
args = [a for a in *arg when take a]
if w = index_of args, "-r"
success, w = pcall tonumber, arg[w + 1]
wait = w if success
return if wait <= 0.0
sel = S.select(readfds: {S.stdin}, wait)
-- if there was no input, bail into normal spook mode
if sel.count == 0
io.stderr\write "waited for data on stdin for #{wait}s but got nothing.\n"
return
-- using a table because excessive string concatenation
-- in Lua is the wrong approach and will be very slow here
log.output io.stderr -- use stderr in stdin mode for logging
input = {}
append input, line for line in readline(S.stdin)
return unless #input > 0
input
expand_file = (data, file) ->
return nil unless data
filename, _ = fs.name_ext file
basename = fs.basename file
basenamenoext = fs.basename filename
data = data\gsub '([[{%<](file)[]}>])', file
data = data\gsub '([[{%<](filenoext)[]}>])', filename
data = data\gsub '([[{%<](basename)[]}>])', basename
data\gsub '([[{%<](basenamenoext)[]}>])', basenamenoext
-- for example, this file list:
-- ./a/b/c/d/e/file-e.txt
-- ./a/b/c/file-c.txt
-- ./b/c/file-bc.txt
-- ./b/c/e/file-bc.txt
-- should give us this list of dirs (to watch):
-- ./a/b/c/d/e
-- ./a/b/c
-- ./b/c
-- ./b/c/e
watch_dirs = (files) ->
to_dir = (file) ->
fst = file\sub 1, 1
snd = file\sub 2, 2
file = './' .. file if fst != '/' and "#{fst}#{snd}" != './'
attr = lfs.attributes file
unless attr
if S.lstat file -- broken symbolic link - we can skip those I hope
log.debug "Broken symbolic link here: '#{file}' - skipping"
return nil
log.error "What is this '#{file}' you give me? There's nothing called that where you say there is.\nDid you use your special awesome 'ls' alias that outputs very cool stuff maybe?\nPlease give me actual names of files or directories. The standard find utility usually does a good job.\n\nAnyway - k thx bye."
os.exit 1
attr.mode == 'directory' and file or fs.dirname(file)
dirs = [to_dir(file) for file in *files when to_dir(file)]
dirmap = {lfs.attributes(dir).ino, dir for dir in *dirs}
[dir for _, dir in pairs dirmap]
-- if there's anything on stdin, then work somewhat like entr: http://entrproject.org
watch_files_from_stdin = (files) ->
io.stdout\setvbuf 'no'
spook\stop! if spook
spook = Spook.new!
_G.spook = spook
_G.notify.clear!
fs_events = spook.fs_events
start_now = false
exit_after_event = false
take = take_while (item) -> item != '--'
args = [a for a in *arg when take(a)]
local si, oi, ll, pp
if si = index_of args, "-s"
start_now = true
si or= 0
if oi = index_of args, "-o"
exit_after_event = true
oi or= 0
if ll = index_of args, "-l"
ll += 1
spook.log_level = arg[ll]\upper!
ll or= 0
if pp = index_of args, "-p"
pp += 1
pp or= 0
li = max(max(max(si, oi), ll), pp)
take = drop_while (index, item) -> index <= li or item == '--'
args = [a for i, a in ipairs arg when take i, a]
command = if #args > 0
concat ([a for i, a in ipairs args]), ' '
filemap = {file\gsub('^%./',''), true for file in *files}
pid = 0
if start_now and command
pid = coroutine.wrap(-> execute command)!
is_match = (name) -> filemap[name]
handler = if command
(event, f) ->
if exit_after_event and pid > 0
S.kill -pid, "term"
os.exit(0)
return unless is_match f
S.kill -pid, "term" if start_now
clear fs_events -- empty the event list in place when we have a match
cmdline = expand_file command, f
opts = {}
if exit_after_event
opts.on_death = (success, exittype, exitstatus) ->
os.exit(0) if success
os.exit(exitstatus) if exitstatus > 0
os.exit(1)
pid = coroutine.wrap(-> execute cmdline, opts)!
else
(event, f) ->
unless is_match f
os.exit(0) if exit_after_event
return
io.stdout\write f, "\n"
os.exit(0) if exit_after_event
dirs = watch_dirs(files)
log.debug "Start watching #{#dirs} (unique) directories..."
log.debug "Matching events against #{#files} files..."
spook\watchnr dirs, ->
on_changed '(.*)', handler
on_deleted '(.*)', handler
start!
t = timer "initialization"
if input = stdin_input!
watch_files_from_stdin input
else
load_spookfile!
t log.debug
run!