bananajour 2.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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