On Oct 28, 2006, at 9:10 PM, Soryu wrote:
Hey Chris, what are you up to?
I was myself somehow in the process to revamp all the SVN commands
(that output HTML) to use the nicer WebPreview Styling. That Status
command, though, gave me quite some trouble. That is, the builder
stuff in there. I threw most of it away and used ERB and was now on
the JavaScript.
It looks like we're using similar approaches. If you want to see what
I've got so far, use the enclosed format_status.rb in place of the
standard one.
The main thing left to be done at this point is harmonization of the
SVN status CSS with the WebPreview CSS. If you have ideas for how the
status window should look, I'd encourage you to go ahead. I haven't
touched the CSS yet and won't get to it for a few days.
Allan also said, it would be nice to maybe use images/icons for the
add/remove commands.
It could be nice, I agree.
Chris
# Includes
support = ENV['TM_SUPPORT_PATH']
require (support + "/lib/Builder.rb")
require (support + "/lib/shelltokenize.rb")
require (support + "/lib/escape.rb")
require "cgi"
require "erb"
# Arguments
bundle = ENV['TM_BUNDLE_SUPPORT']
work_path = ENV['WorkPath']
work_paths = TextMate.selected_paths_array
ignore_file_pattern = /(\/.*)*(\/\..*|\.(tmproj|o|pyc)|Icon)/
# First escape for use in the shell, then escape for use in a JS string
def e_sh_js(str)
(e_sh str).gsub("\\", "\\\\\\\\")
end
def shorten_path(path)
prefix = ENV['WorkPath']
if prefix.nil?
work_paths = TextMate.selected_paths_array
prefix = work_paths.first unless work_paths.nil? || work_paths.size != 1
end
if prefix && prefix == path
File.basename(path)
elsif prefix
File.expand_path(path).gsub(/#{Regexp.escape prefix}\//, '')
else
File.expand_path(path).gsub(/#{Regexp.escape File.expand_path('~')}/, '~')
end
end
svn = ENV['TM_SVN'] || 'svn'
svn = `which svn`.chomp unless svn[0] == ?/
display_title = work_paths[0] if work_path.nil? and (not work_paths.nil?) and (work_paths.size == 1)
display_title ||= '(selected files)'
#
# Status or update?
#
$is_status = false
$is_checkout = false
command_name = 'update'
ARGV.each do |arg|
case arg
when '--status'
$is_status = true
command_name = 'status'
when '--checkout'
$is_checkout = true
command_name = 'checkout'
end
end
StatusColumnNames = ['File', 'Property', 'Lock', 'History', 'Switched', 'Repository Lock']
StatusMap = { 'A' => 'added',
'D' => 'deleted',
'G' => 'merged',
'U' => 'updated',
'M' => 'modified',
'L' => 'locked',
'B' => 'broken',
'R' => 'replaced',
'C' => 'conflict',
'!' => 'missing',
'+' => 'added',
'"' => 'typeconflict',
'?' => 'unknown',
'I' => 'ignored',
'X' => 'external',
' ' => 'none'}
# this may be more dynamic in the future
# it also possibly should not be stashed in mup
def status_column_count
# update has three columns vs. up to eight for status as of 1.3.x
# But, for status, if we assume eight, we might have compatibility
# issues with earlier versions which had fewer columns (unless they
# had more padding than I recall). The last three columns seem to be
# network status-only, so this isn't a big deal for now.
$is_status ? 5 : 3
end
def status_colspan
$is_status ? (status_column_count + 5) : (status_column_count + 1)
end
def status_map(status)
StatusMap[status]
end
rhtml = ERB.new(IO.read(bundle + '/Templates/Status.rhtml'))
rhtml.run
#mup = Builder::XmlMarkup.new(:target => STDOUT)
=begin
class << mup
StatusColumnNames = ['File', 'Property', 'Lock', 'History', 'Switched', 'Repository Lock']
StatusMap = { 'A' => 'added',
'D' => 'deleted',
'G' => 'merged',
'U' => 'updated',
'M' => 'modified',
'L' => 'locked',
'B' => 'broken',
'R' => 'replaced',
'C' => 'conflict',
'!' => 'missing',
'+' => 'added',
'"' => 'typeconflict',
'?' => 'unknown',
'I' => 'ignored',
'X' => 'external',
' ' => 'none'}
# this may be more dynamic in the future
# it also possibly should not be stashed in mup
def status_column_count
# update has three columns vs. up to eight for status as of 1.3.x
# But, for status, if we assume eight, we might have compatibility
# issues with earlier versions which had fewer columns (unless they
# had more padding than I recall). The last three columns seem to be
# network status-only, so this isn't a big deal for now.
$is_status ? 5 : 3
end
def status_colspan
$is_status ? (status_column_count + 5) : (status_column_count + 1)
end
def status_map(status)
StatusMap[status]
end
def td_status!(status,id)
status_column_count.times do |i|
c = status[i].chr
status_class = StatusMap[c] || 'dunno'
td(c, "class" => "status_col #{status_class}", "title" => StatusColumnNames[i] + " " + status_class.capitalize, :id => "status#{id}")
end
end
def button_td!(show, name, onclick)
lowercase_name = name.downcase
col_class_name = lowercase_name + '_col'
button_class_name = lowercase_name + '_button'
td(:class => col_name) {
if show
a( name, "href" => '#', "class" => button_class_name, "onclick" => onclick )
end
}
end
end
mup.html {
mup.head {
mup.title("Subversion #{command_name.capitalize}")
mup.style( "@import 'file://"+bundle+"/Stylesheets/svn_status_style.css';", "type" => "text/css")
js_functions = <<ENDJS #javascript
<script>
the_filename = null;
the_id = null;
the_displayname = null;
the_new_status = null;
function display_tail(id, className, string){
if(string != null && string != '')
{
// tail_id = 'tail_' + id
// tail_div = document.getElementById(tail_id);
// if(tail_div == null)
// {
// status_div = document.getElementById('commandOutput');
// tail_div = document.createElement('div');
// tail_div.setAttribute('id', tail_id);
// tail_div.setAttribute('class', className);
// status_div.appendChild(tail_div);
// }
//
// tail_div.innerHTML = string;
string += " \\n";
document.getElementById('commandOutput').innerHTML += string;
}
}
function SVNCommand(cmd, id, statusString, className){
results = TextMate.system('LC_CTYPE=en_US.UTF-8 ' + cmd, null)
outputString = results.outputString;
// errorString = results.errorString;
errorCode = results.status;
// TM doesn't receive the error stream unless output and error are the same descriptor?
// display_tail('error', 'error', errorString);
display_tail('info', 'info', outputString);
if(errorCode == 0)
{
document.getElementById('status'+id).innerHTML = statusString;
document.getElementById('status'+id).className = 'status_col ' + className;
}
}
svn_commit = function(){
TextMate.isBusy = true;
// cmd = 'require_cmd "#{e_sh svn}" "If you have installed svn, then you need to either update your <tt>PATH</tt> or set the <tt>TM_SVN</tt> shell variable (e.g. in Preferences / Advanced)"\\n\\nexport TM_SVN\\nexport CommitWindow="$TM_SUPPORT_PATH/bin/CommitWindow.app/Contents/MacOS/CommitWindow"\\n\\ncd "${TM_PROJECT_DIRECTORY:-$TM_DIRECTORY}"\\n"${TM_RUBY:-ruby}" -- "${TM_BUNDLE_SUPPORT}/svn_commit.rb"'
cmd = ""
cmd += 'export LC_CTYPE=en_US.UTF-8 ;';
#{ %{cmd += "export PATH=#{ e_sh_js ENV['PATH'] }; ";} }
#{ %{cmd += "export TM_SVN=#{ e_sh_js ENV['TM_SVN'] }; ";} if ENV['TM_SVN'] }
#{ %{cmd += "export TM_BUNDLE_SUPPORT=#{ e_sh_js ENV['TM_BUNDLE_SUPPORT'] }; ";} if ENV['TM_BUNDLE_SUPPORT'] }
#{ %{cmd += "export TM_SUPPORT_PATH=#{ e_sh_js ENV['TM_SUPPORT_PATH'] }; ";} if ENV['TM_SUPPORT_PATH'] }
#{ %{cmd += "export CommitWindow=#{ e_sh_js ENV['CommitWindow'] }; ";} if ENV['CommitWindow'] }
#{ %{cmd += "export TM_SVN_DIFF_CMD=#{ e_sh_js ENV['TM_SVN_DIFF_CMD'] }; ";} if ENV['TM_SVN_DIFF_CMD'] }
cmd += '"#{ENV['TM_RUBY'] || "ruby"}" -- "#{ENV['TM_BUNDLE_SUPPORT']}/svn_commit.rb" "#{work_paths.join("\" \"")}"'
document.getElementById('commandOutput').innerHTML = TextMate.system(cmd, null).outputString + ' \\n'
// SVNCommand(cmd, '_commit', '-', 'done')
TextMate.isBusy = false;
};
// the filename passed in to the following functions is already properly shell escaped
diff_to_mate = function(filename,id){
TextMate.isBusy = true;
tmp = '/tmp/diff_to_mate' + id + '.diff'
cmd = 'LC_CTYPE=en_US.UTF-8 #{e_sh svn} 2>&1 diff --diff-cmd diff ' + filename + ' >' + tmp + ' && open -a TextMate ' + tmp
document.getElementById('commandOutput').innerHTML += TextMate.system(cmd, null).outputString + ' \\n'
TextMate.isBusy = false;
};
svn_add = function(filename,id){
TextMate.isBusy = true;
cmd = '#{e_sh svn} add ' + filename + ' 2>&1'
SVNCommand(cmd, id, 'A', '#{mup.status_map('A')}')
TextMate.isBusy = false;
};
svn_revert = function(filename,id){
TextMate.isBusy = true;
cmd = '#{e_sh svn} 2>&1 revert ' + filename;
SVNCommand(cmd, id, '?', '#{mup.status_map('?')}')
TextMate.isBusy = false;
};
svn_revert_confirm = function(filename,id,displayname){
the_filename = filename;
the_id = id;
the_displayname = displayname;
the_new_status = '?';
TextMate.isBusy = true;
cmd = 'LC_CTYPE=en_US.UTF-8 #{e_sh_js ENV['TM_BUNDLE_SUPPORT']}/revert_file.rb -svn=#{e_sh_js svn} -path=' + filename + ' -displayname=' + displayname;
myCommand = TextMate.system(cmd, function (task) { });
myCommand.onreadoutput = svn_output;
};
svn_remove = function(filename,id,displayname){
the_filename = filename;
the_id = id;
the_displayname = displayname;
the_new_status = 'D';
TextMate.isBusy = true;
cmd = 'LC_CTYPE=en_US.UTF-8 #{e_sh_js ENV['TM_BUNDLE_SUPPORT']}/remove_file.rb -svn=#{e_sh_js svn} -path=' + filename + ' -displayname=' + displayname;
myCommand = TextMate.system(cmd, function (task) { });
myCommand.onreadoutput = svn_output;
};
svn_output = function(str){
display_tail('info', 'info', str);
document.getElementById('status'+the_id).innerHTML = the_new_status;
if(the_new_status == '-'){document.getElementById('status'+the_id).className = 'status_col #{mup.status_map('-')}'};
if(the_new_status == 'D'){document.getElementById('status'+the_id).className = 'status_col #{mup.status_map('D')}'};
TextMate.isBusy = false;
the_filename = null;
the_id = null;
the_displayname = null;
the_new_status = null;
};
finder_open = function(filename,id){
TextMate.isBusy = true;
cmd = "open 2>&1 " + filename;
output = TextMate.system(cmd, null).outputString;
display_tail('info', 'info', output);
TextMate.isBusy = false;
};
</script>
ENDJS
mup << js_functions
}
mup.body {
mup.h1 do
mup.img( :src => "file://"+bundle+"/Stylesheets/subversion_logo.tiff",
:height => 21,
:width => 32 )
mup << " #{command_name.capitalize} for "
mup << "“"
mup << "#{File.basename(display_title)}"
mup << "”"
end
# mup.hr
# mup.div("class" => "command"){ mup.strong("#{svn} #{command_name}"); mup.text!(" " + display_title) } #mup.text!("checking ");
STDOUT.flush
mup.div( "class" => "section" ) do
mup.table("class" => "status") {
match_columns = '.' * mup.status_column_count
unknown_file_status = '?' + (' ' * (mup.status_column_count - 1))
missing_file_status = '!' + (' ' * (mup.status_column_count - 1))
added_file_status = 'A' + (' ' * (mup.status_column_count - 1))
stdin_line_count = 1
STDIN.each_line do |line|
# ignore lines consisting only of whitespace
next if line.squeeze.strip.empty?
# build the row
mup.tr {
if /^svn:/.match( line ).nil? then
match = /^(#{match_columns})(?:\s+)(.*)\n/.match( line )
if match.nil? then
# Informational text, not status
mup.td(:colspan => (mup.status_colspan).to_s ) do
mup.div(:class => 'info') { mup.text(line) }
end
else
status = match[1]
file = match[2]
esc_file = '"' + CGI.escapeHTML(e_sh_js(file).gsub(/(?=")/, '\\')) + '"'
esc_displayname = '"' + CGI.escapeHTML(e_sh_js(shorten_path(file)).gsub(/(?=")/, '\\')) + '"'
# Skip files that we don't want to know about
next if (status == unknown_file_status and ignore_file_pattern =~ file)
# Status string
mup.td_status!(status, stdin_line_count)
# Add, Revert, etc buttons
if $is_status
# ADD Column
mup.button_td!((status == unknown_file_status),
'Add',
"svn_add(#{esc_file},#{stdin_line_count}); return false")
# REVERT Column
mup.button_td!((status != unknown_file_status),
'Revert',
"svn_revert#{"_confirm" unless status == added_file_status}(#{esc_file},#{stdin_line_count},#{esc_displayname}); return false")
# REMOVE Column
mup.button_td!((status == missing_file_status),
'Remove',
"svn_remove(#{esc_file},#{stdin_line_count},#{esc_displayname}); return false")
# DIFF Column
if file.match(/\.(png|gif|jpe?g|psd|tif?f|zip|rar)$/i)
onclick = "finder_open(#{esc_file},#{stdin_line_count}); return false"
column_is_an_image = true
else
onclick = ""
# Diff Column (only available for text)
column_is_an_image = false
end
mup.button_td!( ((not column_is_an_image) and (status != unknown_file_status)),
'Diff',
"diff_to_mate(#{esc_file},#{stdin_line_count}); return false")
end
# FILE Column
mup.td(:class => 'file_col') {
mup.a( shorten_path(file), "href" => 'txmt://open?url=file://' + (e_url file), "class" => "pathname", "onclick" => onclick )
}
end
else
mup.td { mup.div( line, "class" => "error" ) }
end
}
mup << "\n"
stdin_line_count += 1
end
}
end
if $is_status then
mup.div(:id => 'actions'){
mup.a('Commit', :href => '#', :onclick => 'svn_commit(); return false')
mup.div(:style => 'clear:both'){}
}
end
mup.div(:id => 'commandOutput'){
mup << " "
#DEBUG# mup << display_title + "\n"
#DEBUG# ENV.sort.each { |key,value| puts key + ' = ' + value + "\n" }
}
}
}
=end
_______________________________________________
textmate-dev mailing list
[email protected]
http://lists.macromates.com/mailman/listinfo/textmate-dev