Wow, I am not the only such geek %)
My solution of the similar goal is 7 yo and it'll go to school soon.
$ git log lib/osascript.rb
commit 1f39d1d42b499d1424af1fa5a109ecd6ab219563 (HEAD -> master)
Author: Anton
Date: Thu Jun 11 08:47:12 2015 +0300
# @example Simple
# Osascript.new(<<~SCPT.freeze).()
# activate application "Finder"
# SCPT
#
# @example JSC with args
# # The script takes 2 arguments: directory path & image path
# # to set a folder icon to the given directory.
# script = Osascript.new(<<-JS.freeze, lang: 'JavaScript')
# ObjC.import("Cocoa");
# function run(input) {
# var target_path = input[0].toString();
# var source_image = $.NSImage.alloc.initWithContentsOfFile(input[1].toString());
# var result = $.NSWorkspace.sharedWorkspace.setIconForFileOptions(source_image, target_path, 0);
# return target_path;
# }
# JS
# script.(target_dir, folder_icon)
class Osascript
attr_accessor :script
def initialize(script = nil, lang: "AppleScript")
@script = block_given? ? yield : script
@lang = lang
end
def call(*other)
handle_errors do
cmd = ["/usr/bin/env", "osascript", *params(*other)]
IO.popen cmd, "r+", 2 => %i(child out) do |io|
io.write script
io.close_write
io.readlines
end
end
end
def params(*args)
["-l", @lang].tap { |e| e.concat(args.unshift(?-)) unless args.empty? }
end
ERROR_PATTERN = /(?<=execution error: )(.+?)(?=$)/
USER_CANCELLED_PATTERN = /user canceled/i
NL = "\n"
private
def handle_errors
yield().each_with_object([]) do |line, buf|
line.match(ERROR_PATTERN) { |m| raise error_for(m[0]), m[0], caller(4) }
buf << line.strip
end.join(NL)
end
def error_for(msg)
USER_CANCELLED_PATTERN.match?(msg) ? UserCanceled : ExecutionError
end
class ExecutionError < RuntimeError
CAPTURE_MSG_AND_CODE = /(.+?) \((-?\d+?)\)$/
attr_reader :code
def initialize(msg)
msg.match(CAPTURE_MSG_AND_CODE) { |m| msg, @code, * = m.captures }
super(msg)
end
end
UserCanceled = Class.new(ExecutionError)
end
I've wrote it when I've known that cool syntax hook at the first time — an ability to pass only the opening heredoc word in closed parenthesis on single line and you can ducktype it infinitely.
Oh, and I just called in mind one more thing about heredoc: there is some tricky heredoc syntax in core source file forwardable.rb
which brakes my brain when I try to understand it:
if _valid_method?(method)
loc, = caller_locations(2,1)
pre = "_ ="
mesg = "#{Module === obj ? obj : obj.class}\##{ali} at #{loc.path}:#{loc.lineno} forwarding to private method "
method_call = "#{<<-"begin;"}\n#{<<-"end;".chomp}"
begin;
unless defined? _.#{method}
::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1
_#{method_call}
else
_.#{method}(*args, &block)
end
end;
end
_compile_method("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1)
begin;
proc do
def #{ali}(*args, &block)
#{pre}
begin
#{accessor}
end#{method_call}
end
end
end;
Pretty cryptic, isn't it?