]> git.cworth.org Git - sup/blob - lib/sup/hook.rb
Merge branch 'hook-local-vars' into next
[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       info "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   end
73
74   attr_reader :tags
75
76   def run name, locals={}
77     hook = hook_for(name) or return
78     context = @contexts[hook] ||= HookContext.new(name)
79
80     result = nil
81     begin
82       result = context.__run hook, fn_for(name), locals
83     rescue Exception => e
84       log "error running hook: #{e.message}"
85       log e.backtrace.join("\n")
86       @hooks[name] = nil # disable it
87       BufferManager.flash "Error running hook: #{e.message}" if BufferManager.instantiated?
88     end
89     result
90   end
91
92   def register name, desc
93     @descs[name] = desc
94   end
95
96   def print_hooks f=$stdout
97 puts <<EOS
98 Have #{@descs.size} registered hooks:
99
100 EOS
101
102     @descs.sort.each do |name, desc|
103       f.puts <<EOS
104 #{name}
105 #{"-" * name.length}
106 File: #{fn_for name}
107 #{desc}
108 EOS
109     end
110   end
111
112   def enabled? name; !hook_for(name).nil? end
113
114   def clear; @hooks.clear; 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     info("hook: " + m)
139   end
140 end
141
142 end