Embedding a ruby script in AppleScript

applescriptruby

I have made a folder action AppleScript which mainly gets the folder name with path of the attached folder and calls a ruby script with the pathname as the argument. This works perfectly but is there a way to embed this ruby file inside the AppleScript as a variable, so I don’t have to deal with two files?

I am new to this so if an example is provided would be great.
Thanks.

I am trying the code given below but getting an error "-e:82: syntax error, unexpected end-of-input, expecting keyword_end" number 1.

As instructed I have copied the file and inserted the escape \ character, not changed any code. the third commented line will run the program without any error.

set folder_name to quoted form of "/Volumes/HackSSD/Testing Space"

display dialog folder_name

--set makeDateFolders to "/Users/raj/Documents/ivfolders.rb " & folder_name

set rubyScript to "# ivfolder.rb
require 'set'
require 'benchmark'
require 'date'
require 'fileutils'
require 'open3'

raise ArgumentError, 'Expecting single command-line value' if ARGV.length > 1

def file_created_date(ivfile)
  # Not all images have a uniform EXIF/IPTC tag name for created date/time.
  # Use exiftool if installed and attempt three different date tags. If these
  # find no creation date, return the System creation date.
  # returns created_date as 'yyyy-mm-dd' string

  exiftool = '/usr/local/bin/exiftool'
  create_date = ''

  if File.exist?(exiftool)
    create_date, = Open3.capture2(exiftool, '-s3', '-d', '%F',
                                  '-DateTimeOriginal',
                                  '-FileModifyDate',
                                  '-CreateDate',
                                  ivfile)[0]

    if create_date && create_date.count(\"\\n\") > 1
      # n-tuple dates found, usually the same, but take oldest if present.
      datestr = create_date.split(\"\\n\").min
    elsif create_date
      # one date, likely the FileModifyDate. Remove trailing newline
      datestr = create_date.chomp
    end
    return datestr unless create_date.empty?
  end
  File.birthtime(ivfile).strftime('%F') if create_date.empty?
end

# check folder name to see if it is in YYYY-MM-DD format
def valid_date?(str, format = '%F')
  Date.strptime(str, format)
rescue ArgumentError
  false
end


# file excluded if not in this set.
extensions = Set.new(['.cr2', '.cr3', '.nef', 'dng', '.arw', '.gif', '.jpg',
                      '.jpeg', '.jp2', '.png', '.ai', '.psd', '.svg', '.tif',
                      '.tiff', '.heic', '.heif', '.mp4', '.hevc', '.mov',
                      '.m4v', '.avi', '.mts', '.mpg'])

folders = Set.new
matched_files = Set.new
filecnt = 0
foldercnt = 0

elapsed = Benchmark.realtime do
  start_folder = File.expand_path(ARGV.first)

  Dir.chdir(start_folder) do
    Dir.glob(\"#{start_folder}/*.*\").select do |f|
      matched_files << f if extensions.include?(File.extname(f.downcase))
    end

    # if previous run, collect existing folders matching yyyy-mm-dd
    Dir.glob(\"#{start_folder}/*\").select do |f|
      z = File.basename(f)
      folders << z if valid_date?(z) && z.length == 10
    end

    matched_files.each do |f|
      # create_date = File.birthtime(f).strftime('%F') # yyyy-mm-dd
      create_date = file_created_date(f)
      FileUtils.mkdir(create_date, mode: 0o0755, verbose: false) 
                unless folders.include?(create_date)
      folders << create_date
      FileUtils.mv(File.basename(f), create_date, verbose: false)
    end
    filecnt = matched_files.count
    foldercnt = folders.count
  end
end"

do shell script ("ruby -e " & (quoted form of rubyScript) & space & folder_name) -- run it

In my folder action script which is working fine I am taking care of when the folder action is triggered after a folder is created, the actual working script is given below. As mentioned earlier I want to embed the ruby script to not bother with an external file.
The Working Folder Action Script

on adding folder items to this_folder after receiving added_items
    try


        repeat with theCurrent_item in added_items
            set filePosixPath to quoted form of (POSIX path of (theCurrent_item as alias))
            set fileType to (do shell script "file -b " & filePosixPath) --Test if it is a directory
            if fileType = "directory" then
                return
            end if

        end repeat


        set folder_name to quoted form of (POSIX path of (this_folder as alias))

        set makeDateFolders to "/Users/raj/Documents/ivfolders.rb " & folder_name


        do shell script makeDateFolders

    end try
end adding folder items to

Best Answer

A Ruby script can be placed into an AppleScript string for use with do shell script, but note that special characters in the string such as backslash and double quotes will need to be escaped with a backslash. There are a couple of different arrangements you can use, my preference is a here document, for example:

on run -- Script Editor or application double-clicked
    doStuff for (choose file with multiple selections allowed)
end run

on open theFiles -- items dropped onto the application
    doStuff for theFiles
end open

on adding folder items to this_folder after receiving these_items -- folder action
    doStuff for these_items
end adding folder items to

to doStuff for someFiles
    set argString to "" -- create an argument string from the item list
    repeat with anItem in someFiles -- quote and separate
        set anItem to POSIX path of anItem
        set argString to argString & quoted form of anItem & space
    end repeat

    set heredoc to "<<-SCRIPT
# place your (escaped for AppleScript strings) code here, for example:
ARGV.each_with_index do |arg, index|
  puts \"Argument #{index}:  #{arg}\"
end
SCRIPT"

    set scriptResult to (do shell script "/usr/bin/env ruby - " & argString & heredoc) -- run it
    return paragraphs of scriptResult -- or whatever
end doStuff

The -e option can also be used instead of the here document (see the Ruby man page), for example:

    set rubyScript to "# place your (escaped for AppleScript strings) code here, for example:
ARGV.each_with_index do |arg, index|
  puts \"Argument #{index}:  #{arg}\"
end"

    set scriptResult to (do shell script "/usr/bin/env ruby -e " & (quoted form of rubyScript) & space & argString) -- run it