Module: Mailmate::CLI::Modify Private

Extended by:
Modify
Included in:
Modify
Defined in:
lib/mailmate/cli/modify.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-modify` — apply AppleScript-driven actions (read, flag, tag, archive, move, …) to a MailMate message by its eml-id.

Ports mailmate-modify. Multiple actions in one invocation share a single open+wait cycle so chained operations are batched.

Constant Summary collapse

ACTIONS =

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

action → [selector] or [selector, arg-count]. arg-count default 0.

{
  "read"       => ["markAsRead:"],
  "unread"     => ["markAsUnread:"],
  "flag"       => [:ensure_flagged],
  "unflag"     => [:ensure_not_flagged],
  "tag"        => ["setTag:", 1],
  "untag"      => ["removeTag:", 1],
  "clear-tags" => ["clearTags:"],
  "archive"    => ["archive:"],
  "junk"       => ["markAsJunk:"],
  "not-junk"   => ["markAsNotJunk:"],
  "mute"       => ["toggleMuteState:"],
  "delete"     => ["deleteMessage:"],
  "move"       => ["moveToMailbox:", 1],
}.freeze

Instance Method Summary collapse

Instance Method Details

#current_flags(eml_id) ⇒ 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.



174
175
176
177
178
179
180
181
# File 'lib/mailmate/cli/modify.rb', line 174

def current_flags(eml_id)
  # AppleScript actions write the index asynchronously — bust just the
  # #flags cache to pick up the latest values without throwing away
  # other warmed indexes (#message-id, #source) that this same
  # invocation may still need.
  Mailmate::IndexReader.reset!("#flags")
  Mailmate::IndexReader.for("#flags").flags_for(eml_id.to_i)
end

#drive(eml_id, message_id, actions, 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.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/mailmate/cli/modify.rb', line 137

def drive(eml_id, message_id, actions, opts)
  driver  = Mailmate::AppleScriptDriver.new(dry_run: opts[:dry_run])
  mid_url = Mailmate::MidUrl.for(message_id)

  windows_before = driver.window_ids
  driver.open_url(mid_url)
  sleep(opts[:settle]) unless opts[:dry_run]
  new_windows = driver.window_ids - windows_before

  actions.each do |name, selector, args|
    case selector
    when :ensure_flagged, :ensure_not_flagged
      want  = selector == :ensure_flagged
      flags = opts[:dry_run] ? [] : current_flags(eml_id)
      has   = flags.include?("\\Flagged")
      if has == want
        $stdout.puts "#{name}: already #{want ? "flagged" : "not flagged"} — no-op"
      else
        driver.perform("toggleFlag:")
        sleep(opts[:settle]) unless opts[:dry_run]
      end
    else
      driver.perform(selector, *args)
      sleep(opts[:settle]) unless opts[:dry_run]
    end
  end

  if opts[:verify] && !opts[:dry_run]
    sleep(opts[:settle])
    $stdout.puts "Flags now: #{current_flags(eml_id).inspect}"
  end

  unless opts[:keep_window] || opts[:dry_run] || new_windows.empty?
    driver.close_windows(new_windows)
  end
end

#parse_actions(argv, parser) ⇒ 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.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/mailmate/cli/modify.rb', line 103

def parse_actions(argv, parser)
  actions = []
  i = 0
  while i < argv.length
    name = argv[i]
    spec = ACTIONS[name]
    unless spec
      warn "mm-modify: unknown action #{name.inspect}"
      warn parser.help
      return nil
    end
    arg_count = spec.is_a?(Symbol) ? 0 : (spec[1] || 0)
    args = argv[(i + 1)...(i + 1 + arg_count)] || []
    if args.length < arg_count
      warn "mm-modify: action '#{name}' needs #{arg_count} arg(s); got #{args.length}"
      return nil
    end
    actions << [name, spec.first, args]
    i += 1 + arg_count
  end
  actions
end

#parse_options(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.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/mailmate/cli/modify.rb', line 66

def parse_options(argv)
  opts = { verify: false, dry_run: false, settle: 3.5, keep_window: false }
  parser = OptionParser.new do |o|
    o.banner = <<~BANNER
      Usage: mm-modify <id> <action> [args...] [<action> [args...]]...

      <id> can be either a local eml-id (e.g. 183715) or an RFC Message-ID
      (with or without angle brackets, e.g. <abc@example.com>). Quote the
      Message-ID in your shell so the < > aren't interpreted as redirection.

      Selects the message in MailMate (via the `mid:` URL) and runs one or
      more AppleScript key-binding selectors against the now-selected message.
      Multiple actions share one open+wait cycle.

      ACTIONS
        read                          Mark seen (\\Seen)
        unread                        Mark unseen
        flag                          Ensure \\Flagged is set (no-op if already)
        unflag                        Ensure \\Flagged is cleared (no-op if already)
        tag <name>                    Set IMAP keyword <name> (e.g. urgent, $Followup)
        untag <name>                  Remove IMAP keyword <name>
        clear-tags                    Remove all keywords
        archive                       Move to the archive mailbox
        move <mailbox-uuid>           Move to a specific mailbox (use UUID from MailMate)
        junk / not-junk               Mark as junk / not junk
        mute                          Toggle mute state
        delete                        Delete (move to trash)
    BANNER
    o.on("--verify", "After running, print the message's current flags") { opts[:verify] = true }
    o.on("--dry-run", "Print the actions; don't run") { opts[:dry_run] = true }
    o.on("--settle SECONDS", Float, "Sleep between operations (default 3.5)") { |s| opts[:settle] = s }
    o.on("--keep-window", "Don't close the spawned message-viewer window") { opts[:keep_window] = true }
  end
  parser.parse!(argv)
  [opts, parser]
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.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/mailmate/cli/modify.rb', line 33

def run(argv)
  opts, parser = parse_options(argv)
  input = argv.shift
  return usage_error(parser, "missing <id>") if input.nil? || input.empty?
  return usage_error(parser, "no actions given") if argv.empty?

  actions = parse_actions(argv, parser)
  return 2 if actions.nil?

  eml_id = Mailmate::EmlLookup.resolve_id(input)
  if eml_id.nil? || eml_id.zero?
    warn "Not found: #{input.inspect} (couldn't resolve as eml-id or Message-ID)"
    return 1
  end

  path = Mailmate::EmlLookup.path_for(eml_id)
  unless path
    warn "Not found: #{eml_id}.eml"
    return 1
  end

  message_id = Mailmate::HeaderReader.message_id(path)
  unless message_id
    warn "Could not find Message-ID in #{path}"
    return 1
  end

  warn_on_duplicates(message_id, eml_id)

  drive(eml_id, message_id, actions, opts)
  0
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.



183
184
185
186
187
# File 'lib/mailmate/cli/modify.rb', line 183

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

#warn_on_duplicates(message_id, eml_id) ⇒ 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.



126
127
128
129
130
131
132
133
134
135
# File 'lib/mailmate/cli/modify.rb', line 126

def warn_on_duplicates(message_id, eml_id)
  dup_ids = Mailmate::DuplicateScanner.eml_ids_for(message_id)
  return unless dup_ids.size > 1
  others = dup_ids.reject { |id| id == eml_id.to_i }
  warn "WARNING: Message-ID has #{dup_ids.size} copies in MailMate's tree."
  warn "         You targeted #{eml_id}.eml but the action may land on:"
  warn "           #{others.join(", ")}"
  warn "         (MailMate picks one candidate when resolving `mid:` URLs;"
  warn "          the choice is not deterministic by .eml id.)"
end