#!/usr/bin/env ruby
# This is a quick script to generate YASnippets from TextMate Snippets.
#
# I based the script off of a python script of a similar nature by
# Jeff Wheeler: http://nokrev.com
# http://code.nokrev.com/?p=snippet-copier.git;a=blob_plain;f=snippet_copier.py
#
# Usage
#
# Make sure you have the plist and the choice gem installed
# $ sudo gem install plist
# $ sudo gem install choice
#
# Usage: snippet_copier.rb [-dofp]
#
# Standard Options:
#     -d, --snippet-dir=PATH           Tells the program the directory to find the TextMate Snippets
#     -o, --output-dir=PATH            What directory to write the new YASnippets to
#     -f, --file=SNIPPET FILE NAME     A specific snippet that you want to copy or a glob for various files
#     -p, --print-pretty               Pretty prints multiple snippets when printing to standard out
#     -b, --convert-bindings           TextMate "keyEquivalent" keys are translated to YASnippet "# binding :" directives
#     -g, --info-plist=INFO            Attempt to derive group information from "info.plist" type-file PLIST
#
# Common options:
#         --help                       Show this message
require 'rubygems'
require 'plist'
require 'choice'
require 'ruby-debug' if $DEBUG

Choice.options do
  header ''
  header 'Standard Options:'

  option :snippet_dir do
    short '-d'
    long '--snippet-dir=PATH'
    desc 'Tells the program the directory to find the TextMate Snippets'
    default '.'
  end

  option :output_dir do
    short '-o'
    long '--output-dir=PATH'
    desc 'What directory to write the new YASnippets to'
  end

  option :snippet do
    short '-f'
    long '--file=SNIPPET FILE NAME'
    desc 'A specific snippet that you want to copy or a glob for various files'
    default '*.{tmSnippet,plist,tmMacro}'
  end

  option :print_pretty do
    short '-p'
    long '--pretty-print'
    desc 'Pretty prints multiple snippets when printing to standard out'
  end

  option :convert_bindings do
    short '-b'
    long '--convert-bindings'
    desc "TextMate \"keyEquivalent\" keys are translated to YASnippet \"# binding :\" directives"
  end

  option :info_plist do
    short '-g'
    long '--info-plist'
    desc "Attempt to derive group information from \"info.plist\" type-file PLIST"
  end

  separator ''
  separator 'Common options: '

  option :help do
    long '--help'
    desc 'Show this message'
  end
end


class TmSnippet
  @@known_substitutions=[
                         {
                           "${TM_RAILS_TEMPLATE_START_RUBY_EXPR}"   => "<%= ",
                           "${TM_RAILS_TEMPLATE_END_RUBY_EXPR}"     => " %>",
                           "${TM_RAILS_TEMPLATE_START_RUBY_INLINE}" => "<% ",
                           "${TM_RAILS_TEMPLATE_END_RUBY_INLINE}"   => " -%>",
                           "${TM_RAILS_TEMPLATE_END_RUBY_BLOCK}"    => "end" ,
                           "${0:$TM_SELECTED_TEXT}"                 => "$TM_SELECTED_TEXT$0",
                         },
                         { "$TM_SELECTED_TEXT"                      => "`yas/selected-text`" }
                        ]
  
  attr_reader :file

  # Makes a TmSnippet
  #
  # * file is the .tmsnippet/.plist file path relative to cwd 
  # * optional info is a Plist.parsed info.plist found in the bundle dir
  #
  def initialize(file,info=nil)
    @file    = file
    @info    = info
    @snippet = Plist::parse_xml(file)
  end

  def name
    @snippet["name"]
  end

  def group
    if @info
      submenus = @info["mainMenu"]["submenus"]
      container = submenus.keys.find do |submenu|
        submenus[submenu]["items"].member?(uuid)
      end
      submenus[container]["name"] if container;
    end
  end

  def uuid
    @snippet["uuid"]
  end

  def tab_trigger
    @snippet["tabTrigger"]
  end

  def key_equivalent
    @snippet["keyEquivalent"]
  end

  def content
    @snippet["content"]
  end

  def scope
    @snippet["scope"]
  end

  def to_yasnippet
    doc = "# -*- mode: snippet -*-\n"
    doc << "# type: command\n" unless self.content
    doc << "# key: #{self.tab_trigger}\n" if self.tab_trigger
    doc << "# contributor: Translated from TextMate Snippet\n"
    doc << "# name: #{self.name}\n"
    if self.key_equivalent
      doc << "#" unless Choice.choices.convert_bindings
      doc << "# binding: \"#{self.key_equivalent}\"\n"
    end
    if self.scope
      doc << "#" 
      doc << "# condition: \"#{self.scope}\"\n"
    end
    doc << "# --\n"
    if self.content
      @@known_substitutions.each {|level| level.each_pair { |k, v| self.content.gsub!(k,v) }} 
      doc << "#{self.content}"
    end
    doc
  end

  def canonicalize(filename)
    invalid_char = /[^ a-z_0-9.+=~(){}'"'"'`&#,-]/i

    filename.
      gsub(invalid_char, '').  # remove invalid characters
      gsub(/ {2,}/,' ').       # squeeze repeated spaces into a single one
      rstrip                   # remove trailing whitespaces
  end

  def yasnippet_file(basedir)
    # files cannot end with dots (followed by spaces) on windows
    basedir = File.join(basedir,group.gsub(/[.]+ *$/,'')) if group
    File.join(basedir,canonicalize(@file[0, @file.length-File.extname(@file).length]) + ".yasnippet")
  end

end



if $0 == __FILE__ 
  info_plist = Plist::parse_xml(Choice.choices.info_plist) if Choice.choices.info_plist

  if Choice.choices.output_dir
    FileUtils.mkdir_p Choice.choices.output_dir
    FileUtils.touch File.join(Choice.choices.output_dir, ".yas-make-groups")
    FileUtils.touch File.join(Choice.choices.output_dir, ".yas-ignore-filenames-as-triggers")
  end
  
  original_dir = Dir.pwd
  Dir.chdir Choice.choices.snippet_dir
  snippet_files_glob = File.join("**", Choice.choices.snippet)
  snippet_files = Dir.glob(snippet_files_glob)

  puts "Will try to convert #{snippet_files.length} snippets...\n"
  snippet_files.each do |file|
    begin
      puts "Processing \"#{File.join(Choice.choices.snippet_dir,file)}\"\n"
      snippet = TmSnippet.new(file,info_plist)
      if Choice.choices.output_dir
        file_to_create = snippet.yasnippet_file(File.join(original_dir, Choice.choices.output_dir))
        FileUtils.mkdir_p(File.dirname(file_to_create))
        File.open(file_to_create, 'w') do |f|
          f.write(snippet.to_yasnippet)
        end
      else
        if Choice.choices.print_pretty
          puts "--------------------------------------------"
        end
        puts snippet.to_yasnippet
        if Choice.choices.print_pretty
          puts "--------------------------------------------"
        end
        puts "\n\n"
      end
    rescue Exception => e
      $stderr.puts "Oops... #{e.class}:#{e.message}"
    end
  end
end
