]> git.notmuchmail.org Git - sup/blob - lib/sup/hook.rb
d3d2ba8fc4b6f88619843512c99dec8c0c94c8cb
[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       ret
58     end
59   end
60
61   include Singleton
62
63   def initialize dir
64     @dir = dir
65     @hooks = {}
66     @descs = {}
67     @contexts = {}
68     @tags = {}
69
70     Dir.mkdir dir unless File.exists? dir
71
72     self.class.i_am_the_instance self
73   end
74
75   attr_reader :tags
76
77   def run name, locals={}
78     hook = hook_for(name) or return
79     context = @contexts[hook] ||= HookContext.new(name)
80
81     result = nil
82     begin
83       result = context.__run hook, fn_for(name), locals
84     rescue Exception => e
85       log "error running hook: #{e.message}"
86       log e.backtrace.join("\n")
87       @hooks[name] = nil # disable it
88       BufferManager.flash "Error running hook: #{e.message}" if BufferManager.instantiated?
89     end
90     result
91   end
92
93   def register name, desc
94     @descs[name] = desc
95   end
96
97   def print_hooks f=$stdout
98 puts <<EOS
99 Have #{@descs.size} registered hooks:
100
101 EOS
102
103     @descs.sort.each do |name, desc|
104       f.puts <<EOS
105 #{name}
106 #{"-" * name.length}
107 File: #{fn_for name}
108 #{desc}
109 EOS
110     end
111   end
112
113   def enabled? name; !hook_for(name).nil? end
114
115 private
116
117   def hook_for name
118     unless @hooks.member? name
119       @hooks[name] = begin
120         returning IO.read(fn_for(name)) do
121           log "read '#{name}' from #{fn_for(name)}"
122         end
123       rescue SystemCallError => e
124         #log "disabled hook for '#{name}': #{e.message}"
125         nil
126       end
127     end
128
129     @hooks[name]
130   end
131
132   def fn_for name
133     File.join @dir, "#{name}.rb"
134   end
135
136   def log m
137     Redwood::log("hook: " + m)
138   end
139 end
140
141 end