toolmantim-bananajour 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +66 -0
- data/Readme.md +66 -0
- data/bin/bananajour +57 -0
- data/lib/bananajour/bonjour/advertiser.rb +55 -0
- data/lib/bananajour/bonjour/bananajour_browser.rb +24 -0
- data/lib/bananajour/bonjour/browser.rb +52 -0
- data/lib/bananajour/bonjour/person.rb +21 -0
- data/lib/bananajour/bonjour/repository.rb +39 -0
- data/lib/bananajour/bonjour/repository_browser.rb +36 -0
- data/lib/bananajour/bonjour.rb +9 -0
- data/lib/bananajour/commands.rb +96 -0
- data/lib/bananajour/gem_dependencies.rb +34 -0
- data/lib/bananajour/grit_extensions.rb +13 -0
- data/lib/bananajour/helpers.rb +98 -0
- data/lib/bananajour/repository.rb +84 -0
- data/lib/bananajour/version.rb +3 -0
- data/lib/bananajour.rb +90 -0
- data/sinatra/app.rb +87 -0
- data/sinatra/lib/browsers.rb +2 -0
- data/sinatra/lib/diff_helpers.rb +80 -0
- data/sinatra/lib/mock_browsers.rb +55 -0
- data/sinatra/public/jquery-1.3.2.min.js +19 -0
- data/sinatra/public/loader.gif +0 -0
- data/sinatra/public/logo.png +0 -0
- data/sinatra/public/pbjt.swf +0 -0
- data/sinatra/public/peanut.png +0 -0
- data/sinatra/views/commit.haml +112 -0
- data/sinatra/views/home.haml +308 -0
- data/sinatra/views/layout.haml +119 -0
- data/sinatra/views/readme.haml +52 -0
- metadata +186 -0
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
|
3
|
+
$:.unshift "#{File.dirname(__FILE__)}/lib"
|
4
|
+
|
5
|
+
require "bananajour/version"
|
6
|
+
require "bananajour/gem_dependencies"
|
7
|
+
|
8
|
+
gem = Gem::Specification.new do |gem|
|
9
|
+
gem.name = "bananajour"
|
10
|
+
gem.version = Bananajour::VERSION
|
11
|
+
gem.platform = Gem::Platform::RUBY
|
12
|
+
gem.extra_rdoc_files = ["Readme.md"]
|
13
|
+
gem.summary = "Local git repository hosting with a sexy web interface and bonjour discovery. It's like your own little adhoc, network-aware github!"
|
14
|
+
gem.description = gem.summary
|
15
|
+
gem.authors = ["Tim Lucas"]
|
16
|
+
gem.email = "t.lucas@toolmantim.com"
|
17
|
+
gem.homepage = "http://github.com/toolmantim/bananajour"
|
18
|
+
gem.require_path = "lib"
|
19
|
+
gem.files = %w(Readme.md Rakefile) + Dir.glob("{bin,lib,sinatra}/**/*")
|
20
|
+
gem.has_rdoc = false
|
21
|
+
gem.bindir = 'bin'
|
22
|
+
gem.executables = [ 'bananajour' ]
|
23
|
+
Bananajour::GemDependencies.all.each {|dep| gem.add_runtime_dependency( dep.name, dep.version ) }
|
24
|
+
end
|
25
|
+
|
26
|
+
task :clean do
|
27
|
+
FileUtils.rm_rf Dir['*.gem', '*.gemspec']
|
28
|
+
end
|
29
|
+
|
30
|
+
namespace :gem do
|
31
|
+
|
32
|
+
desc "Rebuild and install bananajour as a gem"
|
33
|
+
task :install => :package do
|
34
|
+
require 'rubygems/installer'
|
35
|
+
Dir['*.gem'].each do |gem|
|
36
|
+
Gem::Installer.new(gem).install
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Create the gem"
|
41
|
+
task :package => [:clean, "spec:generate"] do
|
42
|
+
require 'rubygems/builder'
|
43
|
+
Gem::Builder.new( gem ).build
|
44
|
+
end
|
45
|
+
|
46
|
+
namespace :spec do
|
47
|
+
|
48
|
+
desc "Update #{gem.name}.gemspec"
|
49
|
+
task :generate do
|
50
|
+
File.open("#{gem.name}.gemspec", "w") do |f|
|
51
|
+
f.puts(gem.to_ruby)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Test spec in github cleanroom"
|
56
|
+
task :test => :generate do
|
57
|
+
require 'rubygems/specification'
|
58
|
+
data = File.read("#{gem.name}.gemspec")
|
59
|
+
spec = nil
|
60
|
+
Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
|
61
|
+
puts "#{spec} - Good to go!"
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/Readme.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
Bananajour - Local git publication and collaboration
|
2
|
+
====================================================
|
3
|
+
|
4
|
+
Local git repository hosting with a sexy web interface and Bonjour discovery. It's like a bunch of adhoc, local, network-aware githubs!
|
5
|
+
|
6
|
+
Unlike Gitjour, the repositories you're serving are not your working git repositories, they're served from `~/.bananajour/repositories`. You can push to your bananajour repositories from your working copies just like you do with github.
|
7
|
+
|
8
|
+
Bananajour is the baby of [Tim Lucas](http://toolmantim.com/) with much railscamp hackage by [Nathan de Vries](http://github.com/atnan), [Lachlan Hardy](http://github.com/lachlanhardy), [Josh Bassett](http://github.com/nullobject), [Myles Byrne](http://github.com/quackingduck), [Ben Hoskings](http://github.com/benhoskings), [Brett Goulder](http://github.com/brettgo1), [Tony Issakov](https://github.com/tissak), and [Mark Bennett](http://github.com/MarkBennett). The rad logo was by [Carla Hackett](http://carlahackettdesign.com/).
|
9
|
+
|
10
|
+
Installation and usage
|
11
|
+
----------------------
|
12
|
+
|
13
|
+
You'll need at least [git version 1.6](http://git-scm.com/). Run `git --version` if you're unsure.
|
14
|
+
|
15
|
+
Install it from github via gems:
|
16
|
+
|
17
|
+
gem install toolmantim-bananajour
|
18
|
+
|
19
|
+
(you might need to do a `gem sources -a http://gems.github.com` beforehand!)
|
20
|
+
|
21
|
+
Start it up:
|
22
|
+
|
23
|
+
bananajour
|
24
|
+
|
25
|
+
Initialize a new Bananajour repository:
|
26
|
+
|
27
|
+
cd ~/code/myproj
|
28
|
+
bananajour init
|
29
|
+
|
30
|
+
Publish your codez:
|
31
|
+
|
32
|
+
git push banana master
|
33
|
+
|
34
|
+
Fire up [http://localhost:9331/](http://localhost:9331/) to check it out.
|
35
|
+
|
36
|
+
If somebody starts sharing a Bananajour repository with the same name on the
|
37
|
+
network, it'll automatically show up in the network thanks to the wonder that is Bonjour.
|
38
|
+
|
39
|
+
Official repository and support
|
40
|
+
-------------------------------
|
41
|
+
|
42
|
+
[http://github.com/toolmantim/bananajour](http://github.com/toolmantim/bananajour) is where Bananajour lives along with all of its support issues.
|
43
|
+
|
44
|
+
Developing
|
45
|
+
----------
|
46
|
+
|
47
|
+
If you want to hack on the sinatra app then do so from a local clone but run your actual bananjour from the gem version. Running the sinatra app directly won't broadcast it onto the network and it'll run on a different port:
|
48
|
+
|
49
|
+
ruby sinatra/app.rb -s thin
|
50
|
+
|
51
|
+
If you want code reloading use [shotgun](http://github.com/rtomayko/shotgun) instead:
|
52
|
+
|
53
|
+
shotgun sinatra/app.rb -s thin
|
54
|
+
|
55
|
+
If you then want to run your working copy as your public bananajour rebuild and install it as a gem:
|
56
|
+
|
57
|
+
sudo rake gem:install
|
58
|
+
|
59
|
+
License
|
60
|
+
-------
|
61
|
+
|
62
|
+
All directories and files are MIT Licensed.
|
63
|
+
|
64
|
+
Warning to all those who still believe secrecy will save their revenue stream
|
65
|
+
-----------------------------------------------------------------------------
|
66
|
+
Bananas were meant to be shared. There are no secret bananas.
|
data/bin/bananajour
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "#{File.dirname(__FILE__)}/../lib/bananajour"
|
5
|
+
|
6
|
+
Thread.abort_on_exception = true
|
7
|
+
|
8
|
+
Bananajour.check_git!
|
9
|
+
Bananajour.check_git_config!
|
10
|
+
|
11
|
+
case ARGV.first
|
12
|
+
|
13
|
+
when nil
|
14
|
+
Bananajour.serve_web!
|
15
|
+
Bananajour.serve_git!
|
16
|
+
Bananajour.advertise!
|
17
|
+
Process.wait
|
18
|
+
|
19
|
+
when "init", "add"
|
20
|
+
repo = Bananajour.init!(ARGV[1] || File.expand_path("."))
|
21
|
+
|
22
|
+
when "remove", "rm"
|
23
|
+
name = ARGV[1]
|
24
|
+
|
25
|
+
if !name || name.empty?
|
26
|
+
abort "You need to specify the name of the repository you'd like to remove:\n#{File.basename($0)} remove <path>"
|
27
|
+
elsif !(repo = Bananajour::Repository.for_name(name))
|
28
|
+
abort "#{name.inspect} is not a valid repository name"
|
29
|
+
end
|
30
|
+
|
31
|
+
repo.remove!
|
32
|
+
|
33
|
+
when "clone"
|
34
|
+
if ARGV[1].nil? || ARGV[1].empty?
|
35
|
+
abort "You need to specify the path to the repository you'd like to clone:\n$ bananajour clone <path>"
|
36
|
+
end
|
37
|
+
repo = Bananajour.clone!(ARGV[1], ARGV[2])
|
38
|
+
|
39
|
+
when "help", "--help", "-h"
|
40
|
+
puts <<-HELP
|
41
|
+
Usage: #{File.basename($0)} [<command>]
|
42
|
+
|
43
|
+
Commands:
|
44
|
+
none - Start the web, git and bonjour serving
|
45
|
+
init [path] - Init a new repo
|
46
|
+
remove <name> - Remove a repo
|
47
|
+
clone <url> [path] - Clone a remote repo and init it as a new bananajour repo
|
48
|
+
help
|
49
|
+
version
|
50
|
+
HELP
|
51
|
+
|
52
|
+
when "version", "--version", "-v"
|
53
|
+
puts "bananajour version #{Bananajour::VERSION}"
|
54
|
+
|
55
|
+
else
|
56
|
+
abort "Say what? Try: #{File.basename($0)} help"
|
57
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Bananajour::Bonjour::Advertiser
|
2
|
+
def initialize
|
3
|
+
@services = []
|
4
|
+
end
|
5
|
+
def go!
|
6
|
+
register_app
|
7
|
+
register_repos
|
8
|
+
end
|
9
|
+
private
|
10
|
+
def register_app
|
11
|
+
STDERR.puts "Registering #{Bananajour.web_uri}"
|
12
|
+
tr = DNSSD::TextRecord.new
|
13
|
+
tr["name"] = Bananajour.config.name
|
14
|
+
tr["email"] = Bananajour.config.email
|
15
|
+
tr["uri"] = Bananajour.web_uri
|
16
|
+
tr["gravatar"] = Bananajour.gravatar
|
17
|
+
DNSSD.register("#{Bananajour.config.name}'s bananajour", "_http._tcp,_bananajour", nil, Bananajour.web_port, tr) {}
|
18
|
+
end
|
19
|
+
def register_repos
|
20
|
+
loop do
|
21
|
+
stop_old_services
|
22
|
+
register_new_repositories
|
23
|
+
sleep(1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
def stop_old_services
|
27
|
+
old_services.each do |old_service|
|
28
|
+
STDERR.puts "Unregistering #{old_service.repository.uri}"
|
29
|
+
old_service.stop
|
30
|
+
@services.delete(old_service)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
def old_services
|
34
|
+
@services.reject {|s| Bananajour.repositories.include?(s.repository)}
|
35
|
+
end
|
36
|
+
def register_new_repositories
|
37
|
+
new_repositories.each do |new_repo|
|
38
|
+
STDERR.puts "Registering #{new_repo.uri}"
|
39
|
+
tr = DNSSD::TextRecord.new
|
40
|
+
tr["name"] = new_repo.name
|
41
|
+
tr["uri"] = new_repo.uri
|
42
|
+
tr["bjour-name"] = Bananajour.config.name
|
43
|
+
tr["bjour-email"] = Bananajour.config.email
|
44
|
+
tr["bjour-uri"] = Bananajour.web_uri
|
45
|
+
tr["bjour-gravatar"] = Bananajour.gravatar
|
46
|
+
service = DNSSD.register(new_repo.name, "_git._tcp,_bananajour", nil, 9418, tr) {}
|
47
|
+
service.class.instance_eval { attr_accessor(:repository) }
|
48
|
+
service.repository = new_repo
|
49
|
+
@services << service
|
50
|
+
end
|
51
|
+
end
|
52
|
+
def new_repositories
|
53
|
+
Bananajour.repositories.select {|repo| !@services.any? {|s| s.repository == repo } }
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Bananajour::Bonjour
|
2
|
+
class BananajourBrowser
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@browser = Browser.new('_bananajour._http._tcp')
|
6
|
+
end
|
7
|
+
|
8
|
+
def bananajours
|
9
|
+
@browser.replies.map do |reply|
|
10
|
+
Person.new(
|
11
|
+
reply.text_record["name"],
|
12
|
+
reply.text_record["email"],
|
13
|
+
reply.text_record["uri"],
|
14
|
+
reply.text_record["gravatar"]
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def other_bananajours
|
20
|
+
bananajours.reject {|b| b.uri == Bananajour.web_uri}
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
Bananajour.require_gem 'dnssd'
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
Thread.abort_on_exception = true
|
7
|
+
|
8
|
+
# Generic bonjour browser
|
9
|
+
#
|
10
|
+
# Example use:
|
11
|
+
#
|
12
|
+
# browser = BonjourBrowser.new("_bananajour._git._tcp")
|
13
|
+
# loop do
|
14
|
+
# sleep(1)
|
15
|
+
# pp browser.replies.map {|r| r.name}
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Probably gem-worthy
|
19
|
+
class Bananajour::Bonjour::Browser
|
20
|
+
attr_reader :replies
|
21
|
+
def initialize(service)
|
22
|
+
@service = service
|
23
|
+
@mutex = Mutex.new
|
24
|
+
@replies = []
|
25
|
+
watch!
|
26
|
+
end
|
27
|
+
private
|
28
|
+
def watch!
|
29
|
+
DNSSD.browse(@service) do |br|
|
30
|
+
begin
|
31
|
+
Timeout.timeout(5) do
|
32
|
+
DNSSD.resolve(br.name, br.type, br.domain) do |rr|
|
33
|
+
begin
|
34
|
+
@mutex.synchronize do
|
35
|
+
rr_exists = Proc.new {|existing_rr| existing_rr.target == rr.target && existing_rr.fullname == rr.fullname}
|
36
|
+
if (DNSSD::Flags::Add & br.flags.to_i) != 0
|
37
|
+
@replies << rr unless @replies.any?(&rr_exists)
|
38
|
+
else
|
39
|
+
@replies.delete_if(&rr_exists)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
ensure
|
43
|
+
rr.service.stop
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
rescue Timeout::Error
|
48
|
+
# Do nothing
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -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_friendly_name
|
10
|
+
Bananajour::Repository.html_friendly_name(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_friendly_name}"
|
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,9 @@
|
|
1
|
+
module Bananajour::Bonjour
|
2
|
+
end
|
3
|
+
|
4
|
+
require 'bananajour/bonjour/person'
|
5
|
+
require 'bananajour/bonjour/repository'
|
6
|
+
require 'bananajour/bonjour/browser'
|
7
|
+
require 'bananajour/bonjour/repository_browser'
|
8
|
+
require 'bananajour/bonjour/bananajour_browser'
|
9
|
+
require 'bananajour/bonjour/advertiser'
|
@@ -0,0 +1,96 @@
|
|
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
|
+
if repositories.empty?
|
17
|
+
STDERR.puts "Warning: you don't have any bananajour repositories. Use: bananajour init"
|
18
|
+
end
|
19
|
+
fork { exec "/usr/bin/env ruby #{File.dirname(__FILE__)}/../../sinatra/app.rb -p #{web_port} -e production" }
|
20
|
+
puts "* Started " + web_uri.foreground(:yellow)
|
21
|
+
end
|
22
|
+
|
23
|
+
def serve_git!
|
24
|
+
fork { exec "git daemon --base-path=#{repositories_path} --export-all" }
|
25
|
+
puts "* Started " + "#{git_uri}".foreground(:yellow)
|
26
|
+
end
|
27
|
+
|
28
|
+
def serve_git!
|
29
|
+
fork { exec "git daemon --base-path=#{repositories_path} --export-all" }
|
30
|
+
puts "* Started " + "#{git_uri}".foreground(:yellow)
|
31
|
+
end
|
32
|
+
|
33
|
+
def advertise!
|
34
|
+
fork { Bananajour::Bonjour::Advertiser.new.go! }
|
35
|
+
end
|
36
|
+
|
37
|
+
def init!(dir, name = nil)
|
38
|
+
dir = Fancypath(dir)
|
39
|
+
|
40
|
+
unless dir.join(".git").directory?
|
41
|
+
abort "Can't init project #{dir}, no .git directory found."
|
42
|
+
end
|
43
|
+
|
44
|
+
if name.nil?
|
45
|
+
default_name = dir.basename.to_s
|
46
|
+
print "Project Name?".foreground(:yellow) + " [#{default_name}] "
|
47
|
+
name = (STDIN.gets || "").strip
|
48
|
+
name = default_name if name.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
repo = Bananajour::Repository.for_name(name)
|
52
|
+
|
53
|
+
if repo.exists?
|
54
|
+
abort "You've already a project #{repo}."
|
55
|
+
end
|
56
|
+
|
57
|
+
repo.init!
|
58
|
+
Dir.chdir(dir) { `git remote add banana #{repo.path.expand_path}` }
|
59
|
+
puts init_success_message(repo.dirname)
|
60
|
+
|
61
|
+
repo
|
62
|
+
end
|
63
|
+
|
64
|
+
def init_success_message(repo_dirname)
|
65
|
+
plain_init_success_message(repo_dirname).gsub("git push banana master", "git push banana master".foreground(:yellow))
|
66
|
+
end
|
67
|
+
|
68
|
+
def plain_init_success_message(repo_dirname)
|
69
|
+
"Bananajour repository #{repo_dirname} initialised and remote banana added.\nNext: git push banana master"
|
70
|
+
end
|
71
|
+
|
72
|
+
def clone!(url, clone_name)
|
73
|
+
dir = clone_name || File.basename(url).chomp('.git')
|
74
|
+
|
75
|
+
if File.exists?(dir)
|
76
|
+
abort "Can't clone #{url} to #{dir}, the directory already exists."
|
77
|
+
end
|
78
|
+
|
79
|
+
`git clone #{url} #{dir}`
|
80
|
+
if $? != 0
|
81
|
+
abort clone_failure_message(url, repo.dirname)
|
82
|
+
else
|
83
|
+
puts clone_success_message(url, dir)
|
84
|
+
init!(dir, dir)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def clone_success_message(source_repo_url, repo_dirname)
|
89
|
+
"Bananajour repository #{source_repo_url} cloned to #{repo_dirname}."
|
90
|
+
end
|
91
|
+
|
92
|
+
def clone_failure_message(source_repo_url, repo_dirname)
|
93
|
+
"Failed to clone Bananajour repository #{source_repo_url} to #{repo_dirname}."
|
94
|
+
end
|
95
|
+
|
96
|
+
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.2 ),
|
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,98 @@
|
|
1
|
+
module Bananajour
|
2
|
+
module GravatarHelpers
|
3
|
+
def gravatar
|
4
|
+
gravatar_uri(self.config.email)
|
5
|
+
end
|
6
|
+
def gravatar_uri(email)
|
7
|
+
"http://gravatar.com/avatar/#{MD5.md5(email)}.png"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Lifted from Rails
|
12
|
+
module DateHelpers
|
13
|
+
# Reports the approximate distance in time between two Time or Date objects or integers as seconds.
|
14
|
+
# Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs
|
15
|
+
# Distances are reported based on the following table:
|
16
|
+
#
|
17
|
+
# 0 <-> 29 secs # => less than a minute
|
18
|
+
# 30 secs <-> 1 min, 29 secs # => 1 minute
|
19
|
+
# 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
|
20
|
+
# 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
|
21
|
+
# 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
|
22
|
+
# 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
|
23
|
+
# 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
|
24
|
+
# 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
|
25
|
+
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
|
26
|
+
# 1 yr <-> 2 yrs minus 1 secs # => about 1 year
|
27
|
+
# 2 yrs <-> max time or date # => over [2..X] years
|
28
|
+
#
|
29
|
+
# With <tt>include_seconds</tt> = true and the difference < 1 minute 29 seconds:
|
30
|
+
# 0-4 secs # => less than 5 seconds
|
31
|
+
# 5-9 secs # => less than 10 seconds
|
32
|
+
# 10-19 secs # => less than 20 seconds
|
33
|
+
# 20-39 secs # => half a minute
|
34
|
+
# 40-59 secs # => less than a minute
|
35
|
+
# 60-89 secs # => 1 minute
|
36
|
+
#
|
37
|
+
# ==== Examples
|
38
|
+
# from_time = Time.now
|
39
|
+
# distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
|
40
|
+
# distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
|
41
|
+
# distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
|
42
|
+
# distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
|
43
|
+
# distance_of_time_in_words(from_time, 3.years.from_now) # => over 3 years
|
44
|
+
# distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days
|
45
|
+
# distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
|
46
|
+
# distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
|
47
|
+
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
|
48
|
+
# distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
|
49
|
+
# distance_of_time_in_words(from_time, from_time + 4.years + 15.days + 30.minutes + 5.seconds) # => over 4 years
|
50
|
+
#
|
51
|
+
# to_time = Time.now + 6.years + 19.days
|
52
|
+
# distance_of_time_in_words(from_time, to_time, true) # => over 6 years
|
53
|
+
# distance_of_time_in_words(to_time, from_time, true) # => over 6 years
|
54
|
+
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
|
55
|
+
#
|
56
|
+
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
|
57
|
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
|
58
|
+
to_time = to_time.to_time if to_time.respond_to?(:to_time)
|
59
|
+
distance_in_minutes = (((to_time - from_time).abs)/60).round
|
60
|
+
distance_in_seconds = ((to_time - from_time).abs).round
|
61
|
+
|
62
|
+
case distance_in_minutes
|
63
|
+
when 0..1
|
64
|
+
return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
|
65
|
+
case distance_in_seconds
|
66
|
+
when 0..4 then 'less than 5 seconds'
|
67
|
+
when 5..9 then 'less than 10 seconds'
|
68
|
+
when 10..19 then 'less than 20 seconds'
|
69
|
+
when 20..39 then 'half a minute'
|
70
|
+
when 40..59 then 'less than a minute'
|
71
|
+
else '1 minute'
|
72
|
+
end
|
73
|
+
|
74
|
+
when 2..44 then "#{distance_in_minutes} minutes"
|
75
|
+
when 45..89 then 'about 1 hour'
|
76
|
+
when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
|
77
|
+
when 1440..2879 then '1 day'
|
78
|
+
when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
|
79
|
+
when 43200..86399 then 'about 1 month'
|
80
|
+
when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
|
81
|
+
when 525600..1051199 then 'about 1 year'
|
82
|
+
else "over #{(distance_in_minutes / 525600).round} years"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
|
87
|
+
#
|
88
|
+
# ==== Examples
|
89
|
+
# time_ago_in_words(3.minutes.from_now) # => 3 minutes
|
90
|
+
# time_ago_in_words(Time.now - 15.hours) # => 15 hours
|
91
|
+
# time_ago_in_words(Time.now) # => less than a minute
|
92
|
+
#
|
93
|
+
# from_time = Time.now - 3.days - 14.minutes - 25.seconds # => 3 days
|
94
|
+
def time_ago_in_words(from_time, include_seconds = false)
|
95
|
+
distance_of_time_in_words(from_time, Time.now, include_seconds)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|