ginatra 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.gitattributes +2 -0
  2. data/.gitignore +13 -0
  3. data/.gitmodules +3 -0
  4. data/README.md +127 -0
  5. data/Rakefile +85 -0
  6. data/TODO.md +10 -0
  7. data/VERSION +1 -0
  8. data/bin/ginatra +60 -0
  9. data/bin/ginatra-daemon +81 -0
  10. data/bin/ginatra-directory +60 -0
  11. data/bin/ginatra-server +28 -0
  12. data/config.ru +7 -0
  13. data/features/pages.feature +33 -0
  14. data/features/step_definitions/page_steps.rb +36 -0
  15. data/features/support/env.rb +12 -0
  16. data/ginatra.gemspec +107 -0
  17. data/lib/ginatra/config.rb +55 -0
  18. data/lib/ginatra/helpers.rb +112 -0
  19. data/lib/ginatra/repo.rb +50 -0
  20. data/lib/ginatra/repo_list.rb +53 -0
  21. data/lib/ginatra.rb +185 -0
  22. data/lib/sinatra/partials.rb +17 -0
  23. data/public/favicon.ico +0 -0
  24. data/public/img/add.png +0 -0
  25. data/public/img/diff.png +0 -0
  26. data/public/img/doc.png +0 -0
  27. data/public/img/rm.png +0 -0
  28. data/public/img/tree.png +0 -0
  29. data/public/src/blank.gif +0 -0
  30. data/public/src/colour.css +85 -0
  31. data/public/src/commit.css +211 -0
  32. data/public/src/default.css +115 -0
  33. data/public/src/ginatra.js +9 -0
  34. data/public/src/highlight.pack.js +1 -0
  35. data/public/src/iepngfix.htc +103 -0
  36. data/public/src/index.css +92 -0
  37. data/public/src/jquery-1.3.2.min.js +19 -0
  38. data/public/src/lists.css +25 -0
  39. data/public/src/reset.css +49 -0
  40. data/public/src/table.css +33 -0
  41. data/public/src/type.css +30 -0
  42. data/rackup.ru +7 -0
  43. data/repos/README.md +8 -0
  44. data/spec/repo_list_spec.rb +22 -0
  45. data/spec/repo_spec.rb +58 -0
  46. data/spec/spec_helper.rb +30 -0
  47. data/views/_actor_box.erb +13 -0
  48. data/views/_commit_info_box.erb +27 -0
  49. data/views/_header.erb +6 -0
  50. data/views/_tree_part.erb +11 -0
  51. data/views/atom.builder +32 -0
  52. data/views/blob.erb +9 -0
  53. data/views/commit.erb +20 -0
  54. data/views/index.erb +12 -0
  55. data/views/layout.erb +35 -0
  56. data/views/log.erb +64 -0
  57. data/views/tree.erb +24 -0
  58. metadata +146 -0
@@ -0,0 +1,55 @@
1
+ module Ginatra
2
+ class Config
3
+
4
+ current_path = File.expand_path("#{File.dirname(__FILE__)}")
5
+ CONFIG_PATH = File.expand_path("~/.ginatra/config.yml")
6
+ DEFAULT_CONFIG = {
7
+ :git_dirs => [File.expand_path("#{current_path}/../../repos/*")],
8
+ :ignored_files => ['README.md'],
9
+ :description => "View My Git Repositories",
10
+ :port => 9797
11
+ }
12
+
13
+ def self.setup! # Very Destructive Method. Use with care!
14
+ File.open(CONFIG_PATH, 'w') do |f|
15
+ YAML.dump(DEFAULT_CONFIG, f)
16
+ end
17
+ end
18
+
19
+ def self.load!
20
+ @config = {}
21
+ begin
22
+ @config = YAML.load_file(CONFIG_PATH)
23
+ rescue Errno::ENOENT
24
+ end
25
+ @config = DEFAULT_CONFIG.merge(@config)
26
+ end
27
+
28
+ def self.dump!
29
+ File.open(CONFIG_PATH, 'w') do |f|
30
+ YAML.dump(@config, f)
31
+ end
32
+ end
33
+
34
+ def self.method_missing(sym, *args, &block)
35
+ if @config.respond_to?(sym)
36
+ @config.send(sym, *args, &block)
37
+ elsif @config.has_key?(sym)
38
+ @config[sym]
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ def self.respond_to?(name)
45
+ if @config.respond_to?(name)
46
+ true
47
+ elsif @config.has_key?(name)
48
+ true
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,112 @@
1
+ require "digest/md5"
2
+
3
+ module Ginatra
4
+ # Actually useful stuff
5
+ module Helpers
6
+
7
+ def gravatar_url(email)
8
+ "https://secure.gravatar.com/avatar/#{Digest::MD5.hexdigest(email)}?s=40"
9
+ end
10
+
11
+ def nicetime(date)
12
+ date.strftime("%b %d, %Y – %H:%M")
13
+ end
14
+
15
+ def actor_box(actor, role, date)
16
+ partial(:actor_box, :locals => { :actor => actor, :role => role, :date => date })
17
+ end
18
+
19
+ def actor_boxes(commit)
20
+ o = actor_box(commit.committer, :committer, commit.committed_date)
21
+ if commit.author.name != commit.committer.name
22
+ o = actor_box(commit.author, :author, commit.authored_date) + o
23
+ end
24
+ end
25
+
26
+ def commit_ref(ref, repo_param)
27
+ ref_class = ref.class.to_s.split("::")[1].to_s
28
+ "<a class=\"ref #{ref_class}\" href=\"/#{repo_param}/#{ref.name}\">#{ref.name}</a>"
29
+ end
30
+
31
+ def commit_refs(commit, repo_param)
32
+ commit.refs.map{ |r| commit_ref(r, repo_param) }.join("\n")
33
+ end
34
+
35
+ def archive_link(tree, repo_param)
36
+ "<a class=\"download\" href=\"/#{repo_param}/archive/#{tree.id}.tar.gz\" title=\"Download a tar.gz snapshot of this Tree\">Download Archive</a>"
37
+ end
38
+
39
+ def patch_link(commit, repo_param)
40
+ "<a class=\"download\" href=\"/#{repo_param}/commit/#{commit.id}.patch\" title=\"Download a patch file of this Commit\">Download Patch</a>"
41
+ end
42
+
43
+ # The only reason this doesn't work 100% of the time is because grit doesn't :/
44
+ # if i find a fix, it'll go upstream :D
45
+ def file_listing(commit)
46
+ count = 0
47
+ out = commit.diffs.map do |diff|
48
+ count = count + 1
49
+ if diff.deleted_file
50
+ %(<li class='file_rm'><a href='#file_#{count}'>#{diff.a_path}</a></li>)
51
+ else
52
+ cla = diff.new_file ? "add" : "diff"
53
+ %(<li class='file_#{cla}'><a href='#file_#{count}'>#{diff.a_path}</a></li>)
54
+ end
55
+ end
56
+ "<ul id='files'>#{out.join}</ul>"
57
+ end
58
+
59
+ def diff(diff)
60
+ diff = CodeRay.scan(diff, :diff).div(:line_numbers => :table, :css => :class)
61
+ end
62
+
63
+ # Stolen from rails: ActionView::Helpers::TextHelper#simple_format
64
+ # and simplified to just use <p> tags without any options
65
+ # modified since
66
+ def simple_format(text)
67
+ text.gsub!(/ +/, " ")
68
+ text.gsub!(/\r\n?/, "\n")
69
+ text.gsub!(/\n/, "<br />\n")
70
+ text
71
+ end
72
+
73
+ # stolen from rails: ERB::Util
74
+ def html_escape(s)
75
+ s.to_s.gsub(/[&"<>]/) do |special|
76
+ { '&' => '&amp;',
77
+ '>' => '&gt;',
78
+ '<' => '&lt;',
79
+ '"' => '&quot;' }[special]
80
+ end
81
+ end
82
+ alias :h :html_escape
83
+
84
+ # Stolen and bastardised from rails
85
+ def truncate(text, options={})
86
+ options[:length] ||= 30
87
+ options[:omission] ||= "..."
88
+
89
+ if text
90
+ l = options[:length] - options[:omission].length
91
+ chars = text
92
+ stop = options[:separator] ? (chars.rindex(options[:separator], l) || l) : l
93
+ (chars.length > options[:length] ? chars[0...stop] + options[:omission] : text).to_s
94
+ end
95
+ end
96
+
97
+ # stolen from Marley
98
+ def rfc_date(datetime)
99
+ datetime.strftime("%Y-%m-%dT%H:%M:%SZ") # 2003-12-13T18:30:02Z
100
+ end
101
+
102
+ # stolen from Marley
103
+ def hostname
104
+ (request.env['HTTP_X_FORWARDED_SERVER'] =~ /[a-z]*/) ? request.env['HTTP_X_FORWARDED_SERVER'] : request.env['HTTP_HOST']
105
+ end
106
+
107
+ def atom_feed_link(repo_param, ref=nil)
108
+ "<a href=\"/#{repo_param}#{"/#{ref}" if !ref.nil?}.atom\" title=\"Atom Feed\" class=\"atom\">Feed</a>"
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,50 @@
1
+ # to make refs work!
2
+ module Grit
3
+ class Commit
4
+ attr_accessor :refs
5
+ end
6
+ end
7
+
8
+ module Ginatra
9
+ # Convenience class for me!
10
+ class Repo
11
+
12
+ attr_reader :name, :param, :description
13
+
14
+ def initialize(path)
15
+ @repo = Grit::Repo.new(path)
16
+ @param = File.split(path).last
17
+ @name = @param
18
+ @description = @repo.description
19
+ @description = "Please edit the #{@repo.path}/description file for this repository and set the description for it." if /^Unnamed repository;/.match(@description)
20
+ @repo
21
+ end
22
+
23
+ def commit(id)
24
+ @commit = @repo.commit(id)
25
+ raise(Ginatra::InvalidCommit.new(id)) if @commit.nil?
26
+ add_refs(@commit)
27
+ @commit
28
+ end
29
+
30
+ def commits(start = 'master', max_count = 10, skip = 0)
31
+ raise(Ginatra::Error.new("max_count cannot be less than 0")) if max_count < 0
32
+ @repo.commits(start, max_count, skip).each do |commit|
33
+ add_refs(commit)
34
+ end
35
+ end
36
+
37
+ # TODO: Perhaps move into commit class.
38
+ def add_refs(commit)
39
+ commit.refs = []
40
+ refs = @repo.refs.select { |ref| ref.commit.id == commit.id }
41
+ commit.refs << refs
42
+ commit.refs.flatten!
43
+ end
44
+
45
+ def method_missing(sym, *args, &block)
46
+ @repo.send(sym, *args, &block)
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,53 @@
1
+ require 'singleton'
2
+
3
+ module Ginatra
4
+ # Convenience class for me!
5
+ class RepoList
6
+ include Singleton
7
+ attr_accessor :list
8
+
9
+ def initialize
10
+ self.list = []
11
+ self.refresh
12
+ end
13
+
14
+ def self.list
15
+ self.instance.refresh
16
+ self.instance.list
17
+ end
18
+
19
+ def refresh
20
+ Ginatra::Config.git_dirs.map! do |git_dir|
21
+ files = Dir.glob(git_dir)
22
+ files.each { |e| add(e) unless Ginatra::Config.ignored_files.include?(File.split(e).last) }
23
+ end
24
+ end
25
+
26
+ def add(path, param = File.split(path).last)
27
+ unless self.has_repo?(param)
28
+ list << Repo.new(path)
29
+ end
30
+ end
31
+
32
+ def has_repo?(local_param)
33
+ !list.find { |r| r.param == local_param }.nil?
34
+ end
35
+
36
+ def find(local_param)
37
+ if repo = list.find { |r| r.param == local_param }
38
+ repo
39
+ else
40
+ refresh
41
+ list.find { |r| r.param == local_param }
42
+ end
43
+ end
44
+
45
+ def self.find(local_param)
46
+ self.instance.find(local_param)
47
+ end
48
+
49
+ def self.method_missing(sym, *args, &block)
50
+ instance.send(sym, *args, &block)
51
+ end
52
+ end
53
+ end
data/lib/ginatra.rb ADDED
@@ -0,0 +1,185 @@
1
+ require 'sinatra/base'
2
+ require 'grit'
3
+ require 'coderay'
4
+
5
+ current_path = File.expand_path(File.dirname(__FILE__))
6
+
7
+ module Ginatra; end
8
+
9
+ # Loading in reverse because RepoList needs to be loaded before MultiRepoList
10
+ Dir.glob("#{current_path}/ginatra/*.rb").reverse.each { |f| require f }
11
+
12
+ require "#{current_path}/sinatra/partials"
13
+
14
+ # Written myself. i know, what the hell?!
15
+ module Ginatra
16
+
17
+ class Error < StandardError; end
18
+ class CommitsError < Error; end
19
+
20
+ class InvalidCommit < Error
21
+ def initialize(id)
22
+ super("Could not find a commit with the id of #{id}")
23
+ end
24
+ end
25
+
26
+ current_path = File.expand_path(File.dirname(__FILE__))
27
+ VERSION = File.new("#{current_path}/../VERSION").read
28
+
29
+ class App < Sinatra::Base
30
+
31
+ configure do
32
+ current_path = File.expand_path(File.dirname(__FILE__))
33
+ Config.load!
34
+ set :port, Ginatra::Config[:port]
35
+ set :raise_errors, Proc.new { test? }
36
+ set :show_exceptions, Proc.new { development? }
37
+ set :dump_errors, true
38
+ set :logging, Proc.new { !test? }
39
+ set :static, true
40
+ set :public, "#{current_path}/../public"
41
+ set :views, "#{current_path}/../views"
42
+ end
43
+
44
+ helpers do
45
+ include Helpers
46
+ include ::Sinatra::Partials
47
+ end
48
+
49
+ error CommitsError do
50
+ 'No commits were returned for ' + request.uri
51
+ end
52
+
53
+ get '/' do
54
+ erb :index
55
+ end
56
+
57
+ get '/:repo.atom' do
58
+ @repo = RepoList.find(params[:repo])
59
+ @commits = @repo.commits
60
+ return "" if @commits.empty?
61
+ etag(@commits.first.id)
62
+ builder :atom, :layout => nil
63
+ end
64
+
65
+ get '/:repo' do
66
+ @repo = RepoList.find(params[:repo])
67
+ @commits = @repo.commits
68
+ etag(@commits.first.id)
69
+ erb :log
70
+ end
71
+
72
+ get '/:repo/:ref.atom' do
73
+ @repo = RepoList.find(params[:repo])
74
+ @commits = @repo.commits(params[:ref])
75
+ return "" if @commits.empty?
76
+ etag(@commits.first.id)
77
+ builder :atom, :layout => nil
78
+ end
79
+
80
+ get '/:repo/:ref' do
81
+ params[:page] = 1
82
+ @repo = RepoList.find(params[:repo])
83
+ @commits = @repo.commits(params[:ref])
84
+ etag(@commits.first.id)
85
+ erb :log
86
+ end
87
+
88
+ get '/:repo/commit/:commit.patch' do
89
+ response['Content-Type'] = "text/plain"
90
+ @repo = RepoList.find(params[:repo])
91
+ @repo.git.format_patch({}, "--stdout", "-1", params[:commit])
92
+ end
93
+
94
+ get '/:repo/commit/:commit' do
95
+ @repo = RepoList.find(params[:repo])
96
+ @commit = @repo.commit(params[:commit]) # can also be a ref
97
+ etag(@commit.id)
98
+ erb(:commit)
99
+ end
100
+
101
+ get '/:repo/archive/:tree.tar.gz' do
102
+ response['Content-Type'] = "application/x-tar-gz"
103
+ @repo = RepoList.find(params[:repo])
104
+ @repo.archive_tar_gz(params[:tree])
105
+ end
106
+
107
+ get '/:repo/tree/:tree' do
108
+ @repo = RepoList.find(params[:repo])
109
+
110
+ if (tag = @repo.git.method_missing('rev_parse', {}, '--verify', "#{params[:tree]}^{tree}")).empty?
111
+ # we don't have a tree.
112
+ not_found
113
+ else
114
+ etag(tag)
115
+ end
116
+
117
+ @tree = @repo.tree(params[:tree]) # can also be a ref (i think)
118
+ @path = {}
119
+ @path[:tree] = "/#{params[:repo]}/tree/#{params[:tree]}"
120
+ @path[:blob] = "/#{params[:repo]}/blob/#{params[:tree]}"
121
+ erb(:tree)
122
+ end
123
+
124
+ get '/:repo/tree/:tree/*' do # for when we specify a path
125
+ @repo = RepoList.find(params[:repo])
126
+ @tree = @repo.tree(params[:tree])/params[:splat].first # can also be a ref (i think)
127
+ if @tree.is_a?(Grit::Blob)
128
+ # we need @tree to be a tree. if it's a blob, send it to the blob page
129
+ # this allows people to put in the remaining part of the path to the file, rather than endless clicks like you need in github
130
+ redirect "/#{params[:repo]}/blob/#{params[:tree]}/#{params[:splat].first}"
131
+ else
132
+ etag(@tree.id)
133
+ @path = {}
134
+ @path[:tree] = "/#{params[:repo]}/tree/#{params[:tree]}/#{params[:splat].first}"
135
+ @path[:blob] = "/#{params[:repo]}/blob/#{params[:tree]}/#{params[:splat].first}"
136
+ erb(:tree)
137
+ end
138
+ end
139
+
140
+ get '/:repo/blob/:blob' do
141
+ @repo = RepoList.find(params[:repo])
142
+ @blob = @repo.blob(params[:blob])
143
+ etag(@blob.id)
144
+ erb(:blob)
145
+ end
146
+
147
+ get '/:repo/blob/:tree/*' do
148
+ @repo = RepoList.find(params[:repo])
149
+ @blob = @repo.tree(params[:tree])/params[:splat].first
150
+ if @blob.is_a?(Grit::Tree)
151
+ # as above, we need @blob to be a blob. if it's a tree, send it to the tree page
152
+ # this allows people to put in the remaining part of the path to the folder, rather than endless clicks like you need in github
153
+ redirect "/#{params[:repo]}/tree/#{params[:tree]}/#{params[:splat].first}"
154
+ else
155
+ etag(@blob.id)
156
+ extension = params[:splat].first.split(".").last
157
+ @highlighter = case extension
158
+ when 'js'
159
+ 'javascript'
160
+ when 'css'
161
+ 'css'
162
+ end
163
+
164
+ @highlighter ||= 'ruby'
165
+
166
+ erb(:blob)
167
+ end
168
+ end
169
+
170
+ get '/:repo/:ref/:page' do
171
+ pass unless params[:page] =~ /^(\d)+$/
172
+ params[:page] = params[:page].to_i
173
+ @repo = RepoList.find(params[:repo])
174
+ @commits = @repo.commits(params[:ref], 10, (params[:page] - 1) * 10)
175
+ @next_commits = !@repo.commits(params[:ref], 10, params[:page] * 10).empty?
176
+ if params[:page] - 1 > 0
177
+ @previous_commits = !@repo.commits(params[:ref], 10, (params[:page] - 1) * 10).empty?
178
+ end
179
+ @separator = @next_commits && @previous_commits
180
+ erb :log
181
+ end
182
+
183
+ end # App
184
+
185
+ end # Ginatra
@@ -0,0 +1,17 @@
1
+ # stolen from http://github.com/cschneid/irclogger/blob/master/lib/partials.rb
2
+ module Sinatra::Partials
3
+ def partial(template, *args)
4
+ template_array = template.to_s.split('/')
5
+ template = template_array[0..-2].join('/') + "/_#{template_array[-1]}"
6
+ options = args.last.is_a?(Hash) ? args.pop : {}
7
+ options.merge!(:layout => false)
8
+ if collection = options.delete(:collection) then
9
+ collection.inject([]) do |buffer, member|
10
+ buffer << erb(:"#{template}", options.merge(:layout =>
11
+ false, :locals => {template_array[-1].to_sym => member}))
12
+ end.join("\n")
13
+ else
14
+ erb(:"#{template}", options)
15
+ end
16
+ end
17
+ end
File without changes
Binary file
Binary file
Binary file
data/public/img/rm.png ADDED
Binary file
Binary file
Binary file
@@ -0,0 +1,85 @@
1
+ /* Main Colours */
2
+ body {
3
+ background: #fff;
4
+ }
5
+ #footer {
6
+ border-top: 1px solid #eee;
7
+ background: #EFEFF7;
8
+ }
9
+ h3{
10
+ background: #F2F2F2;
11
+ color: #224FBA;
12
+ font-weight: bold;
13
+ }
14
+ div.active{
15
+ border-top: 3px solid #dde;
16
+ background: #EFEFF7;
17
+ }
18
+ a { color: #333; }
19
+ a:visited { color: #555; }
20
+ a:hover,
21
+ a:active { color: #333; }
22
+ .quiet { color: #7F7F7F; }
23
+ /* End Main Colours */
24
+
25
+ /* Type Colours */
26
+ body { color: #222; }
27
+ h1 { color: #224FBA; background: #E6E6F2;}
28
+ h1 a, h1 a:visited, h1 a:hover, h1 a:active { color: #224fba; text-decoration: none; }
29
+ h2 a, h2 a:visited, h2 a:hover, h2 a:active { text-decoration: none; }
30
+ /* End Type Colours */
31
+
32
+ /* Table Colours */
33
+ td {
34
+ border-top: 1px solid #7F7F7F;
35
+ }
36
+ .repo-info, .ginatra-info, dl, td {
37
+ background: #E6E6F2;
38
+ }
39
+ .repo-list dl dt:first-child{
40
+ border-top: none;
41
+ }
42
+ /* End Table Colours */
43
+
44
+ /* Commit Colours */
45
+ #commit #info,
46
+ #commit #author,
47
+ #commit #committer,
48
+ .short-commit-message,
49
+ .commit-tree,
50
+ .commit-diff,
51
+ div.blob {
52
+ background: #E6E6F2;
53
+ }
54
+ .commit-author img,
55
+ .commit-committer img {
56
+ border: 1px solid #7F7F7F;
57
+ }
58
+ .commit-tree .tree {
59
+ background: url('../img/tree.png') no-repeat top left;
60
+ }
61
+ .commit-tree .blob {
62
+ background: url('../img/doc.png') no-repeat top left;
63
+ }
64
+ .commit-diff h4,
65
+ div.blob h4 {
66
+ background: #e7e7e7;
67
+ }
68
+ li.diff { background: url('../../img/diff.png') no-repeat center left; }
69
+ li.add a{ color: #080; }
70
+ li.add { background: url('../../img/add.png') no-repeat center left; }
71
+ li.rm {
72
+ color: #800;
73
+ background: url('../../img/rm.png') no-repeat center left;
74
+ }
75
+ a.ref { background-color: #7782FC; color: #fff; }
76
+ a.download { background-color: #7782FC; color: #fff; }
77
+ a.atom { background-color: #7782FC; color: #fff; }
78
+ a.head { background-color: #7782FC; }
79
+ a.tag { background-color: #d95959; }
80
+ a.remote { background-color: #fafa78; }
81
+
82
+ .commit, .list div, .single-commit #message {
83
+ background: #dcdcdc;
84
+ }
85
+ /* End Commit Colours */