railsquest 0.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.
@@ -0,0 +1,66 @@
1
+ require 'dnssd'
2
+
3
+ class Railsquest::Bonjour::Advertiser
4
+ def initialize
5
+ @services = []
6
+ end
7
+
8
+ def go!
9
+ register_app
10
+ register_quests
11
+ end
12
+
13
+ private
14
+ def register_app
15
+ STDOUT.puts "Registering #{Railsquest.web_uri}"
16
+ tr = DNSSD::TextRecord.new
17
+ tr["name"] = Railsquest.config.name
18
+ tr["email"] = Railsquest.config.email
19
+ tr["uri"] = Railsquest.web_uri
20
+ tr["gravatar"] = Railsquest.gravatar
21
+ tr["version"] = Railsquest::VERSION
22
+ DNSSD.register("#{Railsquest.config.name}'s railsquest", "_http._tcp,_railsquest", nil, Railsquest.web_port, tr)
23
+ end
24
+
25
+ def register_quests
26
+ loop do
27
+ stop_old_services
28
+ register_new_quests
29
+ sleep(1)
30
+ end
31
+ end
32
+
33
+ def stop_old_services
34
+ old_services.each do |old_service|
35
+ STDOUT.puts "Unregistering #{old_service.quest.uri}"
36
+ old_service.stop
37
+ @services.delete(old_service)
38
+ end
39
+ end
40
+
41
+ def old_services
42
+ @services.reject {|s| Railsquest.quests.include?(s.quest)}
43
+ end
44
+
45
+ def register_new_quests
46
+ new_quests.each do |new_quest|
47
+ STDOUT.puts "Registering #{new_quest.uri}"
48
+ tr = DNSSD::TextRecord.new
49
+ tr["name"] = new_quest.name
50
+ tr["uri"] = new_quest.uri
51
+ tr["bjour-name"] = Railsquest.config.name
52
+ tr["bjour-email"] = Railsquest.config.email
53
+ tr["bjour-uri"] = Railsquest.web_uri
54
+ tr["bjour-gravatar"] = Railsquest.gravatar
55
+ tr["bjour-version"] = Railsquest::VERSION
56
+ service = DNSSD.register(new_quest.name, "_git._tcp,_railsquest", nil, 9877, tr)
57
+ service.class.instance_eval { attr_accessor(:quest) }
58
+ service.quest = new_quest
59
+ @services << service
60
+ end
61
+ end
62
+
63
+ def new_quests
64
+ Railsquest.quests.select {|quest| !@services.any? {|s| s.quest == quest } }
65
+ end
66
+ end
@@ -0,0 +1,58 @@
1
+ require '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("_git._tcp,_railsquest")
13
+ # loop do
14
+ # sleep(1)
15
+ # pp browser.replies.map {|r| r.name}
16
+ # end
17
+ #
18
+ # Probably gem-worthy
19
+ class Railsquest::Bonjour::Browser
20
+ def initialize(service)
21
+ @service = service
22
+ @mutex = Mutex.new
23
+ @replies = {}
24
+ watch!
25
+ end
26
+
27
+ def replies
28
+ @mutex.synchronize { @replies.values }
29
+ end
30
+
31
+ private
32
+ def watch!
33
+ Thread.new(@service, @mutex, @replies) do |service, mutex, replies|
34
+ begin
35
+ DNSSD.browse!(service) do |reply|
36
+ Thread.new(reply, replies, mutex) do |reply, replies, mutex|
37
+ begin
38
+ DNSSD.resolve!(reply.name, reply.type, reply.domain) do |resolve_reply|
39
+ mutex.synchronize do
40
+ if reply.flags.add?
41
+ replies[reply.fullname] = resolve_reply
42
+ else
43
+ replies.delete(reply.fullname)
44
+ end
45
+ end
46
+ resolve_reply.service.stop unless resolve_reply.service.stopped?
47
+ end
48
+ rescue DNSSD::BadParamError
49
+ # Ignore em
50
+ end
51
+ end
52
+ end
53
+ rescue DNSSD::BadParamError
54
+ # Ignore em
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ class Railsquest::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
+ def host_name
22
+ uri.gsub(/http:\/\//, '').gsub(/:9876/, '')
23
+ end
24
+
25
+ end
@@ -0,0 +1,39 @@
1
+ class Railsquest::Bonjour::Quest
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
+ Railsquest::Quest.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 Railsquest::Bonjour
2
+ class QuestBrowser
3
+
4
+ def initialize
5
+ @browser = Browser.new('_git._tcp,_railsquest')
6
+ end
7
+
8
+ def quests
9
+ @browser.replies.map do |reply|
10
+ Quest.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_quests
24
+ quests.reject {|r| Railsquest.quests.any? {|my_quest| my_quest.name == r.name}}
25
+ end
26
+
27
+ def quests_similar_to(quest)
28
+ quests.select {|r| r.name == quest.name}
29
+ end
30
+
31
+ def quests_for(person)
32
+ quests.select {|r| r.person == person}
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ module Railsquest::Bonjour
2
+ class RailsquestBrowser
3
+
4
+ def initialize
5
+ @browser = Browser.new('_http._tcp,_railsquest')
6
+ end
7
+
8
+ def railsquests
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_railsquests
20
+ railsquests.reject {|b| b.uri == Railsquest.web_uri}
21
+ end
22
+
23
+ def all_railsquests
24
+ railsquests
25
+ end
26
+
27
+ def find_person(hostname)
28
+ railsquests.find { |u| u.uri =~ /hostname/}
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,53 @@
1
+ require 'rainbow'
2
+ require 'pathname'
3
+
4
+ module Railsquest::Commands
5
+
6
+ # Start sinatra app.
7
+ def serve_web!
8
+ puts "* Starting " + web_uri.foreground(:yellow)
9
+ fork do
10
+ ENV["RACK_ENV"] ||= "production"
11
+ require "railsquest/../../sinatra/app"
12
+ Sinatra::Application.set :port, web_port
13
+ Sinatra::Application.set :server, "thin"
14
+ Sinatra::Application.run!
15
+ end
16
+ end
17
+
18
+ def advertise!
19
+ fork { Railsquest::Bonjour::Advertiser.new.go! }
20
+ end
21
+
22
+ def add!(port, name = nil)
23
+
24
+ if name.nil?
25
+ default_name = "My Awesome Quest"
26
+ print "Quest Name?".foreground(:yellow) + " [#{default_name}] "
27
+ name = (STDIN.gets || "").strip
28
+ name = default_name if name.empty?
29
+ end
30
+
31
+ quest = Railsquest::Quest.for_name(name)
32
+ quest.port = port
33
+
34
+ if quest.exist?
35
+ abort "You've already a quest #{quest}."
36
+ end
37
+
38
+ quest.init!
39
+
40
+ puts init_success_message(quest.dirname)
41
+
42
+ quest
43
+ end
44
+
45
+ def init_success_message(quest_dirname)
46
+ plain_init_success_message(quest_dirname)
47
+ end
48
+
49
+ def plain_init_success_message(quest_dirname)
50
+ "Railsquest quest #{quest_dirname} created."
51
+ end
52
+
53
+ 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
+ Railsquest.gravatar_uri(email)
10
+ end
11
+ end
@@ -0,0 +1,100 @@
1
+ require 'digest/md5'
2
+
3
+ module Railsquest
4
+ module GravatarHelpers
5
+ def gravatar
6
+ gravatar_uri(self.config.email)
7
+ end
8
+ def gravatar_uri(email)
9
+ "http://gravatar.com/avatar/#{Digest::MD5.hexdigest(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 questrted 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,86 @@
1
+ require 'grit'
2
+ require 'pathname'
3
+ require 'json'
4
+
5
+ module Railsquest
6
+ class Quest
7
+
8
+ attr_accessor :name, :port
9
+ attr_reader :secret
10
+
11
+ def self.for_name(name)
12
+ n = name.gsub(/[^A-Za-z-]+/, '_')
13
+ q = new(Railsquest.quests_path.join(n + ".quest"))
14
+ q.name = n
15
+ q
16
+ end
17
+
18
+ def self.html_id(name)
19
+ name.gsub(/[^A-Za-z-]+/, '').downcase
20
+ end
21
+
22
+ def initialize(path)
23
+ @path = Pathname(path)
24
+ end
25
+
26
+ def ==(other)
27
+ other.respond_to?(:path) && self.path == other.path
28
+ end
29
+
30
+ attr_reader :path
31
+
32
+ def exist?
33
+ path.exist?
34
+ end
35
+
36
+ def init!
37
+ secret = `uuidgen`.strip
38
+ contents = '{\"secret\" : \"' + secret + '\", \"port\" : ' + port + '}'
39
+ Dir.chdir(Railsquest.quests_path) { `echo "#{contents}" >> #{path}` }
40
+ end
41
+
42
+ def uri
43
+ Railsquest.quest_uri.gsub(/\/$/, '') + ':' + File.open(path) { |f| JSON.parse(f.gets)["port"].to_s }
44
+ end
45
+
46
+ def secret
47
+ File.open(path) { |f| JSON.parse(f.gets)["secret"] }
48
+ end
49
+
50
+ def name
51
+ dirname.sub(".quest",'')
52
+ end
53
+
54
+ def html_id
55
+ self.class.html_id(name)
56
+ end
57
+
58
+ def dirname
59
+ path.split.last.to_s
60
+ end
61
+
62
+ def to_s
63
+ name
64
+ end
65
+
66
+ def web_uri
67
+ Railsquest.web_uri + "#" + html_id
68
+ end
69
+
70
+ def attempts
71
+
72
+ end
73
+
74
+ def remove!
75
+ path.rmtree
76
+ end
77
+
78
+ def to_hash
79
+ {
80
+ "name" => name,
81
+ "uri" => uri,
82
+ "host_name" => Railsquest.host_name
83
+ }
84
+ end
85
+ end
86
+ end