grudge 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,23 @@
1
+ require 'ostruct'
2
+
3
+ module Grudge
4
+ module Config
5
+ class ConfigFile
6
+ def initialize(config_file, env=:development)
7
+ @env_config = OpenStruct.new(YAML.load(config_file)[env.to_s])
8
+ end
9
+
10
+ def method_missing(meth, *args, &blk)
11
+ @env_config.send(meth)
12
+ end
13
+ end # ConfigFile
14
+
15
+ def self.fire_me_up(env)
16
+ @config = ConfigFile.new(File.new('config/grudge.yml'), env)
17
+ end
18
+
19
+ def self.method_missing(meth, *args, &blk)
20
+ @config.send(meth)
21
+ end
22
+ end # Config
23
+ end # Grudge
@@ -0,0 +1,15 @@
1
+ require 'data_mapper'
2
+ require 'data_objects'
3
+ require 'dm-validations'
4
+
5
+ module Grudge
6
+ module Database
7
+ def self.fire_me_up
8
+ puts "Connecting to database and migrating"
9
+ db_config = Grudge::Config.database
10
+ db_config = db_config.symbolize_keys if db_config.kind_of?(Hash)
11
+ DataMapper.setup(:default, db_config)
12
+ DataMapper.auto_upgrade!
13
+ end
14
+ end # Database
15
+ end # Grudge
@@ -0,0 +1,18 @@
1
+ module Grudge
2
+ module Sinatra
3
+ module Extensions
4
+ def catch_all_css
5
+ get('/stylesheets/*.css') {sass params["splat"].first.to_sym}
6
+ end
7
+
8
+ # When you don't want to do anything special, but load
9
+ def get_obvious(name)
10
+ get "/#{name}" do
11
+ title = name.to_s
12
+ haml name.to_sym
13
+ end
14
+ end
15
+
16
+ end # Extensions
17
+ end # Sinatra
18
+ end # Grudge
@@ -0,0 +1,28 @@
1
+ module Grudge
2
+ module Sinatra
3
+ module Helpers
4
+ def set_title(msg) @title = msg; end
5
+ def title; "Grudge - #{@title}"; end
6
+
7
+ def anchor(name, url, options={})
8
+ defaults = {:href => url}
9
+ options_str = hash_to_attributes(defaults.merge(options))
10
+ %Q[<a #{options_str}>#{name}</a>]
11
+ end
12
+
13
+ def stylesheet_include(name, options={})
14
+ defaults = {:href => "/stylesheets/#{name}.css", :media => "screen",
15
+ :rel => "stylesheet", :type => "text/css"}
16
+ options_str = hash_to_attributes(defaults.merge(options))
17
+ %Q[<link #{options_str}/>]
18
+ end
19
+
20
+ def repository_name; Grudge::Repository.origin_uri.to_s; end
21
+
22
+ private
23
+ def hash_to_attributes(options)
24
+ options.map {|k,v| "#{k}=\"#{v}\""}.join(' ')
25
+ end
26
+ end # Helpers
27
+ end # Sinatra
28
+ end # Grudge
@@ -0,0 +1,47 @@
1
+ require 'git'
2
+ require 'logger'
3
+
4
+ module Grudge
5
+ module Repository
6
+
7
+ def self.origin_uri
8
+ URI.parse(Grudge::Config.repo["origin"])
9
+ end
10
+
11
+ def self.master_path; Grudge::Config.repo["master"]; end
12
+
13
+ def self.open
14
+ @repo ||= repo(:open, master_path)
15
+ end
16
+
17
+ def self.open_or_clone
18
+ return open if File.exists?(master_path)
19
+ repo(:clone, origin_uri, master_path)
20
+ end
21
+
22
+ def self.pull
23
+ open.pull('origin', 'origin/master', 'grudge origin pull')
24
+ end
25
+
26
+ def self.object(sha)
27
+ open.object(sha)
28
+ end
29
+
30
+ def self.latest_commits_since(since=nil, &block)
31
+ log_opts = {}
32
+ log_opts[:between] = [since, ''] if since
33
+ pull
34
+ open.lib.log_commits(log_opts).map do |commit|
35
+ yield object(commit) if block_given?
36
+ end
37
+ end
38
+
39
+ private
40
+ # ensuring that we always append the logger to the options
41
+ def self.repo(command, *args)
42
+ @logger ||= Logger.new(Grudge::Config.log)
43
+ args << {:log => @logger}
44
+ Git.send(command, *args)
45
+ end
46
+ end # Repository
47
+ end # Grudge
@@ -0,0 +1,55 @@
1
+ class Commit
2
+ include DataMapper::Resource
3
+ property :id, Serial
4
+ property :sha, String, :length => 40, :nullable => false,
5
+ :unique_index => true
6
+ property :committed_at, DateTime, :nullable => false
7
+ property :score, Integer, :default => 0
8
+ property :created_at, DateTime
9
+ property :updated_at, DateTime
10
+
11
+ has n, :votes
12
+
13
+ def awesome!; vote!(Vote.awesome); end
14
+ def awesome_score; vote_score(:score.gt); end
15
+ def awesome?; score > 0; end
16
+
17
+ def suck!; vote!(Vote.suck); end
18
+ def suck_score; vote_score(:score.lt); end
19
+ def suck?; score < 0; end
20
+
21
+ # Helpers
22
+
23
+ def self.last; first(:order => [:committed_at.desc]); end
24
+ def self.recent; first(20, :order => [:committed_at.desc]); end
25
+ def self.popular; first(20, :order => [:score.desc]); end
26
+ def self.unpopular; first(20, :order => [:score.asc]); end
27
+ def self.search(sha)
28
+ object = Grudge::Repository.object(sha)
29
+ first(:sha => object.sha) if object
30
+ end
31
+
32
+ # Repository delegation methods
33
+
34
+ def author_name; repository_object.author.name; end
35
+ def message; repository_object.message; end
36
+
37
+ def self.download!
38
+ last_commit = last
39
+ last_sha = last_commit ? last_commit.sha : nil
40
+ Grudge::Repository.latest_commits_since(last_sha) do |repo_commit|
41
+ Commit.create!(:sha => repo_commit.sha,
42
+ :committed_at => repo_commit.committer_date)
43
+ end
44
+ end
45
+
46
+ private
47
+ def vote!(vote)
48
+ votes << vote
49
+ update_attributes({:score => @score + vote.score})
50
+ end
51
+
52
+ def vote_score(condition); votes.sum(:score, condition => 0); end
53
+
54
+ def repository_object; @object ||= Grudge::Repository.object(sha); end
55
+ end
@@ -0,0 +1,16 @@
1
+ class Vote
2
+ include DataMapper::Resource
3
+ property :id, Serial
4
+ property :commit_id, Integer, :nullable => false
5
+ property :score, Integer, :nullable => false
6
+ property :created_at, DateTime
7
+
8
+ belongs_to :commit
9
+
10
+ def self.awesome; self.new(:score => 1); end
11
+ def self.suck; self.new(:score => -1); end
12
+
13
+ def awesome?; score > 0; end
14
+ def suck?; score < 0; end
15
+
16
+ end
@@ -0,0 +1,26 @@
1
+ %html
2
+ %head
3
+ %title= title
4
+ = stylesheet_include :application
5
+ %body
6
+ #header
7
+ %span Grudge
8
+ %span.slogan commit some fun
9
+
10
+ #nav
11
+ = anchor('Recent', "/")
12
+ = anchor('Popular', "/popular")
13
+ = anchor('Unpopular', "/unpopular")
14
+ %span#searchbox
15
+ Find a Commit
16
+ %form.box{:method => "post", :action => '/search'}
17
+ %input{:type => 'text', :name => "query", :maxlength => 30, :size => 40}
18
+
19
+ %p
20
+ Repository:
21
+ = repository_name
22
+
23
+ #content= yield
24
+ #footer.clear
25
+ &copy; 2008 - Thumble Monks -
26
+ = Grudge::VERSION
@@ -0,0 +1,110 @@
1
+ !serif = times
2
+ !sans = Arial
3
+
4
+ !light_text_color = #999
5
+ !medium_text_color = #666
6
+
7
+ !highlight_color = #ffc
8
+
9
+ !link_color = #24b
10
+
11
+ body
12
+ :background-color white
13
+ :width 900px
14
+ :margin 0 auto
15
+
16
+ .clear
17
+ :clear both
18
+
19
+ a, a:link, a:visited
20
+ :color = !link_color
21
+
22
+ #header
23
+ :font-family = !serif
24
+ :font-size 180%
25
+ :margin-top 1ex
26
+ :padding-bottom .5ex
27
+ :border-bottom 1px solid #999
28
+ .slogan
29
+ :font-family = !sans
30
+ :color green
31
+ :font-size 60%
32
+
33
+ #footer
34
+ :font-family = !sans
35
+ :font-size 70%
36
+ :padding-top 1ex
37
+
38
+ #nav
39
+ :padding-top 1ex
40
+ :font-family = !sans
41
+ :font-size 85%
42
+ #searchbox
43
+ :margin-left 5em
44
+ form
45
+ :display inline
46
+
47
+ #content
48
+ :font-family = !sans
49
+
50
+ h2
51
+ :margin 1ex 0ex
52
+ :font-weight normal
53
+ :font-family = !serif
54
+ :color = !medium_text_color
55
+
56
+ h3
57
+ :margin 0
58
+ :font-weight normal
59
+ :font-family = !serif
60
+ :color = !medium_text_color
61
+
62
+ ul
63
+ :list-style-type none
64
+ :padding 0
65
+ li
66
+ :margin 0 0 1em 0
67
+ :padding 0
68
+ &.commit
69
+ .awesome
70
+ :color green
71
+ .suck
72
+ :color #e24
73
+ .vote
74
+ :float left
75
+ :text-align right
76
+ :width 3em
77
+ :padding 0 1ex .5ex 1ex
78
+ :font-size 175%
79
+ :color #555
80
+ .info
81
+ :font-size 90%
82
+ :float left
83
+ :width 50%
84
+ :border-left 1px dotted #ddd
85
+ :padding 0 0 0 1em
86
+ .date
87
+ :color #69c
88
+ :font-size 90%
89
+ :width 15em
90
+ :float left
91
+ .author
92
+ :float left
93
+ :font-size 90%
94
+ .sha
95
+ :color = !light_text_color
96
+ :font-size 90%
97
+ a
98
+ :color = !light_text_color
99
+ .nav
100
+ :float left
101
+ :border-left 1px dotted #ddd
102
+ :padding 0 0 0 1em
103
+ :width 25%
104
+ :font-size 70%
105
+ .message
106
+ :margin-top .5ex
107
+ :background-color #f8f8f8
108
+ :font-family = !serif
109
+ pre
110
+ :padding 1ex
@@ -0,0 +1,28 @@
1
+ %h2 Commits
2
+ %ul
3
+ - @commits.each do |commit|
4
+ %li.commit
5
+ .vote
6
+ .net
7
+ - if commit.awesome?
8
+ %span.awesome= commit.score
9
+ - elsif commit.suck?
10
+ %span.suck= commit.score
11
+ - else
12
+ = commit.score
13
+ .info
14
+ .date= commit.committed_at.strftime("%b %m, %Y %X")
15
+ .author= commit.author_name
16
+ .sha.clear= anchor(commit.sha, "/#{commit.sha}")
17
+ .nav
18
+ %div
19
+ Score:
20
+ %span.awesome= commit.awesome_score
21
+ \/
22
+ %span.suck= commit.suck_score
23
+ Vote:
24
+ = anchor('Awesome', "/#{commit.sha}/awesome")
25
+ = anchor('Suck', "/#{commit.sha}/suck")
26
+ .message.clear
27
+ %pre= commit.message
28
+
@@ -0,0 +1 @@
1
+ I don't even know you anymore
@@ -0,0 +1,25 @@
1
+ %ul
2
+ %li.commit
3
+ .vote
4
+ .net
5
+ - if @commit.awesome?
6
+ %span.awesome= @commit.score
7
+ - elsif @commit.suck?
8
+ %span.suck= @commit.score
9
+ - else
10
+ = @commit.score
11
+ .info
12
+ .date= @commit.committed_at.strftime("%b %m, %Y %X")
13
+ .author= @commit.author_name
14
+ .sha.clear= @commit.sha
15
+ .nav
16
+ %div
17
+ Score:
18
+ %span.awesome= @commit.awesome_score
19
+ \/
20
+ %span.suck= @commit.suck_score
21
+ Vote:
22
+ = anchor('Awesome', "/#{@commit.sha}/awesome")
23
+ = anchor('Suck', "/#{@commit.sha}/suck")
24
+ .message.clear
25
+ %pre= @commit.message
@@ -0,0 +1,226 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class CommitTest < Test::Unit::TestCase
4
+ context "required attributes" do
5
+ setup do
6
+ @commit = Commit.new
7
+ @commit.valid?
8
+ @errors = @commit.errors
9
+ end
10
+
11
+ should "include sha" do
12
+ assert_equal ["Sha must not be blank"], @errors[:sha]
13
+ end
14
+
15
+ should "include committed at" do
16
+ assert_equal ["Committed at must not be blank"], @errors[:committed_at]
17
+ end
18
+ end # required attributes
19
+
20
+ context "unique attributes" do
21
+ setup do
22
+ @commit = Factory(:commit)
23
+ end
24
+
25
+ should "include unique sha" do
26
+ begin
27
+ Factory(:commit, :sha => @commit.sha)
28
+ flunk("Should hav raised a unique exception for :sha")
29
+ rescue Exception => e
30
+ assert_match %r[not unique], e.to_s
31
+ end
32
+ end
33
+ end # unique attributes
34
+
35
+ context "default values" do
36
+ setup { @commit = Commit.new }
37
+
38
+ should "be zero for score" do
39
+ assert_equal 0, @commit.score
40
+ end
41
+ end # default values
42
+
43
+ context "associations" do
44
+ setup { @commit = Factory(:commit) }
45
+ should "have n votes" do
46
+ assert_kind_of DataMapper::Associations::OneToMany::Proxy, @commit.votes
47
+ assert_equal (1.0/0), Commit.relationships[:votes].options[:max]
48
+ end
49
+ end # associations
50
+
51
+ context "scoring" do
52
+ context "awesome!" do
53
+ setup do
54
+ @commit = Factory(:commit)
55
+ @commit.awesome!
56
+ end
57
+
58
+ should_change "Vote.count", :by => 1
59
+
60
+ should "add a new positive vote" do
61
+ assert @commit.votes.all?(&:awesome?)
62
+ end
63
+
64
+ should "be accessible seperate from net score" do
65
+ assert_equal 1, @commit.awesome_score
66
+ end
67
+
68
+ should "update net score" do
69
+ assert_equal 1, @commit.score
70
+ end
71
+
72
+ should "be awesome?" do
73
+ assert @commit.awesome?
74
+ end
75
+
76
+ should "not suck?" do
77
+ deny @commit.suck?
78
+ end
79
+ end # awesome!
80
+
81
+ context "suck!" do
82
+ setup do
83
+ @commit = Factory(:commit)
84
+ @commit.suck!
85
+ end
86
+
87
+ should_change "Vote.count", :by => 1
88
+
89
+ should "add a new negative vote" do
90
+ assert @commit.votes.all?(&:suck?)
91
+ end
92
+
93
+ should "be accessible seperate from net score" do
94
+ assert_equal -1, @commit.suck_score
95
+ end
96
+
97
+ should "update net score" do
98
+ assert_equal -1, @commit.score
99
+ end
100
+
101
+ should "not be awesome?" do
102
+ deny @commit.awesome?
103
+ end
104
+
105
+ should "suck?" do
106
+ assert @commit.suck?
107
+ end
108
+ end # suck!
109
+
110
+ context "with equal mix" do
111
+ setup do
112
+ @commit = Factory(:commit)
113
+ @commit.awesome!
114
+ @commit.suck!
115
+ end
116
+
117
+ should_change "Vote.count", :by => 2
118
+
119
+ should "be neither awesome or sucky" do
120
+ deny @commit.awesome?
121
+ deny @commit.suck?
122
+ end
123
+
124
+ should "have zero score" do
125
+ assert_equal 0, @commit.score
126
+ end
127
+ end # with equal mix
128
+ end # scoring
129
+
130
+ context "helpers" do
131
+ setup do
132
+ @awesome = Factory(:commit, :committed_at => (Time.now - 7200))
133
+ @awesome.awesome!
134
+
135
+ @suck = Factory(:commit, :committed_at => (Time.now - 3600))
136
+ @suck.suck!
137
+
138
+ @neutral = Factory(:commit, :committed_at => (Time.now))
139
+ end
140
+
141
+ should "return most recently committed commits" do
142
+ assert_equal [@neutral, @suck, @awesome], Commit.recent
143
+ end
144
+
145
+ should "return most popular commits" do
146
+ assert_equal [@awesome, @neutral, @suck], Commit.popular
147
+ end
148
+
149
+ should "return most unpopular commits" do
150
+ assert_equal [@suck, @neutral, @awesome], Commit.unpopular
151
+ end
152
+
153
+ should "return last committed record" do
154
+ assert_equal @neutral, Commit.last
155
+ end
156
+ end # helpers
157
+
158
+ context "search" do
159
+ context "for existing object" do
160
+ setup do
161
+ @commit = Factory(:commit)
162
+ @search_key = 'abc'
163
+ Grudge::Repository.expects(:object).with(@search_key).
164
+ returns(O(:sha => @commit.sha))
165
+ end
166
+
167
+ should "find commit based on partial id" do
168
+ assert_equal @commit, Commit.search(@search_key)
169
+ end
170
+ end # for existing object
171
+
172
+ context "for nonexistant object" do
173
+ setup do
174
+ @search_key = 'abc'
175
+ Grudge::Repository.expects(:object).with(@search_key).returns(nil)
176
+ end
177
+
178
+ should "find nothing if sha does not match anything" do
179
+ assert_nil Commit.search(@search_key)
180
+ end
181
+ end # for nonexistant object
182
+ end # search
183
+
184
+ context "unstored values" do
185
+ setup do
186
+ @commit = Factory(:commit)
187
+ Grudge::Repository.expects(:object).with(@commit.sha).
188
+ returns(repository_commit)
189
+ end
190
+
191
+ should "delegate author name" do
192
+ assert_equal "Barney", @commit.author_name
193
+ end
194
+
195
+ should "delegate commit message" do
196
+ assert_equal "This is the shiznit!", @commit.message
197
+ end
198
+ end # unstored values
199
+
200
+ context "download" do
201
+ setup do
202
+ @repo_commits = (1..10).map {repository_commit}
203
+ end
204
+
205
+ context "when there is one existing commit" do
206
+ setup do
207
+ last_commit = Factory(:commit)
208
+ Grudge::Repository.stubs(:latest_commits_since).with(last_commit.sha).
209
+ multiple_yields(*@repo_commits)
210
+ Commit.download!
211
+ end
212
+
213
+ should_change "Commit.count", :by => 11
214
+ end # when there is one existing commit
215
+
216
+ context "when there are no existing commits" do
217
+ setup do
218
+ Grudge::Repository.expects(:latest_commits_since).with(nil).
219
+ multiple_yields(*@repo_commits)
220
+ Commit.download!
221
+ end
222
+
223
+ should_change "Commit.count", :by => 10
224
+ end # when there are no existing commits
225
+ end # download
226
+ end