bananajour 2.1.9

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,21 @@
1
+ class Bananajour::Bonjour::Person
2
+
3
+ attr_accessor :name, :email, :uri , :gravatar
4
+
5
+ def initialize(name, email, uri, gravatar)
6
+ @name, @email, @uri, @gravatar = name, email, uri, gravatar
7
+ end
8
+
9
+ def ==(other)
10
+ self.uri == other.uri
11
+ end
12
+
13
+ def hash
14
+ to_hash.hash
15
+ end
16
+
17
+ def to_hash
18
+ {"name" => name, "email" => email, "uri" => uri, "gravatar" => gravatar}
19
+ end
20
+
21
+ end
@@ -0,0 +1,39 @@
1
+ class Bananajour::Bonjour::Repository
2
+
3
+ attr_accessor :name, :uri, :person
4
+
5
+ def initialize(name, uri, person)
6
+ @name, @uri, @person = name, uri, person
7
+ end
8
+
9
+ def html_id
10
+ Bananajour::Repository.html_id(name)
11
+ end
12
+
13
+ def ==(other)
14
+ self.uri == other.uri
15
+ end
16
+
17
+ def hash
18
+ to_hash.hash
19
+ end
20
+
21
+ def json_uri
22
+ "#{person.uri}#{name}.json"
23
+ end
24
+
25
+ def web_uri
26
+ "#{person.uri}##{html_id}"
27
+ end
28
+
29
+ def to_hash
30
+ {
31
+ "name" => name,
32
+ "uri" => uri,
33
+ "json_uri" => json_uri,
34
+ "web_uri" => web_uri,
35
+ "person" => person.to_hash
36
+ }
37
+ end
38
+
39
+ end
@@ -0,0 +1,36 @@
1
+ module Bananajour::Bonjour
2
+ class RepositoryBrowser
3
+
4
+ def initialize
5
+ @browser = Browser.new('_bananajour._git._tcp')
6
+ end
7
+
8
+ def repositories
9
+ @browser.replies.map do |reply|
10
+ Repository.new(
11
+ reply.text_record["name"],
12
+ reply.text_record["uri"],
13
+ Person.new(
14
+ reply.text_record["bjour-name"],
15
+ reply.text_record["bjour-email"],
16
+ reply.text_record["bjour-uri"],
17
+ reply.text_record["bjour-gravatar"]
18
+ )
19
+ )
20
+ end
21
+ end
22
+
23
+ def other_repositories
24
+ repositories.reject {|r| Bananajour.repositories.any? {|my_rep| my_rep.name == r.name}}
25
+ end
26
+
27
+ def repositories_similar_to(repository)
28
+ repositories.select {|r| r.name == repository.name}
29
+ end
30
+
31
+ def repositories_for(person)
32
+ repositories.select {|r| r.person == person}
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,80 @@
1
+ module Bananajour::Commands
2
+
3
+ def check_git!
4
+ if (version = `git --version`.strip) =~ /git version 1\.[12345]/
5
+ abort "You have #{version}, you need at least 1.6"
6
+ end
7
+ end
8
+
9
+ def check_git_config!
10
+ config_message = lambda {|key, example| "You haven't set your #{key} in your git config yet. To set it: git config --global #{key} '#{example}'"}
11
+ abort(config_message["user.name", "My Name"]) if config.name.empty?
12
+ abort(config_message["user.email", "name@domain.com"]) if config.email.empty?
13
+ end
14
+
15
+ def serve_web!
16
+ fork { exec "/usr/bin/env ruby #{File.dirname(__FILE__)}/../../sinatra/app.rb -p #{web_port} -e production" }
17
+ puts "* Started " + web_uri.foreground(:yellow)
18
+ end
19
+
20
+ def serve_git!
21
+ fork { exec "git daemon --base-path=#{repositories_path} --export-all" }
22
+ puts "* Started " + "#{git_uri}".foreground(:yellow)
23
+ end
24
+
25
+ def advertise!
26
+ fork { Bananajour::Bonjour::Advertiser.new.go! }
27
+ end
28
+
29
+ def add!(dir, name = nil)
30
+ dir = Fancypath(dir)
31
+
32
+ unless dir.join(".git").directory?
33
+ abort "Can't init project #{dir}, no .git directory found."
34
+ end
35
+
36
+ if name.nil?
37
+ default_name = dir.basename.to_s
38
+ print "Project Name?".foreground(:yellow) + " [#{default_name}] "
39
+ name = (STDIN.gets || "").strip
40
+ name = default_name if name.empty?
41
+ end
42
+
43
+ repo = Bananajour::Repository.for_name(name)
44
+
45
+ if repo.exists?
46
+ abort "You've already a project #{repo}."
47
+ end
48
+
49
+ repo.init!
50
+ Dir.chdir(dir) { `git remote add banana #{repo.path.expand_path}` }
51
+ puts init_success_message(repo.dirname)
52
+
53
+ repo
54
+ end
55
+
56
+ def init_success_message(repo_dirname)
57
+ plain_init_success_message(repo_dirname).gsub("git push banana master", "git push banana master".foreground(:yellow))
58
+ end
59
+
60
+ def plain_init_success_message(repo_dirname)
61
+ "Bananajour repository #{repo_dirname} initialised and remote banana added.\nNext: git push banana master"
62
+ end
63
+
64
+ def clone!(url, clone_name)
65
+ dir = clone_name || File.basename(url).chomp('.git')
66
+
67
+ if File.exists?(dir)
68
+ abort "Can't clone #{url} to #{dir}, the directory already exists."
69
+ end
70
+
71
+ `git clone #{url} #{dir}`
72
+ if $? != 0
73
+ abort "Failed to clone bananajour repository #{url} to #{dir}."
74
+ else
75
+ puts "Bananajour repository #{url} cloned to #{dir}."
76
+ add!(dir, dir)
77
+ end
78
+ end
79
+
80
+ end
@@ -0,0 +1,34 @@
1
+ module Bananajour
2
+ # DRYs version number dependencies and provides a simple way require them
3
+ module GemDependencies
4
+ DEPENDENCIES = [
5
+ %w( sinatra 0.9.2 ),
6
+ %w( json 1.1.7 ),
7
+ %w( chrislloyd-fancypath 0.5.8 ),
8
+ %w( rainbow 1.0.1 ),
9
+ %w( mojombo-grit 1.1.1 ),
10
+ %w( dnssd 0.7.1 ),
11
+ %w( rack 1.0.0 ),
12
+ %w( thin 1.0.0 ),
13
+ %w( haml 2.0.9 ),
14
+ %w( activesupport 2.3.2 )
15
+ ]
16
+ class Dependency < Struct.new(:name, :version)
17
+ def require_gem; gem name, version end
18
+ end
19
+ def self.all
20
+ DEPENDENCIES.map {|(name, version)| Dependency.new(name, version)}
21
+ end
22
+ def self.for_name(name)
23
+ all.find {|d| d.name == name }
24
+ end
25
+ end
26
+
27
+ def self.gem(name)
28
+ Bananajour::GemDependencies.for_name(name).require_gem
29
+ end
30
+ def self.require_gem(name, lib=nil)
31
+ self.gem(name)
32
+ Kernel.require(lib || name)
33
+ end
34
+ end
@@ -0,0 +1,11 @@
1
+ Grit::Commit.class_eval do
2
+ def ==(other)
3
+ self.id == other.id
4
+ end
5
+ end
6
+
7
+ Grit::Actor.class_eval do
8
+ def gravatar_uri
9
+ Bananajour.gravatar_uri(email)
10
+ end
11
+ end
@@ -0,0 +1,100 @@
1
+ require 'md5'
2
+
3
+ module Bananajour
4
+ module GravatarHelpers
5
+ def gravatar
6
+ gravatar_uri(self.config.email)
7
+ end
8
+ def gravatar_uri(email)
9
+ "http://gravatar.com/avatar/#{MD5.md5(email)}.png"
10
+ end
11
+ end
12
+
13
+ # Lifted from Rails
14
+ module DateHelpers
15
+ # Reports the approximate distance in time between two Time or Date objects or integers as seconds.
16
+ # Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs
17
+ # Distances are reported based on the following table:
18
+ #
19
+ # 0 <-> 29 secs # => less than a minute
20
+ # 30 secs <-> 1 min, 29 secs # => 1 minute
21
+ # 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
22
+ # 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
23
+ # 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
24
+ # 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
25
+ # 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
26
+ # 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
27
+ # 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
28
+ # 1 yr <-> 2 yrs minus 1 secs # => about 1 year
29
+ # 2 yrs <-> max time or date # => over [2..X] years
30
+ #
31
+ # With <tt>include_seconds</tt> = true and the difference < 1 minute 29 seconds:
32
+ # 0-4 secs # => less than 5 seconds
33
+ # 5-9 secs # => less than 10 seconds
34
+ # 10-19 secs # => less than 20 seconds
35
+ # 20-39 secs # => half a minute
36
+ # 40-59 secs # => less than a minute
37
+ # 60-89 secs # => 1 minute
38
+ #
39
+ # ==== Examples
40
+ # from_time = Time.now
41
+ # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
42
+ # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
43
+ # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
44
+ # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
45
+ # distance_of_time_in_words(from_time, 3.years.from_now) # => over 3 years
46
+ # distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days
47
+ # distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
48
+ # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
49
+ # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
50
+ # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
51
+ # distance_of_time_in_words(from_time, from_time + 4.years + 15.days + 30.minutes + 5.seconds) # => over 4 years
52
+ #
53
+ # to_time = Time.now + 6.years + 19.days
54
+ # distance_of_time_in_words(from_time, to_time, true) # => over 6 years
55
+ # distance_of_time_in_words(to_time, from_time, true) # => over 6 years
56
+ # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
57
+ #
58
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
59
+ from_time = from_time.to_time if from_time.respond_to?(:to_time)
60
+ to_time = to_time.to_time if to_time.respond_to?(:to_time)
61
+ distance_in_minutes = (((to_time - from_time).abs)/60).round
62
+ distance_in_seconds = ((to_time - from_time).abs).round
63
+
64
+ case distance_in_minutes
65
+ when 0..1
66
+ return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
67
+ case distance_in_seconds
68
+ when 0..4 then 'less than 5 seconds'
69
+ when 5..9 then 'less than 10 seconds'
70
+ when 10..19 then 'less than 20 seconds'
71
+ when 20..39 then 'half a minute'
72
+ when 40..59 then 'less than a minute'
73
+ else '1 minute'
74
+ end
75
+
76
+ when 2..44 then "#{distance_in_minutes} minutes"
77
+ when 45..89 then 'about 1 hour'
78
+ when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
79
+ when 1440..2879 then '1 day'
80
+ when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
81
+ when 43200..86399 then 'about 1 month'
82
+ when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
83
+ when 525600..1051199 then 'about 1 year'
84
+ else "over #{(distance_in_minutes / 525600).round} years"
85
+ end
86
+ end
87
+
88
+ # Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
89
+ #
90
+ # ==== Examples
91
+ # time_ago_in_words(3.minutes.from_now) # => 3 minutes
92
+ # time_ago_in_words(Time.now - 15.hours) # => 15 hours
93
+ # time_ago_in_words(Time.now) # => less than a minute
94
+ #
95
+ # from_time = Time.now - 3.days - 14.minutes - 25.seconds # => 3 days
96
+ def time_ago_in_words(from_time, include_seconds = false)
97
+ distance_of_time_in_words(from_time, Time.now, include_seconds)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,85 @@
1
+ Bananajour.require_gem 'mojombo-grit', 'grit'
2
+
3
+ module Bananajour
4
+ class Repository
5
+ def self.for_name(name)
6
+ new(Bananajour.repositories_path.join(name + ".git"))
7
+ end
8
+ def self.html_id(name)
9
+ name.gsub(/[^A-Za-z-]+/, '').downcase
10
+ end
11
+ def initialize(path)
12
+ @path = Fancypath(path)
13
+ end
14
+ def ==(other)
15
+ other.respond_to?(:path) && self.path == other.path
16
+ end
17
+ attr_reader :path
18
+ def exists?
19
+ path.exists?
20
+ end
21
+ def init!
22
+ path.create_dir
23
+ Dir.chdir(path) { `git init --bare` }
24
+ end
25
+ def name
26
+ dirname.sub(".git",'')
27
+ end
28
+ def html_id
29
+ self.class.html_id(name)
30
+ end
31
+ def dirname
32
+ path.split.last.to_s
33
+ end
34
+ def to_s
35
+ name
36
+ end
37
+ def uri
38
+ Bananajour.git_uri + dirname
39
+ end
40
+ def web_uri
41
+ Bananajour.web_uri + "#" + html_id
42
+ end
43
+ def grit_repo
44
+ @grit_repo ||= Grit::Repo.new(path)
45
+ end
46
+ def recent_commits
47
+ @commits ||= grit_repo.commits(nil, 10)
48
+ end
49
+ def readme_file
50
+ grit_repo.tree.contents.find {|c| c.name =~ /readme/i}
51
+ end
52
+ def rendered_readme
53
+ case File.extname(readme_file.name)
54
+ when /\.md/i, /\.markdown/i
55
+ require 'rdiscount'
56
+ RDiscount.new(readme_file.data).to_html
57
+ when /\.textile/i
58
+ require 'redcloth'
59
+ RedCloth.new(readme_file.data).to_html(:textile)
60
+ end
61
+ rescue LoadError
62
+ ""
63
+ end
64
+ def remove!
65
+ path.rmtree
66
+ end
67
+ def to_hash
68
+ heads = grit_repo.heads
69
+ {
70
+ "name" => name,
71
+ "html_friendly_name" => html_id, # TODO: Deprecate in v3. Renamed to html_id since 2.1.4
72
+ "html_id" => html_id,
73
+ "uri" => uri,
74
+ "heads" => heads.map {|h| h.name},
75
+ "recent_commits" => recent_commits.collect do |c|
76
+ c.to_hash.merge(
77
+ "head" => (head = heads.find {|h| h.commit == c}) && head.name,
78
+ "gravatar" => c.author.gravatar_uri
79
+ )
80
+ end,
81
+ "bananajour" => Bananajour.to_hash
82
+ }
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,3 @@
1
+ module Bananajour
2
+ VERSION = '2.1.9'
3
+ end
data/sinatra/app.rb ADDED
@@ -0,0 +1,87 @@
1
+ Thread.abort_on_exception = true
2
+
3
+ __DIR__ = File.dirname(__FILE__)
4
+
5
+ require 'rubygems'
6
+
7
+ require "#{__DIR__}/../lib/bananajour"
8
+
9
+ Bananajour.require_gem 'rack'
10
+
11
+ # Must require 'sinatra' from this file for Sinatra's magic to pick up lots of free stuff
12
+ Bananajour::GemDependencies.for_name('sinatra').require_gem
13
+ require 'sinatra'
14
+
15
+ Bananajour.require_gem 'haml'
16
+ Bananajour.require_gem 'json'
17
+
18
+ Bananajour.gem 'activesupport'
19
+ require 'active_support/core_ext/enumerable'
20
+ require 'active_support/core_ext/array'
21
+
22
+ require 'forwardable' # Fix for issue #8 - Thin borking on uninitialized constant Forwardable
23
+
24
+ set :server, 'thin' # Things go weird with anything else - let's lock it down to thin
25
+ set :haml, {:format => :html5, :attr_wrapper => '"'}
26
+ set :logging, false
27
+
28
+ require "#{__DIR__}/lib/browsers" # to prevent reloading
29
+ require "#{__DIR__}/lib/mock_browsers" if Sinatra::Application.development?
30
+ before do
31
+ @bananajour_browser = BANANAJOUR_BROWSER
32
+ @repository_browser = REPO_BROWSER
33
+ end
34
+
35
+ load "#{__DIR__}/lib/diff_helpers.rb"
36
+ helpers DiffHelpers
37
+
38
+ require "bananajour/helpers"
39
+ helpers Bananajour::GravatarHelpers, Bananajour::DateHelpers
40
+
41
+ helpers do
42
+ def json(body)
43
+ content_type "application/json"
44
+ params[:callback] ? "#{params[:callback]}(#{body});" : body
45
+ end
46
+ def local?
47
+ [
48
+ "0.0.0.0",
49
+ "127.0.0.1",
50
+ Socket.getaddrinfo(request.env["SERVER_NAME"], nil).map {|a| a[3]}
51
+ ].flatten.include? request.env["REMOTE_ADDR"]
52
+ end
53
+ def pluralize(number, singular, plural)
54
+ "#{number} #{number == 1 ? singular : plural}"
55
+ end
56
+ end
57
+
58
+ get "/" do
59
+ @my_repositories = Bananajour.repositories
60
+ @other_repos_by_name = @repository_browser.other_repositories.group_by {|r| r.name}
61
+ @people = @bananajour_browser.other_bananajours
62
+ haml :home
63
+ end
64
+
65
+ get "/:repository/readme" do
66
+ @repository = Bananajour::Repository.for_name(params[:repository])
67
+ readme_file = @repository.readme_file
68
+ @rendered_readme = @repository.rendered_readme
69
+ @plain_readme = readme_file.data
70
+ haml :readme
71
+ end
72
+
73
+ get "/:repository/:commit" do
74
+ @repository = Bananajour::Repository.for_name(params[:repository])
75
+ @commit = @repository.grit_repo.commit(params[:commit])
76
+ haml :commit
77
+ end
78
+
79
+ get "/index.json" do
80
+ json Bananajour.to_hash.to_json
81
+ end
82
+
83
+ get "/:repository.json" do
84
+ response = Bananajour::Repository.for_name(params[:repository]).to_hash
85
+ response["recent_commits"].map! { |c| c["committed_date_pretty"] = time_ago_in_words(Time.parse(c["committed_date"])).gsub("about ","") + " ago"; c }
86
+ json response.to_json
87
+ end