Module: OmnifocusMcp::Infrastructure::AppleScript

Defined in:
lib/omnifocus_mcp/infrastructure/apple_script.rb

Overview

Composable AppleScript fragments for write-side primitives. rubocop:disable Metrics/ModuleLength

Constant Summary collapse

ITEM_TYPES =
%w[task project].freeze
LOOKUP_KINDS =
%i[folder project].freeze

Class Method Summary collapse

Class Method Details

.escape(value) ⇒ Object

Escape ‘“` and `` so `value` is safe inside an AppleScript double-quoted string. CR/LF collapse to a single space.



23
24
25
26
27
# File 'lib/omnifocus_mcp/infrastructure/apple_script.rb', line 23

def escape(value)
  value.to_s
       .gsub(/["\\]/) { |m| "\\#{m}" }
       .gsub(/[\r\n]/, " ")
end

.find_item(var:, item_type:, id:, name:) ⇒ Object

Find a task or project, setting ‘var` to the located object or `missing value` when not found.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/omnifocus_mcp/infrastructure/apple_script.rb', line 47

def find_item(var:, item_type:, id:, name:)
  unless ITEM_TYPES.include?(item_type)
    raise ArgumentError, "item_type must be one of #{ITEM_TYPES.inspect}, got #{item_type.inspect}"
  end

  collection = "flattened #{item_type}"
  sections = [
    ["set #{var} to missing value"],
    id_lookup_lines(var: var, collection: collection, item_type: item_type, id: id),
    name_lookup_lines(var: var, collection: collection, item_type: item_type, name: name, fallback: !id.empty?)
  ]

  "#{sections.flatten.join("\n")}\n"
end

.generate_folder_lookup_script(raw_folder_path:, var_name:, error_return_json:) ⇒ Object

Generate AppleScript that resolves a folder by path or simple name.



81
82
83
84
85
86
87
88
# File 'lib/omnifocus_mcp/infrastructure/apple_script.rb', line 81

def generate_folder_lookup_script(raw_folder_path:, var_name:, error_return_json:)
  generate_lookup_script(
    kind: :folder,
    raw_path: raw_folder_path,
    var_name: var_name,
    error_return_json: error_return_json
  )
end

.generate_lookup_script(kind:, raw_path:, var_name:, error_return_json:) ⇒ Object

Unified entry point for folder/project lookup scripts.



101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/omnifocus_mcp/infrastructure/apple_script.rb', line 101

def generate_lookup_script(kind:, raw_path:, var_name:, error_return_json:)
  unless LOOKUP_KINDS.include?(kind)
    raise ArgumentError, "kind must be one of #{LOOKUP_KINDS.inspect}, got #{kind.inspect}"
  end

  components = raw_path.split("/")
                       .reject(&:empty?)
  return "set #{var_name} to missing value" if components.empty?

  escaped_components = components.map { |c| escape(c) }
  builder = components.length == 1 ? :simple : :nested
  send(:"#{builder}_#{kind}_lookup", var_name:, escaped_components:, error_return_json:)
end

.generate_project_lookup_script(raw_project_path:, var_name:, error_return_json:) ⇒ Object

Generate AppleScript that resolves a project by path or simple name.



91
92
93
94
95
96
97
98
# File 'lib/omnifocus_mcp/infrastructure/apple_script.rb', line 91

def generate_project_lookup_script(raw_project_path:, var_name:, error_return_json:)
  generate_lookup_script(
    kind: :project,
    raw_path: raw_project_path,
    var_name: var_name,
    error_return_json: error_return_json
  )
end

.indent(text:, prefix:) ⇒ Object

Prefix every non-empty line of ‘text` with `prefix`. Lines that are blank or only-whitespace are passed through unchanged so the result diffs cleanly.



15
16
17
18
19
# File 'lib/omnifocus_mcp/infrastructure/apple_script.rb', line 15

def indent(text:, prefix:)
  text.each_line
      .map { |line| line.strip.empty? ? line : "#{prefix}#{line}" }
      .join
end

.tag_assignment(item_var:, tag_name:) ⇒ Object

Add an existing tag to ‘item_var`, creating it if it does not exist.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/omnifocus_mcp/infrastructure/apple_script.rb', line 63

def tag_assignment(item_var:, tag_name:)
  <<~APPLESCRIPT.chomp
    try
      set theTag to first flattened tag where name = "#{tag_name}"
      add theTag to tags of #{item_var}
    on error
      -- Tag might not exist, try to create it
      try
        set theTag to make new tag with properties {name:"#{tag_name}"}
        add theTag to tags of #{item_var}
      on error
        -- Could not create or add tag
      end try
    end try
  APPLESCRIPT
end

.tell_document(body) ⇒ Object

Wrap a script ‘body` in the standard OmniFocus front document envelope used by write-side primitives.



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/omnifocus_mcp/infrastructure/apple_script.rb', line 31

def tell_document(body)
  <<~APPLESCRIPT
    try
      tell application "OmniFocus"
        tell front document
    #{indent(text: body.chomp, prefix: "      ")}
        end tell
      end tell
    on error errorMessage
      return "{\\"success\\":false,\\"error\\":\\"" & errorMessage & "\\"}"
    end try
  APPLESCRIPT
end