sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/

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