PageRenderTime 57ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/Managed/Bundles/Bundle Support.tmbundle/Support/shared/lib/scriptmate.rb

https://bitbucket.org/rafaelmoreira/textmate-config
Ruby | 320 lines | 291 code | 18 blank | 11 comment | 2 complexity | 9fa4fa2ac7361d8d08d61eff78021c54 MD5 | raw file
  1. # encoding: utf-8
  2. SUPPORT_LIB = ENV['TM_SUPPORT_PATH'] + '/lib/'
  3. require SUPPORT_LIB + 'escape'
  4. require SUPPORT_LIB + 'web_preview'
  5. require SUPPORT_LIB + 'io'
  6. require SUPPORT_LIB + 'tm/tempfile'
  7. require 'cgi'
  8. require 'fcntl'
  9. $KCODE = 'u'
  10. require 'jcode'
  11. $SCRIPTMATE_VERSION = "$Revision$"
  12. def my_popen3(*cmd) # returns [stdin, stdout, strerr, pid]
  13. pw = IO::pipe # pipe[0] for read, pipe[1] for write
  14. pr = IO::pipe
  15. pe = IO::pipe
  16. # F_SETOWN = 6, ideally this would be under Fcntl::F_SETOWN
  17. pw[0].fcntl(6, ENV['TM_PID'].to_i) if ENV.has_key? 'TM_PID'
  18. pid = fork{
  19. pw[1].close
  20. STDIN.reopen(pw[0])
  21. pw[0].close
  22. pr[0].close
  23. STDOUT.reopen(pr[1])
  24. pr[1].close
  25. pe[0].close
  26. STDERR.reopen(pe[1])
  27. pe[1].close
  28. tm_interactive_input = SUPPORT_LIB + '/tm_interactive_input.dylib'
  29. if (File.exists? tm_interactive_input)
  30. dil = ENV['DYLD_INSERT_LIBRARIES']
  31. ENV['DYLD_INSERT_LIBRARIES'] = (dil) ? "#{tm_interactive_input}:#{dil}" : tm_interactive_input unless (dil =~ /#{tm_interactive_input}/)
  32. ENV['DYLD_FORCE_FLAT_NAMESPACE'] = "1"
  33. ENV['TM_INTERACTIVE_INPUT'] = 'AUTO|ECHO'
  34. end
  35. exec(*cmd)
  36. }
  37. pw[0].close
  38. pr[1].close
  39. pe[1].close
  40. pw[1].sync = true
  41. [pw[1], pr[0], pe[0], pid]
  42. end
  43. def cmd_mate(cmd)
  44. # cmd can be either a string or a list of strings to be passed to Popen3
  45. # this command will write the output of the `cmd` on STDOUT, formatted in
  46. # HTML.
  47. c = UserCommand.new(cmd)
  48. m = CommandMate.new(c)
  49. m.emit_html
  50. end
  51. class UserCommand
  52. attr_reader :display_name, :path
  53. def initialize(cmd)
  54. @cmd = cmd
  55. end
  56. def run
  57. stdin, stdout, stderr, pid = my_popen3(@cmd)
  58. return stdout, stderr, nil, pid
  59. end
  60. def to_s
  61. @cmd.to_s
  62. end
  63. end
  64. class CommandMate
  65. def initialize (command)
  66. # the object `command` needs to implement a method `run`. `run` should
  67. # return an array of three file descriptors [stdout, stderr, stack_dump].
  68. @error = ""
  69. @command = command
  70. STDOUT.sync = true
  71. @mate = self.class.name
  72. end
  73. protected
  74. def filter_stdout(str)
  75. # strings from stdout are passed through this method before being printed
  76. # txmt://open?line=3&url=file:///var/folders/Gx/Gxr7D8ILFba5bZaZC6rrCE%2B%2B%2BTQ/-Tmp-/untitled_m16p.py
  77. str = htmlize(str).gsub(/\<br\>/, "<br>\n")
  78. end
  79. def filter_stderr(str)
  80. # strings from stderr are passwed through this method before printing
  81. "<span style='color: red'>#{htmlize str}</span>".gsub(/\<br\>/, "<br>\n")
  82. end
  83. def emit_header
  84. puts html_head(:window_title => "#{@command}", :page_title => "#{@command}", :sub_title => "")
  85. puts "<pre>"
  86. end
  87. def emit_footer
  88. puts "</pre>"
  89. html_footer
  90. end
  91. public
  92. def emit_html
  93. @command.run do |stdout, stderr, stack_dump, pid|
  94. %w[INT TERM].each do |signal|
  95. trap(signal) do
  96. begin
  97. Process.kill("KILL", pid)
  98. sleep 0.5
  99. Process.kill("TERM", pid)
  100. rescue
  101. # process doesn't exist anymore
  102. end
  103. end
  104. end
  105. emit_header()
  106. TextMate::IO.exhaust(:out => stdout, :err => stderr, :stack => stack_dump) do |str, type|
  107. case type
  108. when :out then print filter_stdout(str)
  109. when :err then puts filter_stderr(str)
  110. when :stack then
  111. unless @command.temp_file.nil?
  112. str.gsub!(/(href=("|')(?:txmt:\/\/open\?(?:[a-z]+=[0-9]+)*?))(&url=.*)([a-z]+=[0-9]+)?(\2)/, '\1\2')
  113. ext = @command.default_extension
  114. str.gsub!(File.basename(@command.temp_file), "untitled")
  115. end
  116. @error << str
  117. end
  118. end
  119. emit_footer()
  120. Process.waitpid(pid)
  121. end
  122. end
  123. end
  124. class UserScript
  125. attr_reader :display_name, :path, :warning
  126. attr_reader :temp_file
  127. def initialize(content)
  128. @warning = ''
  129. @content = content
  130. @hashbang = $1 if @content =~ /\A#!(.*)$/
  131. @saved = true
  132. if ENV.has_key? 'TM_FILEPATH' then
  133. @path = ENV['TM_FILEPATH']
  134. @display_name = File.basename(@path)
  135. begin
  136. file = open(@path, 'w')
  137. file.write @content
  138. rescue Errno::EACCES
  139. @saved = false
  140. @warning = "Could not save #{@path} before running, using temp file..."
  141. ensure
  142. file.close unless file.nil?
  143. end
  144. else
  145. @saved = false
  146. end
  147. end
  148. public
  149. def executable
  150. # return the path to the executable that will run @content.
  151. end
  152. def args
  153. # return any arguments to be fed to the executable
  154. []
  155. end
  156. def filter_cmd(cmd)
  157. # this method is called with this list:
  158. # [executable, args, e_sh(@path), ARGV.to_a ].flatten
  159. cmd
  160. end
  161. def version_string
  162. # return the version string of the executable.
  163. end
  164. def default_extension
  165. # return the extension to use if the script has not yet been saved
  166. end
  167. def run(&block)
  168. rd, wr = IO.pipe
  169. rd.fcntl(Fcntl::F_SETFD, 1)
  170. ENV['TM_ERROR_FD'] = wr.to_i.to_s
  171. if @saved
  172. cmd = filter_cmd([executable, args, e_sh(@path), ARGV.to_a ].flatten)
  173. stdin, stdout, stderr, pid = my_popen3(cmd.join(" "))
  174. wr.close
  175. block.call(stdout, stderr, rd, pid)
  176. else
  177. TextMate::IO.tempfile(default_extension) do |f|
  178. f.write @content
  179. @display_name = "untitled"
  180. @temp_file = f.path
  181. cmd = filter_cmd([executable, args, e_sh(f.path), ARGV.to_a ].flatten)
  182. stdin, stdout, stderr, pid = my_popen3(cmd.join(" "))
  183. wr.close
  184. block.call(stdout, stderr, rd, pid)
  185. end
  186. end
  187. end
  188. end
  189. class ScriptMate < CommandMate
  190. protected
  191. def emit_header
  192. puts html_head(:window_title => "#{@command.display_name} — #{@mate}", :page_title => "#{@mate}", :sub_title => "#{@command.lang}")
  193. puts <<-HTML
  194. <!-- scriptmate javascripts -->
  195. <script type="text/javascript" charset="utf-8">
  196. function press(evt) {
  197. if (evt.keyCode == 67 && evt.ctrlKey == true) {
  198. TextMate.system("kill -s INT #{@pid}; sleep 0.5; kill -s TERM #{@pid}", null);
  199. }
  200. }
  201. document.body.addEventListener('keydown', press, false);
  202. function copyOutput(link) {
  203. output = document.getElementById('_scriptmate_output').innerText;
  204. cmd = TextMate.system('__CF_USER_TEXT_ENCODING=$UID:0x8000100:0x8000100 /usr/bin/pbcopy', function(){});
  205. cmd.write(output);
  206. cmd.close();
  207. link.innerText = 'output copied to clipboard';
  208. }
  209. </script>
  210. <!-- end javascript -->
  211. HTML
  212. puts <<-HTML
  213. <style type="text/css">
  214. /* =================== */
  215. /* = ScriptMate Styles = */
  216. /* =================== */
  217. div.scriptmate {
  218. }
  219. div.scriptmate > div {
  220. /*border-bottom: 1px dotted #666;*/
  221. /*padding: 1ex;*/
  222. }
  223. div.scriptmate pre em
  224. {
  225. /* used for stderr */
  226. font-style: normal;
  227. color: #FF5600;
  228. }
  229. div.scriptmate div#exception_report
  230. {
  231. /* background-color: rgb(210, 220, 255);*/
  232. }
  233. div.scriptmate p#exception strong
  234. {
  235. color: #E4450B;
  236. }
  237. div.scriptmate p#traceback
  238. {
  239. font-size: 8pt;
  240. }
  241. div.scriptmate blockquote {
  242. font-style: normal;
  243. border: none;
  244. }
  245. div.scriptmate table {
  246. margin: 0;
  247. padding: 0;
  248. }
  249. div.scriptmate td {
  250. margin: 0;
  251. padding: 2px 2px 2px 5px;
  252. font-size: 10pt;
  253. }
  254. div.scriptmate a {
  255. color: #FF5600;
  256. }
  257. div#exception_report pre.snippet {
  258. margin:4pt;
  259. padding:4pt;
  260. }
  261. </style>
  262. <strong class="warning" style="float:left; color:#B4AF00;">#{@command.warning}</strong>
  263. <div class="scriptmate #{@mate.downcase}">
  264. <div class="controls" style="text-align:right;">
  265. <a style="text-decoration: none;" href="#" onclick="copyOutput(document.getElementById('_script_output'))">copy output</a>
  266. </div>
  267. <!-- first box containing version info and script output -->
  268. <pre>
  269. <strong>#{@mate} r#{$SCRIPTMATE_VERSION[/\d+/]} running #{@command.version_string}</strong>
  270. <strong>>>> #{@command.display_name}</strong>
  271. <div id="_scriptmate_output" style="white-space: normal; -khtml-nbsp-mode: space; -khtml-line-break: after-white-space;"> <!-- Script output -->
  272. HTML
  273. end
  274. def emit_footer
  275. puts '</div></pre></div>'
  276. puts @error unless @error == ""
  277. puts '<div id="exception_report" class="framed">Program exited.</div>'
  278. html_footer
  279. end
  280. end