toolmantim-bananajour 2.1.1
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.
- 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
|