Module: Mailmate::CLI::Verify Private

Extended by:
Verify
Included in:
Verify
Defined in:
lib/mailmate/cli/verify.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

‘mm-verify` — batch-confirm `mm-modify –emit-check` tickets against the `#flags` index in ONE index-flush wait.

MailMate flushes ‘#flags` to disk a few seconds after an AppleScript action, and it’s a single global file. So a batch of N modifies can be confirmed by waiting once for that flush and reading the index once —not by polling per message (which would pay the latency N times). Feed this the tickets ‘mm-modify –emit-check` printed (a JSON array, or newline-delimited JSON objects); it polls the index until every ticket’s expectations hold or –check-timeout elapses, then prints a JSON summary.

Exit: 0 all confirmed, 3 one or more failed, 2 bad input.

Instance Method Summary collapse

Instance Method Details

#build_parser(opts) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/mailmate/cli/verify.rb', line 118

def build_parser(opts)
  OptionParser.new do |o|
    o.banner = <<~BANNER
      Usage: mm-verify [tickets.json] [options]
             mm-modify <id> <action> --emit-check | ... | mm-verify

      Confirm a batch of `mm-modify --emit-check` tickets against the #flags
      index, paying the index-flush wait ONCE for the whole batch. Input is a
      JSON array of tickets, or newline-delimited JSON objects, read from a
      file (positional or --file), a JSON argument, or stdin.

      Output: a JSON summary {checked, passed, failed, waited_seconds, results}.
      Exit 0 if all confirmed, 3 if any failed, 2 on bad input.
    BANNER
    o.on("--file PATH", "Read tickets from PATH instead of stdin") { |p| opts[:file] = p }
    o.on("--check-timeout SECONDS", Float, "Max seconds to wait for #flags to reflect the batch (default 8.0)") { |s| opts[:check_timeout] = s }
    o.on("--poll SECONDS", Float, "Index re-read interval while waiting (default 0.25)") { |s| opts[:poll] = s }
    o.on("--compact", "Compact JSON output (default pretty)") { opts[:pretty] = false }
  end
end

#check_all(tickets) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

One pass: read #flags fresh, then evaluate every ticket against it.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/mailmate/cli/verify.rb', line 71

def check_all(tickets)
  reader = fresh_flags_reader
  tickets.map do |t|
    eml_id = t["eml_id"].to_i
    exps   = Array(t["expectations"])
    flags  = reader ? reader.flags_for(eml_id) : []
    unmet  = exps.reject { |kind, arg| Mailmate::FlagCheck.met?(flags, kind, arg) }
    {
      "eml_id"     => eml_id,
      "message_id" => t["message_id"],
      "ok"         => unmet.empty?,
      "flags"      => flags,
      "unmet"      => unmet.map { |kind, arg| Mailmate::FlagCheck.label(kind, arg) },
    }
  end
end

#fresh_flags_readerObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Force a fresh read of #flags (bypass the staleness throttle so each poll sees the latest on-disk state). nil if the index is absent.



90
91
92
93
94
95
# File 'lib/mailmate/cli/verify.rb', line 90

def fresh_flags_reader
  Mailmate::IndexReader.reset!("#flags")
  Mailmate::IndexReader.for("#flags")
rescue ArgumentError
  nil
end

#parse_tickets(raw) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Accepts a JSON array of ticket objects, or newline-delimited JSON objects (what ‘mm-modify –emit-check` prints, one per line). A bare single object is wrapped.



100
101
102
103
104
105
106
107
108
109
# File 'lib/mailmate/cli/verify.rb', line 100

def parse_tickets(raw)
  s = raw.strip
  if s.start_with?("[")
    Array(JSON.parse(s))
  elsif s.start_with?("{") && !s.include?("\n")
    [JSON.parse(s)]
  else
    s.each_line.map(&:strip).reject(&:empty?).map { |line| JSON.parse(line) }
  end
end

#read_input(opts, argv) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



111
112
113
114
115
116
# File 'lib/mailmate/cli/verify.rb', line 111

def read_input(opts, argv)
  return File.read(opts[:file]) if opts[:file]
  return argv.join("\n") unless argv.empty?
  return nil if $stdin.tty?
  $stdin.read
end

#run(argv) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/mailmate/cli/verify.rb', line 25

def run(argv)
  opts = { check_timeout: 8.0, poll: 0.25, pretty: true, file: nil }
  parser = build_parser(opts)
  parser.parse!(argv)

  raw = read_input(opts, argv)
  return usage_error(parser, "no ticket input (pass a file, JSON arg, or pipe on stdin)") if raw.nil? || raw.strip.empty?

  tickets =
    begin
      parse_tickets(raw)
    rescue JSON::ParserError => e
      warn "mm-verify: could not parse tickets as JSON array or NDJSON: #{e.message}"
      return 2
    end
  return usage_error(parser, "no tickets found in input") if tickets.empty?

  summary = verify(tickets, timeout: opts[:check_timeout], poll: opts[:poll])
  $stdout.puts(opts[:pretty] ? JSON.pretty_generate(summary) : JSON.generate(summary))
  summary["failed"].zero? ? 0 : 3
end

#usage_error(parser, msg) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



139
140
141
142
143
# File 'lib/mailmate/cli/verify.rb', line 139

def usage_error(parser, msg)
  warn "mm-verify: #{msg}"
  warn parser.help
  2
end

#verify(tickets, timeout:, poll:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Poll the #flags index until every ticket’s expectations hold or the timeout elapses; one index read per poll iteration covers the whole batch. Returns the summary Hash.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/mailmate/cli/verify.rb', line 50

def verify(tickets, timeout:, poll:)
  deadline = Time.now + timeout
  started  = Time.now
  results  = nil
  loop do
    results = check_all(tickets)
    break if results.all? { |r| r["ok"] }
    break if Time.now >= deadline
    sleep(poll)
  end
  passed = results.count { |r| r["ok"] }
  {
    "checked"        => results.size,
    "passed"         => passed,
    "failed"         => results.size - passed,
    "waited_seconds" => (Time.now - started).round(2),
    "results"        => results,
  }
end