rave 0.1.0
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/bin/rave +24 -0
- data/lib/commands/create.rb +145 -0
- data/lib/commands/server.rb +8 -0
- data/lib/commands/usage.rb +13 -0
- data/lib/commands/war.rb +51 -0
- data/lib/exceptions.rb +6 -0
- data/lib/jars/appengine-api-1.0-sdk-1.2.1.jar +0 -0
- data/lib/jars/jruby-core.jar +0 -0
- data/lib/jars/ruby-stdlib.jar +0 -0
- data/lib/mixins/controller.rb +40 -0
- data/lib/mixins/data_format.rb +168 -0
- data/lib/models/annotation.rb +18 -0
- data/lib/models/blip.rb +61 -0
- data/lib/models/context.rb +45 -0
- data/lib/models/document.rb +9 -0
- data/lib/models/event.rb +48 -0
- data/lib/models/operation.rb +89 -0
- data/lib/models/robot.rb +61 -0
- data/lib/models/wave.rb +19 -0
- data/lib/models/wavelet.rb +69 -0
- data/lib/ops/blip_ops.rb +131 -0
- data/lib/rave.rb +23 -0
- metadata +116 -0
data/bin/rave
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
here = File.dirname(__FILE__)
|
|
4
|
+
%w( create server usage war ).each do |cmd|
|
|
5
|
+
require File.join(here, "..", "lib", "commands", cmd)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
args = ARGV
|
|
9
|
+
cmd = args.shift
|
|
10
|
+
|
|
11
|
+
if cmd
|
|
12
|
+
case cmd
|
|
13
|
+
when "create"
|
|
14
|
+
create_robot(args)
|
|
15
|
+
when "server"
|
|
16
|
+
start_robot(args)
|
|
17
|
+
when "war"
|
|
18
|
+
create_war(args)
|
|
19
|
+
else
|
|
20
|
+
display_usage
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
usage
|
|
24
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
require 'ftools'
|
|
2
|
+
|
|
3
|
+
#Creates a project for a robot. Args are:
|
|
4
|
+
# => robot_name (required)
|
|
5
|
+
# => image_url=http://imageurl.com/ (optional)
|
|
6
|
+
# => profile_url=http://profileurl.com/ (optional)
|
|
7
|
+
# e.g. rave my_robot image_url=http://appropriate-casey.appspot.com/image.png profile_url=http://appropriate-casey.appspot.com/profile.json
|
|
8
|
+
def create_robot(args)
|
|
9
|
+
robot_name = args.first
|
|
10
|
+
module_name = robot_name.split(/_|-/).collect { |word| word[0, 1].upcase + word[1, word.length-1] }.join("")
|
|
11
|
+
robot_class_name = "#{module_name}::Robot"
|
|
12
|
+
options = { :name => robot_name }
|
|
13
|
+
args[1, args.length-1].each do |arg|
|
|
14
|
+
key, value = arg.split("=").collect { |part| part.strip }
|
|
15
|
+
options[key.to_sym] = value
|
|
16
|
+
end
|
|
17
|
+
dir = File.join(".", robot_name)
|
|
18
|
+
lib = File.join(dir, "lib")
|
|
19
|
+
config_dir = File.join(dir, "config")
|
|
20
|
+
file = File.join(dir, "robot.rb")
|
|
21
|
+
appengine_web = File.join(dir, "appengine-web.xml")
|
|
22
|
+
config = File.join(dir, "config.ru")
|
|
23
|
+
here = File.dirname(__FILE__)
|
|
24
|
+
jar_dir = File.join(here, "..", "jars")
|
|
25
|
+
jars = %w( appengine-api-1.0-sdk-1.2.1.jar jruby-core.jar ruby-stdlib.jar )
|
|
26
|
+
#Create the project dir
|
|
27
|
+
puts "Creating directory #{File.expand_path(dir)}"
|
|
28
|
+
Dir.mkdir(dir)
|
|
29
|
+
puts "Creating robot class #{File.expand_path(file)}"
|
|
30
|
+
#Make the base robot class
|
|
31
|
+
File.open(file, "w") do |f|
|
|
32
|
+
f.puts robot_file_contents(module_name)
|
|
33
|
+
end
|
|
34
|
+
#Make the rackup config file
|
|
35
|
+
puts "Creating rackup config file #{File.expand_path(config)}"
|
|
36
|
+
options_str = options.collect { |key, val| ":#{key} => \"#{val}\"" }.join(", ")
|
|
37
|
+
File.open(config, "w") do |f|
|
|
38
|
+
f.puts config_file_contents(robot_class_name, options_str)
|
|
39
|
+
end
|
|
40
|
+
#Make the appengine web xml file
|
|
41
|
+
puts "Creating appengine config file #{File.expand_path(appengine_web)}"
|
|
42
|
+
File.open(appengine_web, "w") do |f|
|
|
43
|
+
f.puts appengine_web_contents(robot_name)
|
|
44
|
+
end
|
|
45
|
+
#Copy jars over
|
|
46
|
+
puts "Creating lib directory #{File.expand_path(lib)}"
|
|
47
|
+
Dir.mkdir(lib)
|
|
48
|
+
jars.each do |jar|
|
|
49
|
+
puts "Adding jar #{jar}"
|
|
50
|
+
File.copy(File.join(jar_dir, jar), File.join(lib, jar))
|
|
51
|
+
end
|
|
52
|
+
#Make the wabler config file
|
|
53
|
+
puts "Creating config directory #{File.expand_path(config_dir)}"
|
|
54
|
+
Dir.mkdir(config_dir)
|
|
55
|
+
warble_file = File.join(config_dir, "warble.rb")
|
|
56
|
+
puts "Creating warble config file #{File.expand_path(warble_file)}"
|
|
57
|
+
File.open(warble_file, "w") do |f|
|
|
58
|
+
f.puts warble_config_contents()
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def robot_file_contents(module_name)
|
|
63
|
+
<<-ROBOT
|
|
64
|
+
require 'rubygems'
|
|
65
|
+
require 'rave'
|
|
66
|
+
|
|
67
|
+
module #{module_name}
|
|
68
|
+
class Robot < Rave::Models::Robot
|
|
69
|
+
|
|
70
|
+
#Define handlers here:
|
|
71
|
+
# e.g. if the robot should act on a DOCUMENT_CHANGED event:
|
|
72
|
+
#
|
|
73
|
+
# def document_changed(event, context)
|
|
74
|
+
# #Do some stuff
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# Events are:
|
|
78
|
+
#
|
|
79
|
+
# WAVELET_BLIP_CREATED, WAVELET_BLIP_REMOVED, WAVELET_PARTICIPANTS_CHANGED,
|
|
80
|
+
# WAVELET_TIMESTAMP_CHANGED, WAVELET_TITLE_CHANGED, WAVELET_VERSION_CHANGED,
|
|
81
|
+
# BLIP_CONTRIBUTORS_CHANGED, BLIP_DELETED, BLIP_SUBMITTED, BLIP_TIMESTAMP_CHANGED,
|
|
82
|
+
# BLIP_VERSION_CHANGED, DOCUMENT_CHANGED, FORM_BUTTON_CLICKED
|
|
83
|
+
#
|
|
84
|
+
# If you want to name your event handler something other than the default name,
|
|
85
|
+
# or you need to have more than one handler for an event, you can register handlers
|
|
86
|
+
# in the robot's constructor:
|
|
87
|
+
#
|
|
88
|
+
# def initialize(options={})
|
|
89
|
+
# super
|
|
90
|
+
# register_handler(Rave::Models::Event::DOCUMENT_CHANGED, :custom_doc_changed_handler)
|
|
91
|
+
# end
|
|
92
|
+
#
|
|
93
|
+
# def custom_doc_changed_handler(event, context)
|
|
94
|
+
# #Do some stuff
|
|
95
|
+
# end
|
|
96
|
+
#
|
|
97
|
+
# Note: Don't forget to call super if you define #initialize
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
ROBOT
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def config_file_contents(robot_class_name, options_str)
|
|
105
|
+
<<-CONFIG
|
|
106
|
+
require 'robot'
|
|
107
|
+
run #{robot_class_name}.new( #{options_str} )
|
|
108
|
+
CONFIG
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def appengine_web_contents(robot_name)
|
|
112
|
+
<<-APPENGINE
|
|
113
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
114
|
+
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
|
115
|
+
<application>#{robot_name}</application>
|
|
116
|
+
<version>1</version>
|
|
117
|
+
<static-files />
|
|
118
|
+
<resource-files />
|
|
119
|
+
<sessions-enabled>false</sessions-enabled>
|
|
120
|
+
<system-properties>
|
|
121
|
+
<property name="jruby.management.enabled" value="false" />
|
|
122
|
+
<property name="os.arch" value="" />
|
|
123
|
+
<property name="jruby.compile.mode" value="JIT"/> <!-- JIT|FORCE|OFF -->
|
|
124
|
+
<property name="jruby.compile.fastest" value="true"/>
|
|
125
|
+
<property name="jruby.compile.frameless" value="true"/>
|
|
126
|
+
<property name="jruby.compile.positionless" value="true"/>
|
|
127
|
+
<property name="jruby.compile.threadless" value="false"/>
|
|
128
|
+
<property name="jruby.compile.fastops" value="false"/>
|
|
129
|
+
<property name="jruby.compile.fastcase" value="false"/>
|
|
130
|
+
<property name="jruby.compile.chainsize" value="500"/>
|
|
131
|
+
<property name="jruby.compile.lazyHandles" value="false"/>
|
|
132
|
+
<property name="jruby.compile.peephole" value="true"/>
|
|
133
|
+
</system-properties>
|
|
134
|
+
</appengine-web-app>
|
|
135
|
+
APPENGINE
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def warble_config_contents
|
|
139
|
+
<<-WARBLE
|
|
140
|
+
Warbler::Config.new do |config|
|
|
141
|
+
config.gems = %w( rave json-jruby rack builder )
|
|
142
|
+
config.includes = %w( robot.rb appengine-web.xml )
|
|
143
|
+
end
|
|
144
|
+
WARBLE
|
|
145
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#Starts up rack based on the config.ru file in the working directory
|
|
2
|
+
# Note that this is of limited use right now, because robots have to
|
|
3
|
+
# run on appengine. Better to test locally with the appengine sdk
|
|
4
|
+
def start_robot(args)
|
|
5
|
+
cmd = (RUBY_PLATFORM == 'java') ? "jruby -S rackup" : "rackup"
|
|
6
|
+
cmd += " " + args.join(" ") if args
|
|
7
|
+
exec(cmd)
|
|
8
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#Display usage for rave command
|
|
2
|
+
def display_usage
|
|
3
|
+
puts "Useage: rave [create | server | war] [robot_name] [options]"
|
|
4
|
+
puts "'create' generates a Google Wave robot client stub application."
|
|
5
|
+
puts "e.g."
|
|
6
|
+
puts "rave create my_robot image_url=http://my_robot.appspot.com/image.png profile_url=http://my_robot.appspot.com/"
|
|
7
|
+
puts "'server' launches the robot"
|
|
8
|
+
puts "e.g."
|
|
9
|
+
puts "rave server"
|
|
10
|
+
puts "'war' creates a war file suitable for deploying to Google AppEngine"
|
|
11
|
+
puts "e.g."
|
|
12
|
+
puts "rave war"
|
|
13
|
+
end
|
data/lib/commands/war.rb
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#Runs warbler to package up the robot
|
|
2
|
+
# then does some cleanup that is specific to App Engine:
|
|
3
|
+
# => Deletes the complete JRuby jar from both the app's lib folder and
|
|
4
|
+
# the frozen warbler gem, and replaces them with a broken version
|
|
5
|
+
# => Changes the file path json-jruby
|
|
6
|
+
# TODO: Not sure why this is necessary, but it doesn't run on appengine without it
|
|
7
|
+
def create_war(args)
|
|
8
|
+
#Run warbler
|
|
9
|
+
system("jruby -S warble")
|
|
10
|
+
web_inf = File.join(".", "tmp", "war", "WEB-INF")
|
|
11
|
+
rave_jars = File.join(File.dirname(__FILE__), "..", "jars")
|
|
12
|
+
#Delete the complete JRuby jar that warbler sticks in lib
|
|
13
|
+
delete_jruby_from_lib(File.join(web_inf, "lib"))
|
|
14
|
+
#Delete the complete JRuby jar from warbler itself
|
|
15
|
+
delete_jruby_from_warbler(File.join(web_inf, "gems", "gems"))
|
|
16
|
+
#Copy the broken up JRuby jar into warbler #TODO Is warbler necessary? Can we just delete warbler?
|
|
17
|
+
copy_jruby_chunks_to_warbler(rave_jars, Dir[File.join(web_inf, "gems", "gems", "warbler-*", "lib")].first)
|
|
18
|
+
#Fix the broken paths in json-jruby
|
|
19
|
+
fix_json_jruby_paths(File.join(web_inf, "gems", "gems"))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def delete_jruby_from_lib(web_inf_lib)
|
|
23
|
+
jar = Dir[File.join(web_inf_lib, "jruby-complete-*.jar")].first
|
|
24
|
+
puts "Deleting #{jar}"
|
|
25
|
+
File.delete(jar) if jar
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def delete_jruby_from_warbler(web_inf_gems)
|
|
29
|
+
jar = Dir[File.join(web_inf_gems, "warbler-*", "lib", "jruby-complete-*.jar")].first
|
|
30
|
+
puts "Deleting #{jar}"
|
|
31
|
+
File.delete(jar) if jar
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def copy_jruby_chunks_to_warbler(rave_jar_dir, warbler_jar_dir)
|
|
35
|
+
puts "Copying jruby chunks"
|
|
36
|
+
%w( jruby-core.jar ruby-stdlib.jar ).each do |jar|
|
|
37
|
+
File.copy(File.join(rave_jar_dir, jar), File.join(warbler_jar_dir, jar))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def fix_json_jruby_paths(web_inf_gems)
|
|
42
|
+
#TODO: Why is this necessary? Is this an appengine issue?
|
|
43
|
+
puts "Fixing paths in json-jruby"
|
|
44
|
+
ext = Dir[File.join(web_inf_gems, "json-jruby-*", "lib", "json", "ext.rb")].first
|
|
45
|
+
if ext
|
|
46
|
+
text = File.open(ext, "r") { |f| f.read }
|
|
47
|
+
text.gsub!("require 'json/ext/parser'", "require 'ext/parser'")
|
|
48
|
+
text.gsub!("require 'json/ext/generator'", "require 'ext/generator'")
|
|
49
|
+
File.open(ext, "w") { |f| f.write(text) }
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/exceptions.rb
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#Contains the Rack #call method - to be mixed in to the Robot class
|
|
2
|
+
module Rave
|
|
3
|
+
module Mixins
|
|
4
|
+
module Controller
|
|
5
|
+
|
|
6
|
+
LOGGER = java.util.logging.Logger.getLogger("Controller")
|
|
7
|
+
|
|
8
|
+
def call(env)
|
|
9
|
+
request = Rack::Request.new(env)
|
|
10
|
+
path = request.path_info
|
|
11
|
+
method = request.request_method
|
|
12
|
+
LOGGER.info("#{method}ing #{path}")
|
|
13
|
+
begin
|
|
14
|
+
#There are only 3 URLs that Wave can access:
|
|
15
|
+
# robot capabilities, robot profile, and event notification
|
|
16
|
+
if path == "/_wave/capabilities.xml" && method == "GET"
|
|
17
|
+
[ 200, { 'Content-Type' => 'text/xml' }, capabilities_xml ]
|
|
18
|
+
elsif path == "/_wave/robot/profile" && method == "GET"
|
|
19
|
+
[ 200, { 'Content-Type' => 'application/json' }, profile_json ]
|
|
20
|
+
elsif path == "/_wave/robot/jsonrpc" && method == "POST"
|
|
21
|
+
body = request.body.read
|
|
22
|
+
context, events = parse_json_body(body)
|
|
23
|
+
events.each do |event|
|
|
24
|
+
handle_event(event, context)
|
|
25
|
+
end
|
|
26
|
+
[ 200, { 'Content-Type' => 'application/json' }, context.to_json ]
|
|
27
|
+
else
|
|
28
|
+
#TODO: Also, give one more option: respond_to?(:non_robot_url) or something - can override in impl
|
|
29
|
+
#TODO: Log this
|
|
30
|
+
[ 404, { 'Content-Type' => 'text/html' }, "404 - Not Found" ]
|
|
31
|
+
end
|
|
32
|
+
rescue Exception => e
|
|
33
|
+
#TODO: Log this
|
|
34
|
+
[ 500, { 'Content-Type' => 'text/html' }, "500 - Internal Server Error"]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#This mixin provides methods for robots to deal with parsing and presenting JSON and XML
|
|
2
|
+
module Rave
|
|
3
|
+
module Mixins
|
|
4
|
+
module DataFormat
|
|
5
|
+
|
|
6
|
+
LOGGER = java.util.logging.Logger.getLogger("DataFormat") unless defined?(LOGGER)
|
|
7
|
+
|
|
8
|
+
#Returns this robot's capabilities in XML
|
|
9
|
+
def capabilities_xml
|
|
10
|
+
xml = Builder::XmlMarkup.new
|
|
11
|
+
xml.instruct!
|
|
12
|
+
xml.tag!("w:robot", "xmlns:w" => "http://wave.google.com/extensions/robots/1.0") do
|
|
13
|
+
xml.tag!("w:capabilities") do
|
|
14
|
+
@handlers.keys.each do |capability|
|
|
15
|
+
xml.tag!("w:capability", "name" => capability)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
unless @cron_jobs.empty?
|
|
19
|
+
xml.tag!("w:crons") do
|
|
20
|
+
@cron_jobs.each do |job|
|
|
21
|
+
xml.tag!("w:cron", "path" => job[:path], "timeinseconds" => job[:seconds])
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
attrs = { "name" => @name }
|
|
26
|
+
attrs["imageurl"] = @image_url if @image_url
|
|
27
|
+
attrs["profileurl"] = @profile_url if @profile_url
|
|
28
|
+
xml.tag!("w:profile", attrs)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#Returns the robot's profile in json format
|
|
33
|
+
def profile_json
|
|
34
|
+
{
|
|
35
|
+
"name" => @name,
|
|
36
|
+
"imageurl" => @image_url,
|
|
37
|
+
"profile_url" => @profile_url
|
|
38
|
+
}.to_json
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
#Parses context and event info from JSON input
|
|
42
|
+
def parse_json_body(json)
|
|
43
|
+
LOGGER.info("Parsing JSON:")
|
|
44
|
+
LOGGER.info(json.to_s)
|
|
45
|
+
data = JSON.parse(json)
|
|
46
|
+
#Create Context
|
|
47
|
+
context = context_from_json(data)
|
|
48
|
+
#Create events
|
|
49
|
+
events = events_from_json(data)
|
|
50
|
+
return context, events
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
protected
|
|
54
|
+
def context_from_json(json)
|
|
55
|
+
blips = {}
|
|
56
|
+
blips_from_json(json).each do |blip|
|
|
57
|
+
blips[blip.id] = blip
|
|
58
|
+
end
|
|
59
|
+
wavelets = {}
|
|
60
|
+
wavelets_from_json(json).each do |wavelet|
|
|
61
|
+
wavelets[wavelet.id] = wavelet
|
|
62
|
+
end
|
|
63
|
+
waves = {}
|
|
64
|
+
#Waves aren't sent back, but we can reconstruct them from the wavelets
|
|
65
|
+
waves_from_wavelets(wavelets).each do |wave|
|
|
66
|
+
waves[wave.id] = wave
|
|
67
|
+
end
|
|
68
|
+
Rave::Models::Context.new(
|
|
69
|
+
:waves => waves,
|
|
70
|
+
:wavelets => wavelets,
|
|
71
|
+
:blips => blips
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def blips_from_json(json)
|
|
76
|
+
if json['blips']
|
|
77
|
+
json['blips']['map'].values.collect do |blip_data|
|
|
78
|
+
blip = Rave::Models::Blip.new(
|
|
79
|
+
:id => blip_data['blipId'],
|
|
80
|
+
:annotations => annotations_from_json(json),
|
|
81
|
+
:child_blip_ids => blip_data['childBlipIds'],
|
|
82
|
+
:content => blip_data['content'],
|
|
83
|
+
:contributors => blip_data['contributors'],
|
|
84
|
+
:creator => blip_data['creator'],
|
|
85
|
+
:elements => blip_data['elements'],
|
|
86
|
+
:last_modified_time => blip_data['lastModifiedTime'],
|
|
87
|
+
:parent_blip_id => blip_data['parentBlipId'],
|
|
88
|
+
:version => blip_data['version'],
|
|
89
|
+
:wave_id => blip_data['waveId'],
|
|
90
|
+
:wavelet_id => blip_data['waveletId']
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
else
|
|
94
|
+
[]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def annotations_from_json(json)
|
|
99
|
+
if json['annotation']
|
|
100
|
+
json['annotations'].collect do |annotation|
|
|
101
|
+
Rave::Models::Annotation.new(
|
|
102
|
+
:name => annotation['name'],
|
|
103
|
+
:value => annotation['value'],
|
|
104
|
+
:range => Range.new(annotation['range']['start'], annotation['range']['end'])
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
else
|
|
108
|
+
[]
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def events_from_json(json)
|
|
113
|
+
if json['events'] && json['events']['list']
|
|
114
|
+
json['events']['list'].collect do |event|
|
|
115
|
+
properties = {}
|
|
116
|
+
event['properties']['map'].each { |key, value| properties[key] = value['list'] }
|
|
117
|
+
Rave::Models::Event.new(
|
|
118
|
+
:type => event['type'],
|
|
119
|
+
:timestamp => event['timestamp'],
|
|
120
|
+
:modified_by => event['modifiedBy'],
|
|
121
|
+
:properties => properties
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
[]
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def wavelets_from_json(json)
|
|
130
|
+
#Currently only one wavelet is sent back
|
|
131
|
+
#TODO: should this look at the wavelet's children too?
|
|
132
|
+
wavelet = json['wavelet']
|
|
133
|
+
if wavelet
|
|
134
|
+
[
|
|
135
|
+
Rave::Models::Wavelet.new(
|
|
136
|
+
:creator => wavelet['creator'],
|
|
137
|
+
:creation_time => wavelet['creationTime'],
|
|
138
|
+
:data_documents => wavelet['dataDocuments'],
|
|
139
|
+
:last_modifed_time => wavelet['lastModifiedTime'],
|
|
140
|
+
:participants => wavelet['participants'],
|
|
141
|
+
:root_blip_id => wavelet['rootBlipId'],
|
|
142
|
+
:title => wavelet['title'],
|
|
143
|
+
:version => wavelet['version'],
|
|
144
|
+
:wave_id => wavelet['waveId'],
|
|
145
|
+
:id => wavelet['waveletId']
|
|
146
|
+
)
|
|
147
|
+
]
|
|
148
|
+
else
|
|
149
|
+
[]
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def waves_from_wavelets(wavelets)
|
|
154
|
+
wave_wavelet_map = {}
|
|
155
|
+
if wavelets
|
|
156
|
+
wavelets.values.each do |wavelet|
|
|
157
|
+
wave_wavelet_map[wavelet.wave_id] ||= []
|
|
158
|
+
wave_wavelet_map[wavelet.wave_id] << wavelet.id
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
wave_wavelet_map.collect do |wave_id, wavelet_ids|
|
|
162
|
+
Rave::Models::Wave.new(:id => wave_id, :wavelet_ids => wavelet_ids)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Rave
|
|
2
|
+
module Models
|
|
3
|
+
class Annotation
|
|
4
|
+
attr_reader :name, :value, :range
|
|
5
|
+
|
|
6
|
+
#Options include
|
|
7
|
+
# - :name
|
|
8
|
+
# - :value
|
|
9
|
+
# - :range
|
|
10
|
+
def initialize(options = {})
|
|
11
|
+
@name = options[:name]
|
|
12
|
+
@value = options[:value]
|
|
13
|
+
@range = options[:range]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/models/blip.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#Represents a Blip, owned by a Wavelet
|
|
2
|
+
module Rave
|
|
3
|
+
module Models
|
|
4
|
+
class Blip
|
|
5
|
+
attr_reader :id, :annotations, :child_blip_ids, :content, :constributors, :creator,
|
|
6
|
+
:elements, :last_modified_time, :parent_blip_id, :version, :wave_id, :wavelet_id
|
|
7
|
+
attr_accessor :context
|
|
8
|
+
|
|
9
|
+
#Options include:
|
|
10
|
+
# - :annotations
|
|
11
|
+
# - :child_blip_ids
|
|
12
|
+
# - :content
|
|
13
|
+
# - :contributors
|
|
14
|
+
# - :creator
|
|
15
|
+
# - :elements
|
|
16
|
+
# - :last_modified_time
|
|
17
|
+
# - :parent_blip_id
|
|
18
|
+
# - :version
|
|
19
|
+
# - :wave_id
|
|
20
|
+
# - :wavelet_id
|
|
21
|
+
# - :id
|
|
22
|
+
# - :context
|
|
23
|
+
def initialize(options = {})
|
|
24
|
+
@annotations = options[:annotations] || []
|
|
25
|
+
@child_blip_ids = Set.new(options[:child_blip_ids])
|
|
26
|
+
@content = options[:content]
|
|
27
|
+
@contributors = Set.new(options[:contributors])
|
|
28
|
+
@creator = options[:creator]
|
|
29
|
+
@elements = options[:elements] || {}
|
|
30
|
+
@last_modified_time = options[:last_modified_time] || Time.now
|
|
31
|
+
@parent_blip_id = options[:parent_blip_id]
|
|
32
|
+
@version = options[:version] || -1
|
|
33
|
+
@wave_id = options[:wave_id]
|
|
34
|
+
@wavelet_id = options[:wavelet_id]
|
|
35
|
+
@id = options[:id]
|
|
36
|
+
@context = options[:context]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#Returns true if this is a root blip (no parent blip)
|
|
40
|
+
def root?
|
|
41
|
+
@parent_blip_id.nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#Returns true if an annotation with the given name exists in this blip
|
|
45
|
+
def has_annotation?(name)
|
|
46
|
+
@annotations.any? { |a| a.name == name }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
#Creates a child blip under this blip
|
|
50
|
+
def create_child_blip
|
|
51
|
+
#TODO
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#Delete this blip from its wavelet
|
|
55
|
+
def delete
|
|
56
|
+
#TODO
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#Contains server request information including current waves and operations
|
|
2
|
+
module Rave
|
|
3
|
+
module Models
|
|
4
|
+
class Context
|
|
5
|
+
attr_reader :waves, :wavelets, :blips, :operations
|
|
6
|
+
|
|
7
|
+
#Options include:
|
|
8
|
+
# - :waves
|
|
9
|
+
# - :wavelets
|
|
10
|
+
# - :blips
|
|
11
|
+
# - :operations
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@waves = options[:waves] || {}
|
|
14
|
+
@waves.values.each { |wave| wave.context = self } #Set up self as this wave's context
|
|
15
|
+
@wavelets = options[:wavelets] || {}
|
|
16
|
+
@wavelets.values.each { |wavelet| wavelet.context = self } #Set up self as this wavelet's context
|
|
17
|
+
@blips = options[:blips] || {}
|
|
18
|
+
@blips.values.each { |blip| blip.context = self } #Set up self as this blip's context
|
|
19
|
+
@operations = options[:operations] || []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#Find the root wavelet if it exists in this context
|
|
23
|
+
def root_wavelet
|
|
24
|
+
@wavelets.values.find { |wavelet| wavelet.id =~ Regexp.new(Rave::Models::Wavelet::ROOT_ID_REGEXP) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#Serializes the context to JSON format
|
|
28
|
+
def to_json
|
|
29
|
+
self.to_hash.to_json
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#Serialize the context to a hash map
|
|
33
|
+
def to_hash
|
|
34
|
+
hash = {
|
|
35
|
+
'operations' => { 'javaClass' => 'java.util.ArrayList', 'list' => [] },
|
|
36
|
+
'javaClass' => 'com.google.wave.api.impl.OperationMessageBundle'
|
|
37
|
+
}
|
|
38
|
+
@operations.each do |op|
|
|
39
|
+
hash['operations']['list'] << op.to_hash
|
|
40
|
+
end
|
|
41
|
+
hash
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/models/event.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#Represents and event
|
|
2
|
+
module Rave
|
|
3
|
+
module Models
|
|
4
|
+
class Event
|
|
5
|
+
attr_reader :type, :timestamp, :modified_by, :properties
|
|
6
|
+
|
|
7
|
+
#Event types:
|
|
8
|
+
WAVELET_BLIP_CREATED = 'WAVELET_BLIP_CREATED'
|
|
9
|
+
WAVELET_BLIP_REMOVED = 'WAVELET_BLIP_REMOVED'
|
|
10
|
+
WAVELET_PARTICIPANTS_CHANGED = 'WAVELET_PARTICIPANTS_CHANGED'
|
|
11
|
+
WAVELET_TIMESTAMP_CHANGED = 'WAVELET_TIMESTAMP_CHANGED'
|
|
12
|
+
WAVELET_TITLE_CHANGED = 'WAVELET_TITLE_CHANGED'
|
|
13
|
+
WAVELET_VERSION_CHANGED = 'WAVELET_VERSION_CHANGED'
|
|
14
|
+
BLIP_CONTRIBUTORS_CHANGED = 'BLIP_CONTRIBUTORS_CHANGED'
|
|
15
|
+
BLIP_DELETED = 'BLIP_DELETED'
|
|
16
|
+
BLIP_SUBMITTED = 'BLIP_SUBMITTED'
|
|
17
|
+
BLIP_TIMESTAMP_CHANGED = 'BLIP_TIMESTAMP_CHANGED'
|
|
18
|
+
BLIP_VERSION_CHANGED = 'BLIP_VERSION_CHANGED'
|
|
19
|
+
DOCUMENT_CHANGED = 'DOCUMENT_CHANGED'
|
|
20
|
+
FORM_BUTTON_CLICKED = 'FORM_BUTTON_CLICKED'
|
|
21
|
+
|
|
22
|
+
VALID_EVENTS = [
|
|
23
|
+
WAVELET_BLIP_CREATED, WAVELET_BLIP_REMOVED, WAVELET_PARTICIPANTS_CHANGED,
|
|
24
|
+
WAVELET_TIMESTAMP_CHANGED, WAVELET_TITLE_CHANGED, WAVELET_VERSION_CHANGED,
|
|
25
|
+
BLIP_CONTRIBUTORS_CHANGED, BLIP_DELETED, BLIP_SUBMITTED, BLIP_TIMESTAMP_CHANGED,
|
|
26
|
+
BLIP_VERSION_CHANGED, DOCUMENT_CHANGED, FORM_BUTTON_CLICKED
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
#Options include:
|
|
30
|
+
# - :type
|
|
31
|
+
# - :timestamp
|
|
32
|
+
# - :modified_by
|
|
33
|
+
# - :properties
|
|
34
|
+
def initialize(options = {})
|
|
35
|
+
@type = options[:type]
|
|
36
|
+
@timestamp = options[:timestamp] || Time.now
|
|
37
|
+
@modified_by = options[:modified_by]
|
|
38
|
+
@properties = options[:properties] || {}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
#Returns true if the event_type is a possible event type, and false if not
|
|
42
|
+
def self.valid_event_type?(event_type)
|
|
43
|
+
VALID_EVENTS.include?(event_type)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#Represents an operation to be applied on the server
|
|
2
|
+
module Rave
|
|
3
|
+
module Models
|
|
4
|
+
class Operation
|
|
5
|
+
attr_reader :type, :wave_id, :wavelet_id, :blip_id, :index, :property
|
|
6
|
+
|
|
7
|
+
#Constants
|
|
8
|
+
# Types of operations
|
|
9
|
+
WAVELET_APPEND_BLIP = 'WAVELET_APPEND_BLIP'
|
|
10
|
+
WAVELET_ADD_PARTICIPANT = 'WAVELET_ADD_PARTICIPANT'
|
|
11
|
+
WAVELET_CREATE = 'WAVELET_CREATE'
|
|
12
|
+
WAVELET_REMOVE_SELF = 'WAVELET_REMOVE_SELF'
|
|
13
|
+
WAVELET_DATADOC_SET = 'WAVELET_DATADOC_SET'
|
|
14
|
+
WAVELET_SET_TITLE = 'WAVELET_SET_TITLE'
|
|
15
|
+
BLIP_CREATE_CHILD = 'BLIP_CREATE_CHILD'
|
|
16
|
+
BLIP_DELETE = 'BLIP_DELETE'
|
|
17
|
+
DOCUMENT_ANNOTATION_DELETE = 'DOCUMENT_ANNOTATION_DELETE'
|
|
18
|
+
DOCUMENT_ANNOTATION_SET = 'DOCUMENT_ANNOTATION_SET'
|
|
19
|
+
DOCUMENT_ANNOTATION_SET_NORANGE = 'DOCUMENT_ANNOTATION_SET_NORANGE'
|
|
20
|
+
DOCUMENT_APPEND = 'DOCUMENT_APPEND'
|
|
21
|
+
DOCUMENT_APPEND_STYLED_TEXT = 'DOCUMENT_APPEND_STYLED_TEXT'
|
|
22
|
+
DOCUMENT_INSERT = 'DOCUMENT_INSERT'
|
|
23
|
+
DOCUMENT_DELETE = 'DOCUMENT_DELETE'
|
|
24
|
+
DOCUMENT_REPLACE = 'DOCUMENT_REPLACE'
|
|
25
|
+
DOCUMENT_ELEMENT_APPEND = 'DOCUMENT_ELEMENT_APPEND'
|
|
26
|
+
DOCUMENT_ELEMENT_DELETE = 'DOCUMENT_ELEMENT_DELETE'
|
|
27
|
+
DOCUMENT_ELEMENT_INSERT = 'DOCUMENT_ELEMENT_INSERT'
|
|
28
|
+
DOCUMENT_ELEMENT_INSERT_AFTER = 'DOCUMENT_ELEMENT_INSERT_AFTER'
|
|
29
|
+
DOCUMENT_ELEMENT_INSERT_BEFORE = 'DOCUMENT_ELEMENT_INSERT_BEFORE'
|
|
30
|
+
DOCUMENT_ELEMENT_REPLACE = 'DOCUMENT_ELEMENT_REPLACE'
|
|
31
|
+
DOCUMENT_INLINE_BLIP_APPEND = 'DOCUMENT_INLINE_BLIP_APPEND'
|
|
32
|
+
DOCUMENT_INLINE_BLIP_DELETE = 'DOCUMENT_INLINE_BLIP_DELETE'
|
|
33
|
+
DOCUMENT_INLINE_BLIP_INSERT = 'DOCUMENT_INLINE_BLIP_INSERT'
|
|
34
|
+
DOCUMENT_INLINE_BLIP_INSERT_AFTER_ELEMENT = 'DOCUMENT_INLINE_BLIP_INSERT_AFTER_ELEMENT'
|
|
35
|
+
|
|
36
|
+
#Options include:
|
|
37
|
+
# - :type
|
|
38
|
+
# - :wave_id
|
|
39
|
+
# - :wavelet_id
|
|
40
|
+
# - :blip_id
|
|
41
|
+
# - :index
|
|
42
|
+
# - :property
|
|
43
|
+
def initialize(options = {})
|
|
44
|
+
@type = options[:type]
|
|
45
|
+
@wave_id = options[:wave_id]
|
|
46
|
+
@wavelet_id = options[:wavelet_id]
|
|
47
|
+
@blip_id = options[:blip_id]
|
|
48
|
+
@index = options[:index] || -1
|
|
49
|
+
@property = options[:property]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#Serialize the operation to hash
|
|
53
|
+
def to_hash
|
|
54
|
+
{
|
|
55
|
+
'blipId' => @blip_id,
|
|
56
|
+
'index' => @index,
|
|
57
|
+
'waveletId' => @wavelet_id,
|
|
58
|
+
'waveId' => @wave_id,
|
|
59
|
+
'type' => @type,
|
|
60
|
+
'javaClass' => 'com.google.wave.api.impl.OperationImpl',
|
|
61
|
+
'property' => property_to_hash
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#Serialize teh operation to json
|
|
66
|
+
def to_json
|
|
67
|
+
to_hash.to_json
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
protected
|
|
71
|
+
#Decide what kind of property it is and return the value that should be in the JSON
|
|
72
|
+
def property_to_hash
|
|
73
|
+
if @property.kind_of?(String)
|
|
74
|
+
@property
|
|
75
|
+
elsif @property.kind_of?(Range)
|
|
76
|
+
{
|
|
77
|
+
'javaClass' => 'com.google.wave.api.Range',
|
|
78
|
+
'start' => @property.first,
|
|
79
|
+
'end' => @property.last
|
|
80
|
+
}
|
|
81
|
+
else
|
|
82
|
+
#TODO: What else needs to be in here?
|
|
83
|
+
raise "I don't know what that property is..."
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
data/lib/models/robot.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#Contains Robot data, event handlers and cron jobs
|
|
2
|
+
module Rave
|
|
3
|
+
module Models
|
|
4
|
+
class Robot
|
|
5
|
+
include Rave::Mixins::DataFormat
|
|
6
|
+
include Rave::Mixins::Controller
|
|
7
|
+
|
|
8
|
+
attr_reader :name, :image_url, :profile_url
|
|
9
|
+
|
|
10
|
+
#Options include:
|
|
11
|
+
# - :name
|
|
12
|
+
# - :image_url
|
|
13
|
+
# - :profile_url
|
|
14
|
+
def initialize(options = {})
|
|
15
|
+
@name = options[:name]
|
|
16
|
+
@image_url = options[:image_url]
|
|
17
|
+
@profile_url = options[:profile_url]
|
|
18
|
+
@handlers = {}
|
|
19
|
+
@cron_jobs = []
|
|
20
|
+
register_default_handlers
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#Register a handler
|
|
24
|
+
# event_type is a string, and must be one of Rave::Models::Event::VALID_EVENTS
|
|
25
|
+
# multiple handlers may be applied to an event
|
|
26
|
+
def register_handler(event_type, handler)
|
|
27
|
+
raise Rave::InvalidEventException.new("Unknown event: #{event_type}") unless Rave::Models::Event.valid_event_type?(event_type)
|
|
28
|
+
raise Rave::InvalidHandlerException.new("Unknown handler: #{handler}") unless self.respond_to?(handler)
|
|
29
|
+
@handlers[event_type] ||= []
|
|
30
|
+
@handlers[event_type] << handler unless @handlers[event_type].include?(handler)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#Dispatches events to the appropriate handler
|
|
34
|
+
def handle_event(event, context)
|
|
35
|
+
#Ignore unhandled events
|
|
36
|
+
if (handlers = @handlers[event.type])
|
|
37
|
+
handlers.each do |handler|
|
|
38
|
+
self.send(handler, event, context)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#Registers a cron job
|
|
44
|
+
def register_cron_job(path, seconds)
|
|
45
|
+
@cron_jobs << {:path => path, :seconds => seconds}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
protected
|
|
49
|
+
#Register any handlers that are defined through naming convention
|
|
50
|
+
def register_default_handlers
|
|
51
|
+
Event::VALID_EVENTS.each do |event|
|
|
52
|
+
listener = event.downcase.to_sym
|
|
53
|
+
if respond_to?(listener)
|
|
54
|
+
register_handler(event, listener)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/models/wave.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Represents a Wave
|
|
2
|
+
module Rave
|
|
3
|
+
module Models
|
|
4
|
+
class Wave
|
|
5
|
+
attr_reader :id, :wavelet_ids
|
|
6
|
+
|
|
7
|
+
attr_accessor :context
|
|
8
|
+
|
|
9
|
+
#Options include:
|
|
10
|
+
# - :wavelet_ids
|
|
11
|
+
# - :id
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@id = options[:id]
|
|
14
|
+
@wavelet_ids = Set.new(options[:wavelet_ids])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Represents a Wavelet, owned by a Wave
|
|
2
|
+
module Rave
|
|
3
|
+
module Models
|
|
4
|
+
class Wavelet
|
|
5
|
+
attr_reader :creator, :creation_time, :data_documents, :last_modified_time,
|
|
6
|
+
:participants, :root_blip_id, :title, :version, :wave_id, :id
|
|
7
|
+
attr_accessor :context #Context needs to be able to set this
|
|
8
|
+
|
|
9
|
+
ROOT_ID_SUFFIX = "conv+root" #The suffix for the root wavelet in a wave]
|
|
10
|
+
ROOT_ID_REGEXP = /conv\+root$/
|
|
11
|
+
|
|
12
|
+
# Options include:
|
|
13
|
+
# - :creator
|
|
14
|
+
# - :creation_time
|
|
15
|
+
# - :data_documents
|
|
16
|
+
# - :last_modifed_time
|
|
17
|
+
# - :participants
|
|
18
|
+
# - :root_blip_id
|
|
19
|
+
# - :title
|
|
20
|
+
# - :version
|
|
21
|
+
# - :wave_id
|
|
22
|
+
# - :context
|
|
23
|
+
# - :id
|
|
24
|
+
def initialize(options = {})
|
|
25
|
+
@creator = options[:creator]
|
|
26
|
+
@creation_time = options[:creation_time] || Time.now
|
|
27
|
+
@data_documents = options[:data_documents] || {}
|
|
28
|
+
@last_modified_time = options[:last_modified_time] || Time.now
|
|
29
|
+
@participants = Set.new(options[:participants])
|
|
30
|
+
@root_blip_id = options[:root_blip_id]
|
|
31
|
+
@title = options[:title]
|
|
32
|
+
@version = options[:version] || 0
|
|
33
|
+
@wave_id = options[:wave_id]
|
|
34
|
+
@context = options[:context]
|
|
35
|
+
@id = options[:id]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#Creates a blip for this wavelet
|
|
39
|
+
def create_blip
|
|
40
|
+
#TODO
|
|
41
|
+
blip = Blip.new(:wave_id => @wave_id, :wavelet_id => @id)
|
|
42
|
+
@context.operations << Operation.new(:type => Operation::WAVELET_APPEND_BLIP, :wave_id => @wave_id, :wavelet_id => @id, :prop => blip)
|
|
43
|
+
@context.add_blip(blip)
|
|
44
|
+
blip
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#Adds a participant to the wavelet
|
|
48
|
+
def add_participant(participant_id)
|
|
49
|
+
#TODO
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#Removes this robot from the wavelet
|
|
53
|
+
def remove_robot
|
|
54
|
+
#TODO
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
#Sets the data document for the wavelet
|
|
58
|
+
def set_data_document(name, data)
|
|
59
|
+
#TODO
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#Set the title
|
|
63
|
+
def title=(title)
|
|
64
|
+
@title = title
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/ops/blip_ops.rb
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#Reopen the blip class and add operation-related methods
|
|
2
|
+
module Rave
|
|
3
|
+
module Models
|
|
4
|
+
class Blip
|
|
5
|
+
|
|
6
|
+
#Clear the content
|
|
7
|
+
def clear
|
|
8
|
+
@context.operations << Operation.new(
|
|
9
|
+
:type => Operation::DOCUMENT_DELETE,
|
|
10
|
+
:blip_id => @id,
|
|
11
|
+
:wavelet_id => @wavelet_id,
|
|
12
|
+
:wave_id => @wave_id,
|
|
13
|
+
:index => 0,
|
|
14
|
+
:property => 0..(@content ? @content.length : 0)
|
|
15
|
+
)
|
|
16
|
+
@content = ''
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
#Insert text at an index
|
|
20
|
+
def insert_text(text, index)
|
|
21
|
+
@context.operations << Operation.new(
|
|
22
|
+
:type => Operation::DOCUMENT_INSERT,
|
|
23
|
+
:blip_id => @id,
|
|
24
|
+
:wavelet_id => @wavelet_id,
|
|
25
|
+
:wave_id => @wave_id,
|
|
26
|
+
:index => index,
|
|
27
|
+
:property => text
|
|
28
|
+
)
|
|
29
|
+
@content = @content ? @content[0, index] + text + @content[index, @content.length - index] : text
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#Set the content text of the blip
|
|
33
|
+
def set_text(text)
|
|
34
|
+
clear
|
|
35
|
+
insert_text(text, 0)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#Deletes the text in a given range and replaces it with the given text
|
|
39
|
+
def set_text_in_range(range, text)
|
|
40
|
+
delete_range(range)
|
|
41
|
+
insert_text(text, range.first)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#Appends text to the end of the content
|
|
45
|
+
def append_text(text)
|
|
46
|
+
@context.operations << Operation.new(
|
|
47
|
+
:type => Operation::DOCUMENT_APPEND,
|
|
48
|
+
:blip_id => @id,
|
|
49
|
+
:wavelet_id => @wavelet_id,
|
|
50
|
+
:wave_id => @wave_id,
|
|
51
|
+
:property => text
|
|
52
|
+
)
|
|
53
|
+
@content = @content + text
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#Deletes text in the given range
|
|
57
|
+
def delete_range(range)
|
|
58
|
+
@context.operations << Operation.new(
|
|
59
|
+
:type => Operation::DOCUMENT_DELETE,
|
|
60
|
+
:blip_id => @id,
|
|
61
|
+
:wavelet_id => @wavelet_id,
|
|
62
|
+
:wave_id => @wave_id,
|
|
63
|
+
:index => range.first,
|
|
64
|
+
:property => range
|
|
65
|
+
)
|
|
66
|
+
@content = @content[0..range.first-1] + @content[range.last+1..@content.length-1]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#Annotates the entire content
|
|
70
|
+
def annotate_document(name, value)
|
|
71
|
+
#TODO
|
|
72
|
+
raise "This hasn't been implemented yet"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
#Deletes the annotation with the given name
|
|
76
|
+
def delete_annotation_by_name(name)
|
|
77
|
+
#TODO
|
|
78
|
+
raise "This hasn't been implemented yet"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
#Deletes the annotations with the given key in the given range
|
|
82
|
+
def delete_annotation_in_range(range, name)
|
|
83
|
+
#TODO
|
|
84
|
+
raise "This hasn't been implemented yet"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
#Appends an inline blip to this blip
|
|
88
|
+
def append_inline_blip
|
|
89
|
+
#TODO
|
|
90
|
+
raise "This hasn't been implemented yet"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
#Deletes an inline blip from this blip
|
|
94
|
+
def delete_inline_blip(blip_id)
|
|
95
|
+
#TODO
|
|
96
|
+
raise "This hasn't been implemented yet"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#Inserts an inline blip at the given position
|
|
100
|
+
def insert_inline_blip(position)
|
|
101
|
+
#TODO
|
|
102
|
+
raise "This hasn't been implemented yet"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#Deletes an element at the given position
|
|
106
|
+
def delete_element(position)
|
|
107
|
+
#TODO
|
|
108
|
+
raise "This hasn't been implemented yet"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#Inserts the given element in the given position
|
|
112
|
+
def insert_element(position, element)
|
|
113
|
+
#TODO
|
|
114
|
+
raise "This hasn't been implemented yet"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
#Replaces the element at the given position with the given element
|
|
118
|
+
def replace_element(position, element)
|
|
119
|
+
#TODO
|
|
120
|
+
raise "This hasn't been implemented yet"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
#Appends an element
|
|
124
|
+
def append_element(element)
|
|
125
|
+
#TODO
|
|
126
|
+
raise "This hasn't been implemented yet"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/rave.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'set'
|
|
3
|
+
require 'builder'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
here = File.dirname(__FILE__)
|
|
7
|
+
mixins = File.join(here, "mixins")
|
|
8
|
+
models = File.join(here, "models")
|
|
9
|
+
ops = File.join(here, "ops")
|
|
10
|
+
|
|
11
|
+
require File.join(here, 'exceptions')
|
|
12
|
+
|
|
13
|
+
%w( data_format controller ).each do |dep|
|
|
14
|
+
require File.join(mixins, dep)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
%w( annotation blip context document event operation robot wave wavelet).each do |dep|
|
|
18
|
+
require File.join(models, dep)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
%w( blip_ops ).each do |dep|
|
|
22
|
+
require File.join(ops, dep)
|
|
23
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
extensions: []
|
|
3
|
+
|
|
4
|
+
homepage: http://github.com/diminish7/rave
|
|
5
|
+
executables:
|
|
6
|
+
- rave
|
|
7
|
+
version: !ruby/object:Gem::Version
|
|
8
|
+
version: 0.1.0
|
|
9
|
+
post_install_message:
|
|
10
|
+
date: 2009-06-20 07:00:00 +00:00
|
|
11
|
+
files:
|
|
12
|
+
- lib/exceptions.rb
|
|
13
|
+
- lib/rave.rb
|
|
14
|
+
- lib/commands/create.rb
|
|
15
|
+
- lib/commands/server.rb
|
|
16
|
+
- lib/commands/usage.rb
|
|
17
|
+
- lib/commands/war.rb
|
|
18
|
+
- lib/jars/appengine-api-1.0-sdk-1.2.1.jar
|
|
19
|
+
- lib/jars/jruby-core.jar
|
|
20
|
+
- lib/jars/ruby-stdlib.jar
|
|
21
|
+
- lib/mixins/controller.rb
|
|
22
|
+
- lib/mixins/data_format.rb
|
|
23
|
+
- lib/models/annotation.rb
|
|
24
|
+
- lib/models/blip.rb
|
|
25
|
+
- lib/models/context.rb
|
|
26
|
+
- lib/models/document.rb
|
|
27
|
+
- lib/models/event.rb
|
|
28
|
+
- lib/models/operation.rb
|
|
29
|
+
- lib/models/robot.rb
|
|
30
|
+
- lib/models/wave.rb
|
|
31
|
+
- lib/models/wavelet.rb
|
|
32
|
+
- lib/ops/blip_ops.rb
|
|
33
|
+
- bin/rave
|
|
34
|
+
rubygems_version: 1.3.3
|
|
35
|
+
rdoc_options: []
|
|
36
|
+
|
|
37
|
+
signing_key:
|
|
38
|
+
cert_chain: []
|
|
39
|
+
|
|
40
|
+
name: rave
|
|
41
|
+
has_rdoc: true
|
|
42
|
+
platform: ruby
|
|
43
|
+
summary: A Google Wave robot client API for Ruby
|
|
44
|
+
default_executable:
|
|
45
|
+
bindir: bin
|
|
46
|
+
licenses: []
|
|
47
|
+
|
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
|
+
version:
|
|
50
|
+
requirements:
|
|
51
|
+
- - '>='
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: "0"
|
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
55
|
+
version:
|
|
56
|
+
requirements:
|
|
57
|
+
- - '>='
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: 1.8.6
|
|
60
|
+
require_paths:
|
|
61
|
+
- lib
|
|
62
|
+
specification_version: 3
|
|
63
|
+
test_files: []
|
|
64
|
+
|
|
65
|
+
dependencies:
|
|
66
|
+
- !ruby/object:Gem::Dependency
|
|
67
|
+
type: :runtime
|
|
68
|
+
name: rack
|
|
69
|
+
version_requirement:
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
version:
|
|
72
|
+
requirements:
|
|
73
|
+
- - '>='
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: "1.0"
|
|
76
|
+
- !ruby/object:Gem::Dependency
|
|
77
|
+
type: :runtime
|
|
78
|
+
name: builder
|
|
79
|
+
version_requirement:
|
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
81
|
+
version:
|
|
82
|
+
requirements:
|
|
83
|
+
- - '>='
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: 2.1.2
|
|
86
|
+
- !ruby/object:Gem::Dependency
|
|
87
|
+
type: :runtime
|
|
88
|
+
name: json-jruby
|
|
89
|
+
version_requirement:
|
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
91
|
+
version:
|
|
92
|
+
requirements:
|
|
93
|
+
- - '>='
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: 1.1.6
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
type: :runtime
|
|
98
|
+
name: warbler
|
|
99
|
+
version_requirement:
|
|
100
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
101
|
+
version:
|
|
102
|
+
requirements:
|
|
103
|
+
- - '>='
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: 0.9.13
|
|
106
|
+
description: A toolkit for building Google Wave robots in Ruby
|
|
107
|
+
email: diminish7@gmail.com
|
|
108
|
+
authors:
|
|
109
|
+
- Jason Rush
|
|
110
|
+
- Jay Donnell
|
|
111
|
+
extra_rdoc_files: []
|
|
112
|
+
|
|
113
|
+
requirements: []
|
|
114
|
+
|
|
115
|
+
rubyforge_project: rave
|
|
116
|
+
autorequire:
|