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