summaryrefslogtreecommitdiffstats
path: root/emacs.d/lisp/yasnippet/extras/textmate_import.rb
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xemacs.d/lisp/yasnippet/extras/textmate_import.rb517
1 files changed, 517 insertions, 0 deletions
diff --git a/emacs.d/lisp/yasnippet/extras/textmate_import.rb b/emacs.d/lisp/yasnippet/extras/textmate_import.rb
new file mode 100755
index 0000000..639e508
--- /dev/null
+++ b/emacs.d/lisp/yasnippet/extras/textmate_import.rb
@@ -0,0 +1,517 @@
+#!/usr/bin/ruby
+# -*- coding: utf-8 -*-
+#!/usr/bin/env ruby
+# -*- coding: utf-8 -*-
+# textmate_import.rb --- import textmate snippets
+#
+# Copyright (C) 2009 Rob Christie, 2010 João Távora
+#
+# 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
+#
+# Use textmate_import.rb --help to get usage information.
+
+require 'rubygems'
+require 'plist'
+require 'choice'
+require 'fileutils'
+require 'shellwords' # String#shellescape
+require 'ruby-debug' if $DEBUG
+
+Choice.options do
+ header ''
+ header 'Standard Options:'
+
+ option :bundle_dir do
+ short '-d'
+ long '--bundle-dir=PATH'
+ desc 'Tells the program the directory to find the TextMate bundle directory'
+ default '.'
+ end
+
+ option :output_dir do
+ short '-o'
+ long '--output-dir=PATH'
+ desc 'What directory to write the new YASnippets to'
+ default './textmate_import'
+ 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,tmCommand,plist,tmMacro}'
+ end
+
+ option :print_pretty do
+ short '-p'
+ long '--pretty-print'
+ desc 'Pretty prints multiple snippets when printing to standard out'
+ end
+
+ option :quiet do
+ short '-q'
+ long '--quiet'
+ desc 'Be quiet.'
+ 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=PLIST'
+ desc "Specify a plist file derive menu information from defaults to \"bundle-dir\"/info.plist"
+ end
+
+ separator ''
+ separator 'Common options: '
+
+ option :help do
+ long '--help'
+ desc 'Show this message'
+ end
+end
+
+# Represents and is capable of outputting the representation of a
+# TextMate menu in terms of `yas/define-menu'
+#
+class TmSubmenu
+
+ @@excluded_items = [];
+ def self.excluded_items; @@excluded_items; end
+
+ attr_reader :items, :name
+ def initialize(name, hash)
+ @items = hash["items"]
+ @name = name
+ end
+
+ def to_lisp(allsubmenus,
+ deleteditems,
+ indent = 0,
+ thingy = ["(", ")"])
+
+ first = true;
+
+ string = ""
+ separator_useless = true;
+ items.each do |uuid|
+ if deleteditems.index(uuid)
+ $stderr.puts "#{uuid} has been deleted!"
+ next
+ end
+ string += "\n"
+ string += " " * indent
+ string += (first ? thingy[0] : (" " * thingy[0].length))
+
+ submenu = allsubmenus[uuid]
+ snippet = TmSnippet::snippets_by_uid[uuid]
+ unimplemented = TmSnippet::unknown_substitutions["content"][uuid]
+ if submenu
+ str = "(yas/submenu "
+ string += str + "\"" + submenu.name + "\""
+ string += submenu.to_lisp(allsubmenus, deleteditems,
+ indent + str.length + thingy[0].length)
+ elsif snippet and not unimplemented
+ string += ";; " + snippet.name + "\n"
+ string += " " * (indent + thingy[0].length)
+ string += "(yas/item \"" + uuid + "\")"
+ separator_useless = false;
+ elsif snippet and unimplemented
+ string += ";; Ignoring " + snippet.name + "\n"
+ string += " " * (indent + thingy[0].length)
+ string += "(yas/ignore-item \"" + uuid + "\")"
+ separator_useless = true;
+ elsif (uuid =~ /---------------------/)
+ string += "(yas/separator)" unless separator_useless
+ end
+ first = false;
+ end
+ string += ")"
+ string += thingy[1]
+
+ return string
+ end
+
+ def self.main_menu_to_lisp (parsed_plist, modename)
+ mainmenu = parsed_plist["mainMenu"]
+ deleted = parsed_plist["deleted"]
+
+ root = TmSubmenu.new("__main_menu__", mainmenu)
+ all = {}
+
+ mainmenu["submenus"].each_pair do |k,v|
+ all[k] = TmSubmenu.new(v["name"], v)
+ end
+
+ excluded = mainmenu["excludedItems"] + TmSubmenu::excluded_items
+ closing = "\n '("
+ closing+= excluded.collect do |uuid|
+ "\"" + uuid + "\""
+ end.join( "\n ") + "))"
+
+ str = "(yas/define-menu "
+ return str + "'#{modename}" + root.to_lisp(all,
+ deleted,
+ str.length,
+ ["'(" , closing])
+ end
+end
+
+
+# Represents a textmate snippet
+#
+# - @file is the .tmsnippet/.plist file path relative to cwd
+#
+# - optional @info is a Plist.parsed info.plist found in the bundle dir
+#
+# - @@snippets_by_uid is where one can find all the snippets parsed so
+# far.
+#
+#
+class SkipSnippet < RuntimeError; end
+class TmSnippet
+ @@known_substitutions = {
+ "content" => {
+ "${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}" => "${0:`yas/selected-text`}",
+ /\$\{(\d+)\}/ => "$\\1",
+ "${1:$TM_SELECTED_TEXT}" => "${1:`yas/selected-text`}",
+ "${2:$TM_SELECTED_TEXT}" => "${2:`yas/selected-text`}",
+ '$TM_SELECTED_TEXT' => "`yas/selected-text`",
+ %r'\$\{TM_SELECTED_TEXT:([^\}]*)\}' => "`(or (yas/selected-text) \"\\1\")`",
+ %r'`[^`]+\n[^`]`' => Proc.new {|uuid, match| "(yas/multi-line-unknown " + uuid + ")"}},
+ "condition" => {
+ /^source\..*$/ => "" },
+ "binding" => {},
+ "type" => {}
+ }
+
+ def self.extra_substitutions; @@extra_substitutions; end
+ @@extra_substitutions = {
+ "content" => {},
+ "condition" => {},
+ "binding" => {},
+ "type" => {}
+ }
+
+ def self.unknown_substitutions; @@unknown_substitutions; end
+ @@unknown_substitutions = {
+ "content" => {},
+ "condition" => {},
+ "binding" => {},
+ "type" => {}
+ }
+
+ @@snippets_by_uid={}
+ def self.snippets_by_uid; @@snippets_by_uid; end
+
+ def initialize(file,info=nil)
+ @file = file
+ @info = info
+ @snippet = TmSnippet::read_plist(file)
+ @@snippets_by_uid[self.uuid] = self;
+ raise SkipSnippet.new "not a snippet/command/macro." unless (@snippet["scope"] || @snippet["command"])
+ raise SkipSnippet.new "looks like preferences."if @file =~ /Preferences\//
+ raise RuntimeError.new("Cannot convert this snippet #{file}!") unless @snippet;
+ end
+
+ def name
+ @snippet["name"]
+ end
+
+ def uuid
+ @snippet["uuid"]
+ end
+
+ def key
+ @snippet["tabTrigger"]
+ end
+
+ def condition
+ yas_directive "condition"
+ end
+
+ def type
+ override = yas_directive "type"
+ if override
+ return override
+ else
+ return "# type: command\n" if @file =~ /(Commands\/|Macros\/)/
+ end
+ end
+
+ def binding
+ yas_directive "binding"
+ end
+
+ def content
+ known = @@known_substitutions["content"]
+ extra = @@extra_substitutions["content"]
+ if direct = extra[uuid]
+ return direct
+ else
+ ct = @snippet["content"]
+ if ct
+ known.each_pair do |k,v|
+ if v.respond_to? :call
+ ct.gsub!(k) {|match| v.call(uuid, match)}
+ else
+ ct.gsub!(k,v)
+ end
+ end
+ extra.each_pair do |k,v|
+ ct.gsub!(k,v)
+ end
+ # the remaining stuff is an unknown substitution
+ #
+ [ %r'\$\{ [^/\}\{:]* / [^/]* / [^/]* / [^\}]*\}'x ,
+ %r'\$\{[^\d][^}]+\}',
+ %r'`[^`]+`',
+ %r'\$TM_[\w_]+',
+ %r'\(yas/multi-line-unknown [^\)]*\)'
+ ].each do |reg|
+ ct.scan(reg) do |match|
+ @@unknown_substitutions["content"][match] = self
+ end
+ end
+ return ct
+ else
+ @@unknown_substitutions["content"][uuid] = self
+ TmSubmenu::excluded_items.push(uuid)
+ return "(yas/unimplemented)"
+ end
+ end
+ end
+
+ def to_yas
+ doc = "# -*- mode: snippet -*-\n"
+ doc << (self.type || "")
+ doc << "# uuid: #{self.uuid}\n"
+ doc << "# key: #{self.key}\n" if self.key
+ doc << "# contributor: Translated from textmate snippet by PROGRAM_NAME\n"
+ doc << "# name: #{self.name}\n"
+ doc << (self.binding || "")
+ doc << (self.condition || "")
+ doc << "# --\n"
+ doc << (self.content || "(yas/unimplemented)")
+ doc
+ end
+
+ def self.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 yas_file()
+ File.join(TmSnippet::canonicalize(@file[0, @file.length-File.extname(@file).length]) + ".yasnippet")
+ end
+
+ def self.read_plist(xml_or_binary)
+ begin
+ parsed = Plist::parse_xml(xml_or_binary)
+ return parsed if parsed
+ raise ArgumentError.new "Probably in binary format and parse_xml is very quiet..."
+ rescue StandardError => e
+ if (system "plutil -convert xml1 #{xml_or_binary.shellescape} -o /tmp/textmate_import.tmpxml")
+ return Plist::parse_xml("/tmp/textmate_import.tmpxml")
+ else
+ raise RuntimeError.new "plutil failed miserably, check if you have it..."
+ end
+ end
+ end
+
+ private
+
+ @@yas_to_tm_directives = {"condition" => "scope", "binding" => "keyEquivalent", "key" => "tabTrigger"}
+ def yas_directive(yas_directive)
+ #
+ # Merge "known" hardcoded substitution with "extra" substitutions
+ # provided in the .yas-setup.el file.
+ #
+ merged = @@known_substitutions[yas_directive].
+ merge(@@extra_substitutions[yas_directive])
+ #
+ # First look for an uuid-based direct substitution for this
+ # directive.
+ #
+ if direct = merged[uuid]
+ return "# #{yas_directive}: "+ direct + "\n" unless direct.empty?
+ else
+ tm_directive = @@yas_to_tm_directives[yas_directive]
+ val = tm_directive && @snippet[tm_directive]
+ if val and !val.delete(" ").empty? then
+ #
+ # Sort merged substitutions by length (bigger ones first,
+ # regexps last), and apply them to the value gotten for plist.
+ #
+ merged.sort_by do |what, with|
+ if what.respond_to? :length then -what.length else 0 end
+ end.each do |sub|
+ if val.gsub!(sub[0],sub[1])
+ return "# #{yas_directive}: "+ val + "\n" unless val.empty?
+ end
+ end
+ #
+ # If we get here, no substitution matched, so mark this an
+ # unknown substitution.
+ #
+ @@unknown_substitutions[yas_directive][val] = self
+ return "## #{yas_directive}: \""+ val + "\n"
+ end
+ end
+ end
+
+end
+
+
+if __FILE__ == $PROGRAM_NAME
+ # Read the the bundle's info.plist if can find it/guess it
+ #
+ info_plist_file = Choice.choices.info_plist || File.join(Choice.choices.bundle_dir,"info.plist")
+ info_plist = TmSnippet::read_plist(info_plist_file) if info_plist_file and File.readable? info_plist_file;
+
+ # Calculate the mode name
+ #
+ modename = File.basename Choice.choices.output_dir || "major-mode-name"
+
+ # Read in .yas-setup.el looking for the separator between auto-generated
+ #
+ original_dir = Dir.pwd
+ yas_setup_el_file = File.join(original_dir, Choice.choices.output_dir, ".yas-setup.el")
+ separator = ";; --**--"
+ whole, head , tail = "", "", ""
+ if File::exists? yas_setup_el_file
+ File.open yas_setup_el_file, 'r' do |file|
+ whole = file.read
+ head , tail = whole.split(separator)
+ end
+ else
+ head = ";; .yas-setup.el for #{modename}\n" + ";; \n"
+ end
+
+ # Now iterate the tail part to find extra substitutions
+ #
+ tail ||= ""
+ head ||= ""
+ directive = nil
+ # puts "get this head #{head}"
+ head.each_line do |line|
+ case line
+ when /^;; Substitutions for:(.*)$/
+ directive = $~[1].strip
+ # puts "found the directove #{directive}"
+ when /^;;(.*)[ ]+=yyas>(.*)$/
+ replacewith = $~[2].strip
+ lookfor = $~[1]
+ lookfor.gsub!(/^[ ]*/, "")
+ lookfor.gsub!(/[ ]*$/, "")
+ # puts "found this wonderful substitution for #{directive} which is #{lookfor} => #{replacewith}"
+ unless !directive or replacewith =~ /yas\/unknown/ then
+ TmSnippet.extra_substitutions[directive][lookfor] = replacewith
+ end
+ end
+ end
+
+ # Glob snippets into snippet_files, going into subdirs
+ #
+ Dir.chdir Choice.choices.bundle_dir
+ snippet_files_glob = File.join("**", Choice.choices.snippet)
+ snippet_files = Dir.glob(snippet_files_glob)
+
+ # Attempt to convert each snippet files in snippet_files
+ #
+ puts "Will try to convert #{snippet_files.length} snippets...\n" unless Choice.choices.quiet
+
+
+ # Iterate the globbed files
+ #
+ snippet_files.each do |file|
+ begin
+ puts "Processing \"#{File.join(Choice.choices.bundle_dir,file)}\"\n" unless Choice.choices.quiet
+ snippet = TmSnippet.new(file,info_plist)
+
+ if
+ file_to_create = File.join(original_dir, Choice.choices.output_dir, snippet.yas_file)
+ FileUtils.mkdir_p(File.dirname(file_to_create))
+ File.open(file_to_create, 'w') do |f|
+ f.write(snippet.to_yas)
+ end
+ else
+ if Choice.choices.print_pretty
+ puts "--------------------------------------------"
+ end
+ puts snippet.to_yas if Choice.choices.print_pretty or not Choice.choices.info_plist
+ if Choice.choices.print_pretty
+ puts "--------------------------------------------\n\n"
+ end
+ end
+ rescue SkipSnippet => e
+ $stdout.puts "Skipping \"#{file}\": #{e.message}"
+ rescue RuntimeError => e
+ $stderr.puts "Oops.... \"#{file}\": #{e.message}"
+ $strerr.puts "#{e.backtrace.join("\n")}" unless Choice.choices.quiet
+ end
+ end
+
+ # Attempt to decypher the menu
+ #
+ menustr = TmSubmenu::main_menu_to_lisp(info_plist, modename) if info_plist
+ puts menustr if $DEBUG
+
+ # Write some basic .yas-* files
+ #
+ if Choice.choices.output_dir
+ FileUtils.mkdir_p Choice.choices.output_dir
+ FileUtils.touch File.join(original_dir, Choice.choices.output_dir, ".yas-make-groups") unless menustr
+ FileUtils.touch File.join(original_dir, Choice.choices.output_dir, ".yas-ignore-filenames-as-triggers")
+
+ # Now, output head + a new tail in (possibly new) .yas-setup.el
+ # file
+ #
+ File.open yas_setup_el_file, 'w' do |file|
+ file.puts head
+ file.puts separator
+ file.puts ";; Automatically generated code, do not edit this part"
+ file.puts ";; "
+ file.puts ";; Translated menu"
+ file.puts ";; "
+ file.puts menustr
+ file.puts
+ file.puts ";; Unknown substitutions"
+ file.puts ";; "
+ ["content", "condition", "binding"].each do |type|
+ file.puts ";; Substitutions for: #{type}"
+ file.puts ";; "
+ # TmSnippet::extra_substitutions[type].
+ # each_pair do |k,v|
+ # file.puts ";; " + k + "" + (" " * [1, 90-k.length].max) + " =yyas> " + v
+ # end
+ unknown = TmSnippet::unknown_substitutions[type];
+ unknown.keys.uniq.each do |k|
+ file.puts ";; # as in " + unknown[k].yas_file
+ file.puts ";; " + k + "" + (" " * [1, 90-k.length].max) + " =yyas> (yas/unknown)"
+ file.puts ";; "
+ end
+ file.puts ";; "
+ file.puts
+ end
+ file.puts ";; .yas-setup.el for #{modename} ends here"
+ end
+ end
+end