ginatra 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 */