grudge 0.1.0

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.
@@ -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