/SharedSupport/lib/scriptmate.rb
Ruby | 315 lines | 289 code | 16 blank | 10 comment | 2 complexity | b174ea676a864cfb769b9b9064b8a9de MD5 | raw file
- SUPPORT_LIB = ENV['TM_SUPPORT_PATH'] + '/lib/'
- require SUPPORT_LIB + 'escape'
- require SUPPORT_LIB + 'web_preview'
- require SUPPORT_LIB + 'io'
- require SUPPORT_LIB + 'tm/tempfile'
- require 'cgi'
- require 'fcntl'
- $SCRIPTMATE_VERSION = "$Revision: 9925 $"
- def my_popen3(*cmd) # returns [stdin, stdout, strerr, pid]
- pw = IO::pipe # pipe[0] for read, pipe[1] for write
- pr = IO::pipe
- pe = IO::pipe
-
- # F_SETOWN = 6, ideally this would be under Fcntl::F_SETOWN
- pw[0].fcntl(6, ENV['TM_PID'].to_i) if ENV.has_key? 'TM_PID'
-
- pid = fork{
- pw[1].close
- STDIN.reopen(pw[0])
- pw[0].close
- pr[0].close
- STDOUT.reopen(pr[1])
- pr[1].close
- pe[0].close
- STDERR.reopen(pe[1])
- pe[1].close
- tm_interactive_input = SUPPORT_LIB + '/tm_interactive_input.dylib'
- if (File.exists? tm_interactive_input)
- dil = ENV['DYLD_INSERT_LIBRARIES']
- ENV['DYLD_INSERT_LIBRARIES'] = (dil) ? "#{tm_interactive_input}:#{dil}" : tm_interactive_input unless (dil =~ /#{tm_interactive_input}/)
- ENV['DYLD_FORCE_FLAT_NAMESPACE'] = "1"
- ENV['TM_INTERACTIVE_INPUT'] = 'AUTO|ECHO'
- end
-
- exec(*cmd)
- }
- pw[0].close
- pr[1].close
- pe[1].close
- pw[1].sync = true
- [pw[1], pr[0], pe[0], pid]
- end
- def cmd_mate(cmd)
- # cmd can be either a string or a list of strings to be passed to Popen3
- # this command will write the output of the `cmd` on STDOUT, formatted in
- # HTML.
- c = UserCommand.new(cmd)
- m = CommandMate.new(c)
- m.emit_html
- end
- class UserCommand
- attr_reader :display_name, :path
- def initialize(cmd)
- @cmd = cmd
- end
- def run
- stdin, stdout, stderr, pid = my_popen3(@cmd)
- return stdout, stderr, nil, pid
- end
- def to_s
- @cmd.to_s
- end
- end
- class CommandMate
- def initialize (command)
- # the object `command` needs to implement a method `run`. `run` should
- # return an array of three file descriptors [stdout, stderr, stack_dump].
- @error = ""
- @command = command
- STDOUT.sync = true
- @mate = self.class.name
- end
- protected
- def filter_stdout(str)
- # strings from stdout are passed through this method before being printed
- # txmt://open?line=3&url=file:///var/folders/Gx/Gxr7D8ILFba5bZaZC6rrCE%2B%2B%2BTQ/-Tmp-/untitled_m16p.py
- str = htmlize(str).gsub(/\<br\>/, "<br>\n")
- end
- def filter_stderr(str)
- # strings from stderr are passwed through this method before printing
- "<span style='color: red'>#{htmlize str}</span>".gsub(/\<br\>/, "<br>\n")
- end
- def emit_header
- puts html_head(:window_title => "#{@command}", :page_title => "#{@command}", :sub_title => "")
- puts "<pre>"
- end
- def emit_footer
- puts "</pre>"
- html_footer
- end
- public
- def emit_html
- @command.run do |stdout, stderr, stack_dump, pid|
- %w[INT TERM].each do |signal|
- trap(signal) do
- begin
- Process.kill("KILL", pid)
- sleep 0.5
- Process.kill("TERM", pid)
- rescue
- # process doesn't exist anymore
- end
- end
- end
- emit_header()
- TextMate::IO.exhaust(:out => stdout, :err => stderr, :stack => stack_dump) do |str, type|
- case type
- when :out then print filter_stdout(str)
- when :err then puts filter_stderr(str)
- when :stack then
- unless @command.temp_file.nil?
- str.gsub!(/(href=("|')(?:txmt:\/\/open\?(?:[a-z]+=[0-9]+)*?))(&url=.*)([a-z]+=[0-9]+)?(\2)/, '\1\2')
- ext = @command.default_extension
- str.gsub!(File.basename(@command.temp_file), "untitled")
- end
- @error << str
- end
- end
- emit_footer()
- Process.waitpid(pid)
- end
- end
- end
- class UserScript
- attr_reader :display_name, :path, :warning
- attr_reader :temp_file
- def initialize(content)
-
- @warning = ''
- @content = content
- @hashbang = $1 if @content =~ /\A#!(.*)$/
-
- @saved = true
- if ENV.has_key? 'TM_FILEPATH' then
- @path = ENV['TM_FILEPATH']
- @display_name = File.basename(@path)
- begin
- file = open(@path, 'w')
- file.write @content
- rescue Errno::EACCES
- @saved = false
- @warning = "Could not save #{@path} before running, using temp file..."
- ensure
- file.close unless file.nil?
- end
- else
- @saved = false
- end
- end
-
- public
-
- def executable
- # return the path to the executable that will run @content.
- end
- def args
- # return any arguments to be fed to the executable
- []
- end
- def filter_cmd(cmd)
- # this method is called with this list:
- # [executable, args, e_sh(@path), ARGV.to_a ].flatten
- cmd
- end
- def version_string
- # return the version string of the executable.
- end
- def default_extension
- # return the extension to use if the script has not yet been saved
- end
- def run(&block)
- rd, wr = IO.pipe
- rd.fcntl(Fcntl::F_SETFD, 1)
- ENV['TM_ERROR_FD'] = wr.to_i.to_s
- if @saved
- cmd = filter_cmd([executable, args, e_sh(@path), ARGV.to_a ].flatten)
- stdin, stdout, stderr, pid = my_popen3(cmd.join(" "))
- wr.close
- block.call(stdout, stderr, rd, pid)
- else
- TextMate::IO.tempfile(default_extension) do |f|
- f.write @content
- @display_name = "untitled"
- @temp_file = f.path
- cmd = filter_cmd([executable, args, e_sh(f.path), ARGV.to_a ].flatten)
- stdin, stdout, stderr, pid = my_popen3(cmd.join(" "))
- wr.close
- block.call(stdout, stderr, rd, pid)
- end
- end
- end
- end
- class ScriptMate < CommandMate
- protected
- def emit_header
- puts html_head(:window_title => "#{@command.display_name} â #{@mate}", :page_title => "#{@mate}", :sub_title => "#{@command.lang}")
- puts <<-HTML
- <!-- scriptmate javascripts -->
- <script type="text/javascript" charset="utf-8">
- function press(evt) {
- if (evt.keyCode == 67 && evt.ctrlKey == true) {
- TextMate.system("kill -s INT #{@pid}; sleep 0.5; kill -s TERM #{@pid}", null);
- }
- }
- document.body.addEventListener('keydown', press, false);
- function copyOutput(link) {
- output = document.getElementById('_scriptmate_output').innerText;
- cmd = TextMate.system('__CF_USER_TEXT_ENCODING=$UID:0x8000100:0x8000100 /usr/bin/pbcopy', function(){});
- cmd.write(output);
- cmd.close();
- link.innerText = 'output copied to clipboard';
- }
- </script>
- <!-- end javascript -->
- HTML
- puts <<-HTML
- <style type="text/css">
- /* =================== */
- /* = ScriptMate Styles = */
- /* =================== */
- div.scriptmate {
- }
- div.scriptmate > div {
- /*border-bottom: 1px dotted #666;*/
- /*padding: 1ex;*/
- }
- div.scriptmate pre em
- {
- /* used for stderr */
- font-style: normal;
- color: #FF5600;
- }
- div.scriptmate div#exception_report
- {
- /* background-color: rgb(210, 220, 255);*/
- }
- div.scriptmate p#exception strong
- {
- color: #E4450B;
- }
- div.scriptmate p#traceback
- {
- font-size: 8pt;
- }
- div.scriptmate blockquote {
- font-style: normal;
- border: none;
- }
- div.scriptmate table {
- margin: 0;
- padding: 0;
- }
- div.scriptmate td {
- margin: 0;
- padding: 2px 2px 2px 5px;
- font-size: 10pt;
- }
- div.scriptmate a {
- color: #FF5600;
- }
-
- div#exception_report pre.snippet {
- margin:4pt;
- padding:4pt;
- }
- </style>
- <strong class="warning" style="float:left; color:#B4AF00;">#{@command.warning}</strong>
- <div class="scriptmate #{@mate.downcase}">
- <div class="controls" style="text-align:right;">
- <a style="text-decoration: none;" href="#" onclick="copyOutput(document.getElementById('_script_output'))">copy output</a>
- </div>
- <!-- first box containing version info and script output -->
- <pre>
- <strong>#{@mate} r#{$SCRIPTMATE_VERSION[/\d+/]} running #{@command.version_string}</strong>
- <strong>>>> #{@command.display_name}</strong>
- <div id="_scriptmate_output" style="white-space: normal; -khtml-nbsp-mode: space; -khtml-line-break: after-white-space;"> <!-- Script output -->
- HTML
- end
- def emit_footer
- puts '</div></pre></div>'
- puts @error unless @error == ""
- puts '<div id="exception_report" class="framed">Program exited.</div>'
- html_footer
- end
- end