require 'rubygems' require 'haml' require 'sass' class Hash alias_method :get, :[] ## # Same as #[], but will take regexps and try to match those. # This will bug out if you are using regexps as keys # # @return [Hash, Value] will return either a hash (if supplied with a regexp) # or whatever it would normally return. def [](key) case key when Regexp select {|k, _| k =~ key }.to_hash else get key end end end module Amp module Servers class FancyHTTPServer < HTTPAuthorizedServer set :views, File.expand_path(File.join(File.dirname(__FILE__), "fancy_views")) enable :static set :public, File.expand_path(File.join(File.dirname(__FILE__), "fancy_views")) def self.amp_repository(http_path, repo, opts={}) super(http_path, repo) http_path.chomp!('/') path_slashed = http_path + "/" get "#{http_path}/changeset/:changeset/?" do |cs| @changeset = repo[cs] haml :changeset, :locals => {:root => http_path, :repo => repo} end [http_path+"/", "#{http_path}/commits/?", "#{http_path}/commits/:page/?"].each do |path| get path do haml :commits, :locals => {:root => http_path, :opts => opts, :repo => repo, :page => params[:page].to_i} end end get "#{http_path}/users/:user" do if users[params[:user]] "You are browsing user #{params[:user]} in a repository located at #{repo.inspect}" else "User #{params[:user].inspect} not found :-(" end end ["#{http_path}/code/:changeset/?*", "#{http_path}/code/?*"].each do |p| get p do path = params[:splat].join path = path.shift('/').chomp('/') # clean it of slashes changeset_node = params[:changeset] || "tip" changeset = repo[changeset_node] info = load_browser_info changeset, path file_list, path, vf_cur, orig_path = info[:file_list], info[:path], info[:vf_cur], info[:orig_path] haml :file, :locals => {:root => http_path, :repo => repo, :file_list => file_list, :path => path, :vf_cur => vf_cur, :orig_path => orig_path, :changeset => changeset} end end get "#{http_path}/diff/:changeset/*" do path = params[:splat].join path = path.shift('/').chomp('/') # clean it of slashes changeset_node = params[:changeset] || "tip" changeset = repo[changeset_node] info = load_browser_info changeset, path file_list, path, vf_cur, orig_path = info[:file_list], info[:path], info[:vf_cur], info[:orig_path] haml :file_diff, :locals => {:root => http_path, :repo => repo, :file_list => file_list, :path => path, :vf_cur => vf_cur, :orig_path => orig_path, :changeset => changeset} end get "#{http_path}/raw/:changeset/*" do changeset_node = params[:changeset] path = params[:splat].join.shift("/").chomp("/") changeset = repo[changeset_node] vf_cur = changeset.get_file path content_type "text/plain" vf_cur.data end get '/stylesheet.css' do content_type 'text/css', :charset => 'utf-8' sass :stylesheet end end helpers do def load_browser_info(changeset, path) mapping = changeset.manifest orig_path = nil # if the path is a file (because we only keep track of files) if mapping[path] # give it the appropriate information (the versioned file) vf_cur = changeset.get_file(path) file_list = [] # and return an empty file_list orig_path = path path = Dir.dirname path end files = mapping.files.select {|f| Dir.dirname(f) == path }.map {|f| f[path.size..-1].shift '/' } dirs = mapping.files.select {|f| File.amp_directories_to(f, true).index(path) && Dir.dirname(f) != path } # only go one deep dirs.map! do |d| idx = File.amp_directories_to(d, true).index path File.amp_directories_to(d, true)[idx - 1][path.size..-1].shift '/' end.uniq! path = path.empty? ? '' : path + '/' file_list = files.map do |name| {:link => path + name , :type => :file, :name => name } end file_list += dirs.map do |name| {:link => path + name, :type => :directory , :name => name } end file_list.sort! {|h1, h2| h1[:name] <=> h2[:name] } # alphabetically sorted vf_cur ||= if mapping[/#{path}readme/i].any? # map[//] returns a hash change_id = params[:changeset] || "tip" readme = mapping[/#{path}readme/i].keys.first orig_path = readme changeset.get_file readme else nil end path = path.chomp '/' # path will not have any trailing slashes {:path => path, :vf_cur => vf_cur, :file_list => file_list, :orig_path => orig_path} end def link(root, action, changeset_node, after_path, opts={}) after_path = "/#{after_path}" if after_path changeset_node = changeset_node[0..11] text = opts.delete(:text) || changeset_node additional_opts = opts.map {|key, value| %{#{key}="#{value}"}}.join(" ") %{<a href="#{root}/#{action}/#{changeset_node}#{after_path}" #{additional_opts}> #{text} </a>} end def link_to_changeset(root, changeset_node, opts={}) link(root, :changeset, changeset_node, nil, opts) end def link_to_file(root, changeset_node, file=nil, opts={}) opts[:text] ||= file link(root, :code, changeset_node, file, opts) end def link_to_file_raw(root, changeset_node, file=nil, opts={}) opts[:text] ||= file link(root, :raw, changeset_node, file, opts) end def link_to_file_diff(root, changeset_node, file=nil, opts={}) opts[:text] ||= file link(root, :diff, changeset_node, file, opts) end def highlight_text(text, opts = {:format => "ruby", :theme => "twilight", :lines => false}) require 'uv' ::Haml::Helpers.preserve(Uv.parse( text.rstrip, "xhtml", opts[:format].to_s, opts[:lines], opts[:theme])) end def rel_date(o_date) a = (Time.now-o_date).to_i case a when 0 then return 'just now' when 1 then return 'a second ago' when 2..59 then return a.to_s+' seconds ago' when 60..119 then return 'a minute ago' #120 = 2 minutes when 120..3540 then return (a/60).to_i.to_s+' minutes ago' when 3541..7100 then return 'an hour ago' # 3600 = 1 hour when 7101..82800 then return ((a+99)/3600).to_i.to_s+' hours ago' when 82801..172000 then return 'a day ago' # 86400 = 1 day when 172001..518400 then return ((a+800)/(60*60*24)).to_i.to_s+' days ago' end return o_date.strftime("%B %d, %Y") end def format_for_filename(ext) ext = File.extname(ext) return :text if ext.nil? || ext.empty? case ext.downcase when ".rb" :ruby when ".py" :python when ".cpp" :"c++" when ".txt" :text else ext[1..-1].to_sym end end def parse_diff(input_diff) line_counter_a, line_counter_b = 0, 0 input_diff.split_lines_better.map do |line| if line[0,1] == ' ' res = %{<li class='diff-unmod'><pre>#{line_counter_a} #{line_counter_b} #{line.rstrip}</pre></li>\n} line_counter_a += 1 line_counter_b += 1 elsif line[0,3] == '+++' elsif line[0,3] == '---' elsif line[0,1] == '+' res = %{<li class='diff-add'><pre>#{" " * line_counter_b.to_s.size} #{line_counter_b} #{line.rstrip}</pre></li>\n} line_counter_b += 1 elsif line[0,1] == '-' res = %{<li class='diff-del'><pre>#{line_counter_a} #{" " * line_counter_a.to_s.size} #{line.rstrip}</pre></li>\n} line_counter_a += 1 elsif line[0,2] == '@@' line_counter_a, line_counter_b = line.scan(/\-(\d+),\d+ \+(\d+),\d+/).shift.map {|x| x.to_i} res = %{<li class='diff-ctx'><pre> #{line.rstrip}</pre></li>\n} end res end end end end end end