rave 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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