diff --git a/ApplicationController.rb b/ApplicationController.rb index c8a4759..67ea0f1 100644 --- a/ApplicationController.rb +++ b/ApplicationController.rb @@ -10,7 +10,8 @@ require 'pathname' require 'osx/cocoa' libdir = OSX::NSBundle.mainBundle.resourcePath.stringByAppendingPathComponent("lib").fileSystemRepresentation -$:.unshift(libdir, "#{libdir}/grit/lib", "#{libdir}/mime-types/lib") +puts "LIB DIR:#{libdir}" +$:.unshift(libdir, "#{libdir}/grit/lib", "#{libdir}/mime-types/lib", "#{libdir}/open4/lib") require 'grit' require 'time_extensions' require 'string_extensions' diff --git a/CommitsController.rb b/CommitsController.rb index 0e22c3d..0637022 100644 --- a/CommitsController.rb +++ b/CommitsController.rb @@ -117,12 +117,17 @@ def webView_didFinishLoadForFrame(view, frame) end def webView_contextMenuItemsForElement_defaultMenuItems(view, element, defaultMenuItems) - defaultMenuItems.select do |item| + items = defaultMenuItems.select do |item| # WebMenuItemTagCopy = 8 # WebMenuItemTagCut = 13 # WebMenuItemTagPaste = 14 [8, 13, 14].include? item.tag end + items << NSMenuItem.alloc.initWithTitle_action_keyEquivalent("Blame", :foo_bar, "") + end + + def foo_bar(sender) + puts "FOO" end def imageLoadForURL_didFinishLoading(url, image) diff --git a/GitNub.xcodeproj/project.pbxproj b/GitNub.xcodeproj/project.pbxproj index 5a26dc1..c02f058 100644 --- a/GitNub.xcodeproj/project.pbxproj +++ b/GitNub.xcodeproj/project.pbxproj @@ -77,7 +77,7 @@ 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 4D922CA60D8889DB002A5539 /* CommitSummaryCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommitSummaryCell.h; sourceTree = ""; }; 4D922CA70D8889DB002A5539 /* CommitSummaryCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommitSummaryCell.m; sourceTree = ""; }; - 4DDCA7110ACC9A6100E082CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Info.plist; sourceTree = ""; }; + 4DDCA7110ACC9A6100E082CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4DDCA7120ACC9A6100E082CE /* GitNub.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GitNub.app; sourceTree = BUILT_PRODUCTS_DIR; }; DA8995E20D890D8A00CF2CDA /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = ""; }; E8F5E24E03AEB6EC03A81C6F /* RubyCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RubyCocoa.framework; path = /Library/Frameworks/RubyCocoa.framework; sourceTree = ""; }; diff --git a/lib/open4/lib/open4.rb b/lib/open4/lib/open4.rb new file mode 100644 index 0000000..137d1fb --- /dev/null +++ b/lib/open4/lib/open4.rb @@ -0,0 +1,393 @@ +# vim: ts=2:sw=2:sts=2:et:fdm=marker +require 'fcntl' +require 'timeout' +require 'thread' + +module Open4 +#--{{{ + VERSION = '0.9.6' + def self.version() VERSION end + + class Error < ::StandardError; end + + def popen4(*cmd, &b) +#--{{{ + pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe + + verbose = $VERBOSE + begin + $VERBOSE = nil + ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) + + cid = fork { + pw.last.close + STDIN.reopen pw.first + pw.first.close + + pr.first.close + STDOUT.reopen pr.last + pr.last.close + + pe.first.close + STDERR.reopen pe.last + pe.last.close + + STDOUT.sync = STDERR.sync = true + + begin + exec(*cmd) + raise 'forty-two' + rescue Exception => e + Marshal.dump(e, ps.last) + ps.last.flush + end + ps.last.close unless (ps.last.closed?) + exit! + } + ensure + $VERBOSE = verbose + end + + [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close} + + begin + e = Marshal.load ps.first + raise(Exception === e ? e : "unknown failure!") + rescue EOFError # If we get an EOF error, then the exec was successful + 42 + ensure + ps.first.close + end + + pw.last.sync = true + + pi = [pw.last, pr.first, pe.first] + + if b + begin + b[cid, *pi] + Process.waitpid2(cid).last + ensure + pi.each{|fd| fd.close unless fd.closed?} + end + else + [cid, pw.last, pr.first, pe.first] + end +#--}}} + end + alias open4 popen4 + module_function :popen4 + module_function :open4 + + class SpawnError < Error +#--{{{ + attr 'cmd' + attr 'status' + attr 'signals' + def exitstatus + @status.exitstatus + end + def initialize cmd, status + @cmd, @status = cmd, status + @signals = {} + if status.signaled? + @signals['termsig'] = status.termsig + @signals['stopsig'] = status.stopsig + end + sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ') + super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>" + end +#--}}} + end + + class ThreadEnsemble +#--{{{ + attr 'threads' + + def initialize cid + @cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false + @killed = false + end + + def add_thread *a, &b + @running ? raise : (@argv << [a, b]) + end + +# +# take down process more nicely +# + def killall + c = Thread.critical + return nil if @killed + Thread.critical = true + (@threads - [Thread.current]).each{|t| t.kill rescue nil} + @killed = true + ensure + Thread.critical = c + end + + def run + @running = true + + begin + @argv.each do |a, b| + @threads << Thread.new(*a) do |*a| + begin + b[*a] + ensure + killall rescue nil if $! + @done.push Thread.current + end + end + end + rescue + killall + raise + ensure + all_done + end + + @threads.map{|t| t.value} + end + + def all_done + @threads.size.times{ @done.pop } + end +#--}}} + end + + def to timeout = nil +#--{{{ + Timeout.timeout(timeout){ yield } +#--}}} + end + module_function :to + + def new_thread *a, &b +#--{{{ + cur = Thread.current + Thread.new(*a) do |*a| + begin + b[*a] + rescue Exception => e + cur.raise e + end + end +#--}}} + end + module_function :new_thread + + def getopts opts = {} +#--{{{ + lambda do |*args| + keys, default, ignored = args + catch('opt') do + [keys].flatten.each do |key| + [key, key.to_s, key.to_s.intern].each do |key| + throw 'opt', opts[key] if opts.has_key?(key) + end + end + default + end + end +#--}}} + end + module_function :getopts + + def relay src, dst = nil, t = nil +#--{{{ + unless src.nil? + if src.respond_to? :gets + while buf = to(t){ src.gets } + dst << buf if dst + end + + elsif src.respond_to? :each + q = Queue.new + th = nil + + timer_set = lambda do |t| + th = new_thread{ to(t){ q.pop } } + end + + timer_cancel = lambda do |t| + th.kill if th rescue nil + end + + timer_set[t] + begin + src.each do |buf| + timer_cancel[t] + dst << buf if dst + timer_set[t] + end + ensure + timer_cancel[t] + end + + elsif src.respond_to? :read + buf = to(t){ src.read } + dst << buf if dst + + else + buf = to(t){ src.to_s } + dst << buf if dst + end + end +#--}}} + end + module_function :relay + + def spawn arg, *argv +#--{{{ + argv.unshift(arg) + opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {}) + argv.flatten! + cmd = argv.join(' ') + + + getopt = getopts opts + + ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ] + ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ] + exitstatus = getopt[ %w( exitstatus exit_status status ) ] + stdin = getopt[ %w( stdin in i 0 ) << 0 ] + stdout = getopt[ %w( stdout out o 1 ) << 1 ] + stderr = getopt[ %w( stderr err e 2 ) << 2 ] + pid = getopt[ 'pid' ] + timeout = getopt[ %w( timeout spawn_timeout ) ] + stdin_timeout = getopt[ %w( stdin_timeout ) ] + stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ] + stderr_timeout = getopt[ %w( stderr_timeout ) ] + status = getopt[ %w( status ) ] + cwd = getopt[ %w( cwd dir ) ] + + exitstatus = + case exitstatus + when TrueClass, FalseClass + ignore_exit_failure = true if exitstatus + [0] + else + [*(exitstatus || 0)].map{|i| Integer i} + end + + stdin ||= '' if stdin_timeout + stdout ||= '' if stdout_timeout + stderr ||= '' if stderr_timeout + + started = false + + status = + begin + chdir(cwd) do + Timeout::timeout(timeout) do + popen4(*argv) do |c, i, o, e| + started = true + + %w( replace pid= << push update ).each do |msg| + break(pid.send(msg, c)) if pid.respond_to? msg + end + + te = ThreadEnsemble.new c + + te.add_thread(i, stdin) do |i, stdin| + relay stdin, i, stdin_timeout + i.close rescue nil + end + + te.add_thread(o, stdout) do |o, stdout| + relay o, stdout, stdout_timeout + end + + te.add_thread(e, stderr) do |o, stderr| + relay e, stderr, stderr_timeout + end + + te.run + end + end + end + rescue + raise unless(not started and ignore_exec_failure) + end + + raise SpawnError.new(cmd, status) unless + (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus)) + + status +#--}}} + end + module_function :spawn + + def chdir cwd, &block + return(block.call Dir.pwd) unless cwd + Dir.chdir cwd, &block + end + module_function :chdir + + def background arg, *argv +#--{{{ + require 'thread' + q = Queue.new + opts = { 'pid' => q, :pid => q } + case argv.last + when Hash + argv.last.update opts + else + argv.push opts + end + thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv} + sc = class << thread; self; end + sc.module_eval { + define_method(:pid){ @pid ||= q.pop } + define_method(:spawn_status){ @spawn_status ||= value } + define_method(:exitstatus){ @exitstatus ||= spawn_status.exitstatus } + } + thread +#--}}} + end + alias bg background + module_function :background + module_function :bg + + def maim pid, opts = {} +#--{{{ + getopt = getopts opts + sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ] + suspend = getopt[ 'suspend', 4 ] + pid = Integer pid + existed = false + sigs.each do |sig| + begin + Process.kill sig, pid + existed = true + rescue Errno::ESRCH + return(existed ? nil : true) + end + return true unless alive? pid + sleep suspend + return true unless alive? pid + end + return(not alive?(pid)) +#--}}} + end + module_function :maim + + def alive pid +#--{{{ + pid = Integer pid + begin + Process.kill 0, pid + true + rescue Errno::ESRCH + false + end +#--}}} + end + alias alive? alive + module_function :alive + module_function :'alive?' +#--}}} +end + +def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end diff --git a/style.css b/style.css index c25f0ed..309ea43 100644 --- a/style.css +++ b/style.css @@ -130,19 +130,20 @@ h1 { #diffs .diff { margin-bottom: 27px; - background-color: #f7f7f7; + background-color: #333; -webkit-border-radius: 4px; border: 1px solid #ddd; width: 100%; } .diff h3 { - background: #ddd; - color: #333; + background: #333; + color: #fff; + font-family: "Lucida Grande"; + font-size: 92%; padding: 6px; - border-bottom: 1px solid #ccc; -webkit-border-top-right-radius: 4px; - -webkit-border-top-left-radius: 4px; + -webkit-border-top-left-radius: 4px; } pre {