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