sup

A curses threads-with-tags style email client

sup.git

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

contrib/screenshot-generator/shoot.rb (3051B) - raw

      1 require 'fileutils'
      2 require 'json'
      3 require 'open3'
      4 require 'optparse'
      5 require 'shellwords'
      6 require 'tmpdir'
      7 require 'yaml'
      8 
      9 SIZE = "120x28"  # terminal grid dimensions
     10 IDLE = 0.3       # settling time in seconds for the terminal
     11 
     12 class Session
     13   def initialize command, env
     14     ht_command = ["ht", "--size", SIZE,
     15                   "--subscribe", "init,output,snapshot",
     16                   "--", Shellwords.join(command)]
     17     @stdin, @stdout, @process = Open3.popen2 env, *ht_command
     18     @stdin.sync = true
     19     raise "ht did not send init event" unless read_event["type"] == "init"
     20   end
     21 
     22   def write command
     23     @stdin.puts JSON.generate(command)
     24   end
     25 
     26   def read_event
     27     line = @stdout.gets or return nil
     28     JSON.parse line
     29   end
     30 
     31   def send_keys keys
     32     write :type => "sendKeys", :keys => keys
     33   end
     34 
     35   ## Wait until at least IDLE seconds has passed with no more events.
     36   def settle
     37     loop do
     38       return unless IO.select([@stdout], nil, nil, IDLE)
     39       return unless read_event
     40     end
     41   end
     42 
     43   def snapshot
     44     write :type => "takeSnapshot"
     45     loop do
     46       event = read_event
     47       return event["data"] if event["type"] == "snapshot"
     48     end
     49   end
     50 
     51   def expect pattern
     52     settle
     53     screen = snapshot
     54     raise "/#{pattern.source}/ not found" unless screen["text"] =~ pattern
     55     screen
     56   end
     57 
     58   def dump_screen
     59     $stderr.puts "--- screen was ---"
     60     $stderr.puts snapshot["text"]
     61     $stderr.puts "------------------"
     62   rescue
     63     $stderr.puts "--- no screen  ---"
     64   end
     65 
     66   def close
     67     @stdin.close unless @stdin.closed?
     68     @stdout.read
     69     @stdout.close
     70   end
     71 end
     72 
     73 options = {}
     74 OptionParser.new do |parser|
     75   parser.on "--sup-base PATH"
     76   parser.on "--sup PATH"
     77   parser.on "--scenes-yaml PATH"
     78   parser.on "--out PATH"
     79 end.parse! into: options
     80 
     81 FileUtils.mkdir_p options[:out]
     82 
     83 scenes = YAML.load_file options[:"scenes-yaml"]
     84 scenes.each do |scene|
     85   name = scene["name"]
     86   steps = scene["steps"] || []
     87 
     88   ## Make a fresh copy of SUP_BASE for this run of sup.
     89   base = Dir.mktmpdir "sup-screenshots-#{name}-"
     90   FileUtils.cp_r Dir.glob(File.join(options[:"sup-base"], "*")), base
     91   FileUtils.chmod_R "u+w", base
     92 
     93   puts "[#{name}] starting sup"
     94   session = nil
     95   begin
     96     session = Session.new(
     97       ["ruby", options[:sup], "--no-threads", "--no-initial-poll"],
     98       {"SUP_BASE" => base, "SUP_LOG_LEVEL" => "debug"})
     99 
    100     screen = session.expect /\[inbox-mode\]/
    101 
    102     steps.each do |step|
    103       keys = step["keys"]
    104       puts "[#{name}] send #{keys.inspect}"
    105       session.send_keys keys
    106 
    107       screen = if step["wait"].nil?
    108         session.settle
    109         session.snapshot
    110       else
    111         session.expect Regexp.new(step["wait"])
    112       end
    113     end
    114 
    115     ansi = screen["seq"]
    116     ansi = ansi.gsub "\u009b", "\e["  # map CSI emitted by ht to ESC [
    117     path = File.join options[:out], "#{name}.ansi"
    118     File.write path, ansi
    119     puts "[#{name}] wrote #{path}"
    120   rescue RuntimeError => e
    121     $stderr.puts "[#{name}] #{e.message}"
    122     session.dump_screen if session
    123     raise
    124   ensure
    125     session.close if session
    126   end
    127 end