]> git.cworth.org Git - sup/blob - lib/sup/hook.rb
33a97b26063bffceb9f3d600962918b46106aa34
[sup] / lib / sup / hook.rb
1 module Redwood
2
3 class HookManager
4   ## there's probably a better way to do this, but to evaluate a hook
5   ## with a bunch of pre-set "local variables" i define a function
6   ## per variable and then instance_evaluate the code.
7   ##
8   ## how does rails do it, when you pass :locals into a partial?
9   ##
10   ## i don't bother providing setters, since i'm pretty sure the
11   ## charade will fall apart pretty quickly with respect to scoping.
12   ## "fail-fast", we'll call it.
13   class HookContext
14     def initialize name
15       @__say_id = nil
16       @__name = name
17       @__locals = {}
18     end
19
20     attr_writer :__locals
21
22     ## an annoying gotcha here is that if you try something
23     ## like var = var.foo(), var will magically get allocated
24     ## to Nil and method_missing will never get called.  You
25     ## can work around this by calling self.var or simply
26     ## not assigning it to itself.
27     def method_missing m, *a
28       case @__locals[m]
29       when Proc
30         @__locals[m] = @__locals[m].call(*a) # only call the proc once
31       when nil
32         super
33       else
34         @__locals[m]
35       end
36     end
37
38     def say s
39       if BufferManager.instantiated?
40         @__say_id = BufferManager.say s, @__say_id
41         BufferManager.draw_screen
42       else
43         log s
44       end
45     end
46
47     def log s
48       Redwood::log "hook[#@__name]: #{s}"
49     end
50
51     def ask_yes_or_no q
52       if BufferManager.instantiated?
53         BufferManager.ask_yes_or_no q
54       else
55         print q
56         gets.chomp.downcase == 'y'
57       end
58     end
59
60     def get tag
61       HookManager.tags[tag]
62     end
63
64     def set tag, value
65       HookManager.tags[tag] = value
66     end
67
68     def __binding 
69       binding
70     end
71
72     def __cleanup
73       BufferManager.clear @__say_id if @__say_id
74     end
75   end
76
77   include Singleton
78
79   def initialize dir
80     @dir = dir
81     @hooks = {}
82     @descs = {}
83     @contexts = {}
84     @tags = {}
85
86     Dir.mkdir dir unless File.exists? dir
87
88     self.class.i_am_the_instance self
89   end
90
91   attr_reader :tags
92
93   def run name, locals={}
94     hook = hook_for(name) or return
95     context = @contexts[hook] ||= HookContext.new(name)
96     context.__locals = locals
97
98     result = nil
99     begin
100       result = context.instance_eval @hooks[name], fn_for(name)
101     rescue Exception => e
102       log "error running hook: #{e.message}"
103       log e.backtrace.join("\n")
104       @hooks[name] = nil # disable it
105       BufferManager.flash "Error running hook: #{e.message}" if BufferManager.instantiated?
106     end
107     context.__cleanup
108     result
109   end
110
111   def register name, desc
112     @descs[name] = desc
113   end
114
115   def print_hooks f=$stdout
116 puts <<EOS
117 Have #{@descs.size} registered hooks:
118
119 EOS
120
121     @descs.sort.each do |name, desc|
122       f.puts <<EOS
123 #{name}
124 #{"-" * name.length}
125 File: #{fn_for name}
126 #{desc}
127 EOS
128     end
129   end
130
131   def enabled? name; !hook_for(name).nil? end
132
133 private
134
135   def hook_for name
136     unless @hooks.member? name
137       @hooks[name] =
138         begin
139           returning IO.read(fn_for(name)) do
140             log "read '#{name}' from #{fn_for(name)}"
141           end
142         rescue SystemCallError => e
143           #log "disabled hook for '#{name}': #{e.message}"
144           nil
145         end
146     end
147
148     @hooks[name]
149   end
150
151   def fn_for name
152     File.join @dir, "#{name}.rb"
153   end
154
155   def log m
156     Redwood::log("hook: " + m)
157   end
158 end
159
160 end