lib/sup/hook.rb (3440B) - raw
1 require "sup/util"
2
3 module Redwood
4
5 class HookManager
6 class HookContext
7 def initialize name
8 @__say_id = nil
9 @__name = name
10 @__cache = {}
11 end
12
13 def say s
14 if BufferManager.instantiated?
15 @__say_id = BufferManager.say s, @__say_id
16 BufferManager.draw_screen
17 else
18 log s
19 end
20 end
21
22 def flash s
23 if BufferManager.instantiated?
24 BufferManager.flash s
25 else
26 log s
27 end
28 end
29
30 def log s
31 info "hook[#@__name]: #{s}"
32 end
33
34 def ask_yes_or_no q
35 if BufferManager.instantiated?
36 BufferManager.ask_yes_or_no q
37 else
38 print q
39 gets.chomp.downcase == 'y'
40 end
41 end
42
43 def get tag
44 HookManager.tags[tag]
45 end
46
47 def set tag, value
48 HookManager.tags[tag] = value
49 end
50
51 def __run __hook, __filename, __locals
52 __binding = binding
53 __lprocs, __lvars = __locals.partition { |k, v| v.is_a?(Proc) }
54 eval __lvars.map { |k, v| "#{k} = __locals[#{k.inspect}];" }.join, __binding
55 ## we also support closures for delays evaluation. unfortunately
56 ## we have to do this via method calls, so you don't get all the
57 ## semantics of a regular variable. not ideal.
58 __lprocs.each do |k, v|
59 self.class.instance_eval do
60 define_method k do
61 @__cache[k] ||= v.call
62 end
63 end
64 end
65 ret = eval __hook, __binding, __filename
66 BufferManager.clear @__say_id if @__say_id
67 @__cache = {}
68 ret
69 end
70 end
71
72 include Redwood::Singleton
73
74 @descs = {}
75
76 class << self
77 attr_reader :descs
78 end
79
80 def initialize dir
81 @dir = dir
82 @hooks = {}
83 @contexts = {}
84 @tags = {}
85
86 Dir.mkdir dir unless File.exist? dir
87 end
88
89 attr_reader :tags
90
91 def run name, locals={}
92 hook = hook_for(name) or return
93 context = @contexts[hook] ||= HookContext.new(name)
94
95 result = nil
96 fn = fn_for name
97 begin
98 result = context.__run hook, fn, locals
99 rescue Exception => e
100 log "error running #{fn}: #{e.message}"
101 log e.backtrace.join("\n")
102 @hooks[name] = nil # disable it
103 BufferManager.flash "Error running hook: #{e.message}" if BufferManager.instantiated?
104 end
105 result
106 end
107
108 def self.register name, desc
109 @descs[name] = desc
110 end
111
112 def print_hooks pattern="", f=$stdout
113 matching_hooks = HookManager.descs.sort.keep_if {|name, desc| pattern.empty? or name.match(pattern)}.map do |name, desc|
114 <<EOS
115 #{name}
116 #{"-" * name.length}
117 File: #{fn_for name}
118 #{desc}
119 EOS
120 end
121
122 showing_str = matching_hooks.size == HookManager.descs.size ? "" : " (showing #{matching_hooks.size})"
123 f.puts "Have #{HookManager.descs.size} registered hooks#{showing_str}:"
124 f.puts
125 matching_hooks.each { |text| f.puts text }
126 end
127
128 def enabled? name; !hook_for(name).nil? end
129
130 def clear; @hooks.clear; BufferManager.flash "Hooks cleared" end
131 def clear_one k; @hooks.delete k; end
132
133 private
134
135 def hook_for name
136 unless @hooks.member? name
137 @hooks[name] = begin
138 returning IO.read(fn_for(name)) do
139 debug "read '#{name}' from #{fn_for(name)}"
140 end
141 rescue SystemCallError
142 #debug "disabled hook for '#{name}': #{e.message}"
143 nil
144 end
145 end
146
147 @hooks[name]
148 end
149
150 def fn_for name
151 File.join @dir, "#{name}.rb"
152 end
153
154 def log m
155 info("hook: " + m)
156 end
157 end
158
159 end