rave 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ module Rave
2
+ module Mixins
3
+ module Logger
4
+
5
+ def logger
6
+ if @logger.nil?
7
+ if RUBY_PLATFORM == 'java'
8
+ @logger = java.util.logging.Logger.getLogger(self.class.to_s)
9
+ else
10
+ #TODO: Need to be able to configure output
11
+ @logger = ::Logger.new(STDOUT)
12
+ end
13
+ end
14
+ @logger
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,87 @@
1
+ module Rave
2
+ module Mixins
3
+ # Abstract object that allows you to create instances of the classes inside
4
+ # it based on providing a type name.
5
+ module ObjectFactory
6
+ WILDCARD = '*' unless defined? WILDCARD
7
+
8
+ def self.included(base)
9
+ base.class_eval do
10
+ # Store the registered classes in a class instance variable.
11
+ class << self
12
+ attr_reader :class_by_type_mapping
13
+ attr_reader :class_by_pattern_mapping
14
+ end
15
+
16
+ @class_by_type_mapping = {}
17
+ @class_by_pattern_mapping = {}
18
+
19
+ class_eval(<<-END, __FILE__, __LINE__)
20
+ def self.classes_by_type
21
+ ::#{self.name}.class_by_type_mapping
22
+ end
23
+ def self.classes_by_pattern
24
+ ::#{self.name}.class_by_pattern_mapping
25
+ end
26
+ END
27
+
28
+ # Object factory method.
29
+ #
30
+ # :type - Type of object to create [String]
31
+ def self.create(type, *args, &block)
32
+ if classes_by_type.has_key? type
33
+ return classes_by_type[type].new(*args, &block)
34
+ elsif
35
+ # Check for pattern-based types. Check for longer matches before shorter ones.
36
+ patterns = classes_by_pattern.keys.sort { |a, b| b.to_s.length <=> a.to_s.length }
37
+ patterns.each do |pattern|
38
+ if type =~ pattern
39
+ return classes_by_pattern[pattern].new($1, *args, &block)
40
+ end
41
+ end
42
+ raise ArgumentError.new("Unknown #{self} type #{type}")
43
+ end
44
+ end
45
+
46
+ # Is this type able to be created?
47
+ def self.valid_type?(type)
48
+ classes_by_type.has_key? type
49
+ end
50
+
51
+ # Register this class with its factory.
52
+ def self.factory_register(type)
53
+ classes_by_type[type] = self
54
+
55
+ # * in a type indicates a wildcard.
56
+ if type[WILDCARD]
57
+ classes_by_pattern[/^#{type.sub(WILDCARD, '(.*)')}$/] = self
58
+ end
59
+
60
+ class << self
61
+ def type; @type.dup; end
62
+ end
63
+
64
+ @type = type
65
+
66
+ end
67
+
68
+ # Classes that can be generated by the factory [Array of Class]
69
+ def self.classes
70
+ classes_by_type.values
71
+ end
72
+
73
+ # Types that can be generated by the factory [Array of String]
74
+ def self.types
75
+ classes_by_type.keys
76
+ end
77
+ end
78
+ end
79
+
80
+ # Type name for this class [String]
81
+ attr_reader :type
82
+ def type # :nodoc:
83
+ self.class.type
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,19 @@
1
+ module Rave
2
+ module Mixins
3
+ module TimeUtils
4
+
5
+ def time_from_json(time)
6
+ if time
7
+ time_s = time.to_s
8
+ epoch = if time_s.length > 10
9
+ "#{time_s[0, 10]}.#{time_s[10..-1]}".to_f
10
+ else
11
+ time.to_i
12
+ end
13
+ Time.at(epoch)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -1,18 +1,148 @@
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
1
+ require 'mixins/object_factory'
2
+
3
+ module Rave
4
+ module Models
5
+ # An annotation applying styling or other meta-data to a section of text.
6
+ class Annotation
7
+ include Rave::Mixins::ObjectFactory
8
+
9
+ JAVA_CLASS = "com.google.wave.api.Annotation"
10
+
11
+ # Name of the annotation type [String]
12
+ def name # :nodoc:
13
+ # If @id is defined, then put that into the type, otherwise just the type is fine.
14
+ @id ? type.sub(WILDCARD, @id) : type
15
+ end
16
+
17
+ # Value of the annotation [String]
18
+ def value # :nodoc:
19
+ @value.dup
20
+ end
21
+
22
+ # Range of characters over which the annotation applies [Range]
23
+ def range # :nodoc:
24
+ @range.dup
25
+ end
26
+
27
+ # +value+:: Value of the annotation [String]
28
+ # +range+:: Range of characters that the annotation applies to [Range]
29
+ def initialize(value, range); end
30
+ # +id+:: The non-class-dependent part of the name [String]
31
+ # +value+:: Value of the annotation [String]
32
+ # +range+:: Range of characters that the annotation applies to [Range]
33
+ def initialize(id, value, range); end
34
+ def initialize(*args) # :nodoc:
35
+ case args.length
36
+ when 3
37
+ @id, @value, @range = args
38
+ when 2
39
+ @value, @range = args
40
+ end
41
+ end
42
+
43
+ def to_json # :nodoc:
44
+ {
45
+ 'javaClass' => JAVA_CLASS,
46
+ 'name' => name,
47
+ 'value' => value,
48
+ 'range' => range,
49
+ }.to_json
50
+ end
51
+
52
+ factory_register '*' # Accept all unrecognised annotations.
53
+
54
+ # Annotation classes:
55
+
56
+ # Language selected, such as "en", "de", etc.
57
+ class Language < Annotation
58
+ factory_register 'lang'
59
+ end
60
+
61
+ # Style, acting the same as the similarly named CSS properties.
62
+ class Style < Annotation
63
+
64
+ factory_register 'style/*' # Accept all unrecognised style annotations.
65
+
66
+ class BackgroundColor < Style
67
+ factory_register 'style/backgroundColor'
68
+ end
69
+
70
+ class Color < Style
71
+ factory_register 'style/color'
72
+ end
73
+
74
+ class FontFamily < Style
75
+ factory_register 'style/fontFamily'
76
+ end
77
+
78
+ class FontSize < Style
79
+ factory_register 'style/fontSize'
80
+ end
81
+
82
+ class FontWeight < Style
83
+ factory_register 'style/fontWeight'
84
+ end
85
+
86
+ class TextDecoration < Style
87
+ factory_register 'style/textDecoration'
88
+ end
89
+
90
+ class VerticalAlign < Style
91
+ factory_register 'style/verticalAlign'
92
+ end
93
+ end
94
+
95
+ class Conversation < Annotation
96
+ factory_register 'conv/*' # Accept all unrecognised conv annotations.
97
+
98
+ class Title < Conversation
99
+ factory_register "conv/title"
100
+ end
101
+ end
102
+
103
+ # (Abstract)
104
+ class Link < Annotation
105
+ factory_register 'link/*' # Accept all unrecognised link annotations.
106
+
107
+ class Manual < Link
108
+ factory_register "link/manual"
109
+ end
110
+
111
+ class Auto < Link
112
+ factory_register "link/autoA"
113
+ end
114
+
115
+ class Wave < Link
116
+ factory_register "link/waveA"
117
+ end
118
+ end
119
+
120
+ # (Abstract)
121
+ class User < Annotation
122
+ factory_register 'user/*' # Accept all unrecognised user annotations.
123
+
124
+ # Session ID for the user annotation.
125
+ def session_id # :nodoc:
126
+ name =~ %r!/([^/]+)$!
127
+ $1
128
+ end
129
+
130
+ def initialize(session_id, value, range)
131
+ super
132
+ end
133
+
134
+ class Document < User
135
+ factory_register "user/d/*"
136
+ end
137
+
138
+ class Selection < User
139
+ factory_register "user/r/*"
140
+ end
141
+
142
+ class Focus < User
143
+ factory_register "user/e/*"
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
data/lib/models/blip.rb CHANGED
@@ -1,61 +1,305 @@
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
1
+ module Rave
2
+ module Models
3
+ # Represents a blip, containing formated text, gadgets and other elements.
4
+ # It is part of a Wavelet within a Wave.
5
+ class Blip < Component
6
+ include Rave::Mixins::TimeUtils
7
+ include Rave::Mixins::Logger
8
+
9
+ JAVA_CLASS = 'com.google.wave.api.impl.BlipData' # :nodoc:
10
+
11
+ # Version number of the contents of the blip [Integer]
12
+ def version
13
+ @version.dup
14
+ end
15
+
16
+ # Annotations on the blip [Array of Annotation]
17
+ def annotations # :nodoc:
18
+ @annotations.dup
19
+ end
20
+
21
+ # IDs of the children of this blip [Array of String]
22
+ def child_blip_ids # :nodoc:
23
+ @child_blip_ids.map { |id| id.dup }
24
+ end
25
+
26
+ # IDs (email addresses) of those who have altered this blip [Array of String]
27
+ def contributor_ids # :nodoc:
28
+ @contributor_ids.map { |id| id.dup }
29
+ end
30
+
31
+ # Elements contained within this blip [Array of Element]
32
+ def elements # :nodoc:
33
+ @elements.dup
34
+ end
35
+
36
+ # Last time the blip was altered [Time]
37
+ def last_modified_time # :nodoc:
38
+ @last_modified_time.dup
39
+ end
40
+
41
+ # ID of this blip's parent [String or nil for a root blip]
42
+ def parent_blip_id # :nodoc:
43
+ @parent_blip_id.nil? ? nil : @parent_blip_id.dup
44
+ end
45
+
46
+ # ID of the wave this blip belongs to [String]
47
+ def wave_id # :nodoc:
48
+ @wave_id.nil? ? nil : @wave_id.dup
49
+ end
50
+
51
+ # ID of the wavelet this blip belongs to [String]
52
+ def wavelet_id # :nodoc:
53
+ @wavelet_id.nil? ? nil : @wavelet_id.dup
54
+ end
55
+
56
+ # Wavelet that the blip is a part of [Wavelet]
57
+ def wavelet # :nodoc:
58
+ @context.wavelets[@wavelet_id]
59
+ end
60
+
61
+ # Wave that this blip is a part of [Wave]
62
+ def wave # :nodoc:
63
+ @context.waves[@wave_id]
64
+ end
65
+
66
+ # Blip that this Blip is a direct reply to. Will be nil if the root blip
67
+ # in a wavelet [Blip or nil for a root blip]
68
+ def parent_blip # :nodoc:
69
+ @context.blips[@parent_blip_id]
70
+ end
71
+
72
+ # Returns true if this is a root blip (no parent blip) [Boolean]
73
+ def root? # :nodoc:
74
+ @parent_blip_id.nil?
75
+ end
76
+
77
+ # Returns true if this is a leaf node (has no children). [Boolean]
78
+ def leaf? # :nodoc:
79
+ @child_blip_ids.empty?
80
+ end
81
+
82
+ # Has the blip been deleted? [Boolean]
83
+ def deleted? # :nodoc:
84
+ [:deleted, :null].include? @state
85
+ end
86
+
87
+ # Has the blip been completely destroyed? [Boolean]
88
+ def null? # :nodoc:
89
+ @state == :null
90
+ end
91
+
92
+ # Text contained in the blip [String]
93
+ def content # :nodoc:
94
+ @content.dup
95
+ end
96
+
97
+ # Users that have made a contribution to the blip [Array of User]
98
+ def contributors # :nodoc:
99
+ @contributor_ids.map { |c| @context.users[c] }
100
+ end
101
+
102
+ # Original creator of the blip [User]
103
+ def creator # :nodoc:
104
+ @context.users[@creator]
105
+ end
106
+
107
+ # List of direct children of this blip. The first one will be continuing
108
+ # the thread, others will be indented replies [Array of Blip]
109
+ def child_blips # :nodoc:
110
+ @child_blip_ids.map { |id| @context.blips[id] }
111
+ end
112
+
113
+ # Ensure that all elements within the blip are given a context.
114
+ def context=(value) # :nodoc:
115
+ super(value)
116
+ @elements.each_value { |e| e.context = value }
117
+ end
118
+
119
+ VALID_STATES = [:normal, :null, :deleted] # :nodoc: As passed to initializer in :state option.
120
+
121
+ #Options include:
122
+ # - :annotations
123
+ # - :child_blip_ids
124
+ # - :content
125
+ # - :contributors
126
+ # - :creator
127
+ # - :elements
128
+ # - :last_modified_time
129
+ # - :parent_blip_id
130
+ # - :version
131
+ # - :wave_id
132
+ # - :wavelet_id
133
+ # - :id
134
+ # - :context
135
+ # - :state
136
+ def initialize(options = {}) # :nodoc:
137
+ @annotations = options[:annotations] || []
138
+ @child_blip_ids = options[:child_blip_ids] || []
139
+ @content = options[:content] || ''
140
+ @contributor_ids = options[:contributors] || []
141
+ @creator = options[:creator] || User::NOBODY_ID
142
+ @elements = options[:elements] || {}
143
+ @last_modified_time = time_from_json(options[:last_modified_time]) || Time.now
144
+ @parent_blip_id = options[:parent_blip_id]
145
+ @version = options[:version] || -1
146
+ @wave_id = options[:wave_id]
147
+ @wavelet_id = options[:wavelet_id]
148
+ @state = options[:state] || :normal
149
+
150
+ unless VALID_STATES.include? @state
151
+ raise ArgumentError.new("Bad state #{options[:state]}. Should be one of #{VALID_STATES.join(', ')}")
152
+ end
153
+
154
+ # If the blip doesn't have a defined ID, since we just created it,
155
+ # assign a temporary, though unique, ID, based on the ID of the wavelet.
156
+ if options[:id].nil?
157
+ options[:id] = "#{GENERATED_PREFIX}_blip_#{unique_id}"
158
+ end
159
+
160
+ super(options)
161
+ end
162
+
163
+ #Returns true if an annotation with the given name exists in this blip
164
+ def has_annotation?(name)
165
+ @annotations.any? { |a| a.name == name }
166
+ end
167
+
168
+ # Adds an annotation to the Blip.
169
+ def add_annotation(annotation)
170
+ @annotations << annotation
171
+ self
172
+ end
173
+
174
+ #Creates a child blip under this blip
175
+ def create_child_blip
176
+ blip = Blip.new(:wave_id => @wave_id, :parent_blip_id => @id, :wavelet_id => @wavelet_id,
177
+ :context => @context, :contributors => [Robot.instance.id])
178
+ @context.add_operation(:type => Operation::BLIP_CREATE_CHILD, :blip_id => @id, :wave_id => @wave_id, :wavelet_id => @wavelet_id, :property => blip)
179
+ add_child_blip(blip)
180
+ blip
181
+ end
182
+
183
+ # Adds a created child blip to this blip.
184
+ def add_child_blip(blip) # :nodoc:
185
+ @child_blip_ids << blip.id
186
+ @context.add_blip(blip)
187
+ end
188
+
189
+ # INTERNAL
190
+ # Removed a child blip.
191
+ def remove_child_blip(blip) # :nodoc:
192
+ @child_blip_ids.delete(blip.id)
193
+
194
+ # Destroy oneself completely if you are no longer useful to structure.
195
+ destroy_me if deleted? and leaf? and not root?
196
+ end
197
+
198
+ # Delete this blip from its wavelet.
199
+ # Returns the blip id.
200
+ def delete
201
+ if deleted?
202
+ logger.warning("Attempt to delete blip that has already been deleted: #{id}")
203
+ elsif root?
204
+ logger.warning("Attempt to delete root blip: #{id}")
205
+ else
206
+ @context.add_operation(:type => Operation::BLIP_DELETE,
207
+ :blip_id => @id, :wave_id => @wave_id, :wavelet_id => @wavelet_id)
208
+ delete_me
209
+ end
210
+ end
211
+
212
+ # Convert to string.
213
+ def to_s
214
+ str = @content.gsub(/\n/, "\\n")
215
+ str = str.length > 24 ? "#{str[0..20]}..." : str
216
+
217
+ str = case @state
218
+ when :normal
219
+ "#{contributors.join(',')}:#{str}"
220
+ when :deleted
221
+ '<DELETED>'
222
+ when :null
223
+ '<NULL>'
224
+ end
225
+
226
+ "#{super}:#{str}"
227
+ end
228
+
229
+ # *INTERNAL*
230
+ # Write out a formatted block of text showing the blip and its descendants.
231
+ def print_structure(indent = 0) # :nodoc:
232
+ str = "#{' ' * indent}#{to_s}\n"
233
+
234
+ unless @child_blip_ids.empty?
235
+ # Move the first blip to the end, since it will be looked at last.
236
+ blip_ids = @child_blip_ids
237
+ blip_ids.push(blip_ids.shift)
238
+
239
+ # All children, except the first, should be indented.
240
+ blip_ids.each_with_index do |blip_id, index|
241
+ is_last_blip = (index == blip_ids.size - 1)
242
+
243
+ # All except the last one should be indented again.
244
+ ind = is_last_blip ? indent : indent + 1
245
+ blip = @context.blips[blip_id]
246
+ if blip
247
+ str << blip.print_structure(ind)
248
+ else
249
+ str << "#{' ' * ind}<undefined-blip>:#{blip_id}\n"
250
+ end
251
+
252
+ str << "\n" unless is_last_blip # Gap between reply chains.
253
+ end
254
+ end
255
+
256
+ str
257
+ end
258
+
259
+ # *INTERNAL*
260
+ # Convert to json for sending in an operation. We should never need to
261
+ # send more data than this, although blips we receive will have more data.
262
+ def to_json # :nodoc:
263
+ {
264
+ 'blipId' => @id,
265
+ 'javaClass' => JAVA_CLASS,
266
+ 'waveId' => @wave_id,
267
+ 'waveletId' => @wavelet_id
268
+ }.to_json
269
+ end
270
+
271
+ # *INTERNAL*
272
+ # Delete the blip or, if appropriate, destroy it instead.
273
+ def delete_me(allow_destroy = true) # :nodoc:
274
+ raise "Can't delete root blip" if root?
275
+
276
+ if leaf? and allow_destroy
277
+ destroy_me
278
+ else
279
+ # Blip is marked as deleted, but stays in place to maintain structure.
280
+ @state = :deleted
281
+ @content = ''
282
+ end
283
+
284
+ @id
285
+ end
286
+
287
+ protected
288
+ # *INTERNAL*
289
+ # Remove the blip entirely, leaving it null.
290
+ def destroy_me # :nodoc:
291
+ raise "Can't destroy root blip" if root?
292
+ raise "Can't destroy non-leaf blip" unless leaf?
293
+
294
+ # Remove the blip entirely to the realm of oblivion.
295
+ parent_blip.remove_child_blip(self)
296
+ @parent_blip_id = nil
297
+ @context.remove_blip(self)
298
+ @state = :null
299
+ @content = ''
300
+
301
+ @id
302
+ end
303
+ end
304
+ end
305
+ end