streams 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. data/lib/streams.rb +3 -1100
  2. data/lib/streams/activitystreams.rb +1106 -0
  3. metadata +2 -1
@@ -0,0 +1,1106 @@
1
+ ##############################################
2
+ # Author: James M Snell (jasnell@gmail.com) #
3
+ # License: Apache v2.0 #
4
+ # #
5
+ # A simple Activity Streams implementation. #
6
+ # (forgive me... my ruby's a bit rusty) #
7
+ # #
8
+ # example use: #
9
+ # require 'activitystreams' #
10
+ # include ActivityStreams #
11
+ # #
12
+ # STDOUT << activity { #
13
+ # verb :post #
14
+ # actor person { #
15
+ # display_name 'James' #
16
+ # } #
17
+ # object note { #
18
+ # content 'This is a test' #
19
+ # } #
20
+ # } #
21
+ # #
22
+ ##############################################
23
+ require 'json'
24
+ require "time"
25
+ require 'addressable/uri'
26
+ require 'base64'
27
+ require 'zlib'
28
+ require 'i18n'
29
+ require 'mime/types'
30
+
31
+ class Hash
32
+ # allows a Hash to be used in place of an
33
+ # ASObj ... we don't necessarily want a
34
+ # mixin on this, so just set a property
35
+ def __object_type= ot
36
+ @object_type = ot
37
+ end
38
+
39
+ def __object_type
40
+ @object_type
41
+ end
42
+
43
+ end
44
+
45
+ class Object
46
+
47
+ # Tests whether the method exists. Unlike the std method, however,
48
+ # does not throw a NameError if it doesn't. Just returns false
49
+ def method? sym
50
+ method sym rescue false
51
+ end
52
+
53
+ # Tests to see if the Object is one of several possible types
54
+ # like is_a? but accepting multiple arguments
55
+ def one_of_type? *args
56
+ args.any? {|x| is_a? x}
57
+ end
58
+
59
+ end
60
+
61
+ module ActivityStreams
62
+
63
+ # Provides a number of generally useful utilities methods
64
+ module Matchers
65
+ include Addressable
66
+
67
+ # true if m validates as a isegment-nz-nc as defined by RFC 3987.
68
+ # The activity streams spec requires that verbs and object types
69
+ # MUST either be isegment-nz-nc or absolute IRI productions.
70
+ def is_token? m
71
+ return false if m == nil
72
+ m = m.to_s
73
+ (m =~ /^([a-zA-Z0-9\-\.\_\~]|\%[0-9a-fA-F]{2}|[\!\$\&\'\(\)\*\+\,\;\=\@])+$/) != nil
74
+ end
75
+
76
+ # true if m is parseable as an IRI and is absolute
77
+ def is_absolute_iri? m
78
+ URI.parse(m).absolute? rescue false
79
+ end
80
+
81
+ # true if m is a valid verb
82
+ def is_verb? m
83
+ is_token?(m) || is_absolute_iri?(m)
84
+ end
85
+
86
+ # true if m is a valid MIME Media Type
87
+ def is_mime_type? m
88
+ MIME::Type.new m rescue false
89
+ end
90
+
91
+ # true if m is a valid RFC 4646 Lang tag
92
+ def is_lang_tag? m
93
+ I18n::Locale::Tag::Rfc4646.tag m rescue false
94
+ end
95
+
96
+ # utility method providing the basic structure of validation
97
+ # checkers for the various fields...
98
+ def checker &block
99
+ ->(v) do
100
+ raise ArgumentError unless block.call v
101
+ v
102
+ end
103
+ end
104
+
105
+ module_function :checker, :is_token?, :is_verb?, :is_absolute_iri?, :is_mime_type?, :is_lang_tag?
106
+
107
+ end
108
+
109
+ # Defines the basic functions for the Activity Streams DSL
110
+ module Makers
111
+
112
+ # Create a new object by copying another.
113
+ # A list of properties to omit from the new
114
+ # copy can be provided a variable arguments.
115
+ # For instance, if you have an existing activity
116
+ # object and wish to copy everything but the
117
+ # current verb and actor properties, you could
118
+ # call new_act = copy_from(old_act, :verb, :actor) { ... }
119
+ def copy_from(other,*without,&block)
120
+ ASObj.from other,*without,&block
121
+ end
122
+
123
+ # Create a new Activity Streams Object
124
+ def object(object_type=nil,&block)
125
+ ASObj.generate object_type,&block
126
+ end
127
+
128
+ # Create a new Media Link
129
+ def media_link &block
130
+ ASObj.generate :media_link,true,&block
131
+ end
132
+
133
+ # Create a new Collection Object
134
+ def collection include_object_type=false, &block
135
+ ASObj.generate :collection, !include_object_type, &block
136
+ end
137
+
138
+ # Create a new Activity Object
139
+ def activity include_object_type=false, &block
140
+ ASObj.generate :activity, !include_object_type, &block
141
+ end
142
+
143
+ # Utility method for returning the current time
144
+ def now
145
+ Time.now.utc
146
+ end
147
+
148
+ # For the remain object types from the Activity Streams Schema
149
+ # iterate through them and create a factory method for each
150
+ [ :alert,
151
+ :application,
152
+ :article,
153
+ :audio,
154
+ :badge,
155
+ :binary,
156
+ :bookmark,
157
+ :comment,
158
+ :device,
159
+ :event,
160
+ :file,
161
+ :game,
162
+ :group,
163
+ :image,
164
+ :issue,
165
+ :job,
166
+ :note,
167
+ :offer,
168
+ :organization,
169
+ :page,
170
+ :permission,
171
+ :person,
172
+ :place,
173
+ :process,
174
+ :product,
175
+ :question,
176
+ :review,
177
+ :role,
178
+ :service,
179
+ :task,
180
+ :team,
181
+ :video
182
+ ].each {|o|
183
+ module_eval "def #{o}(&block) object :#{o}, &block end"
184
+ }
185
+ end
186
+
187
+ def checker &block
188
+ Matchers.checker &block
189
+ end
190
+
191
+ module_function :checker
192
+
193
+ include Makers, Matchers
194
+
195
+ # Represents a basic Activity Streams Object...
196
+ # Instances, once created, are immutable for
197
+ # all the core properties. The object maintains
198
+ # an internal hash and performs basic input
199
+ # validation to ensure that the built object
200
+ # conforms to the basic requirements of the
201
+ # Activity Streams specifications. Specific
202
+ # validation requirements are defined by the
203
+ # Spec module associated with the object_type
204
+ # specified for the ASObj instance
205
+ class ASObj
206
+ include Makers
207
+
208
+ def initialize object_type=nil
209
+ @_ = {}
210
+ @_.__object_type = object_type
211
+ @object_type = object_type
212
+ extend SPECS[object_type] || SPECS[nil]
213
+ strict
214
+ end
215
+
216
+ # Puts this ASObj into lenient validation mode
217
+ def lenient
218
+ @lenient = true
219
+ end
220
+
221
+ # Puts this ASObj into strict validation mode
222
+ def strict
223
+ @lenient = false
224
+ end
225
+
226
+ # Tells this ASObj to generate formatted JSON output
227
+ def pretty
228
+ @pretty = true
229
+ end
230
+
231
+ # true if this ASObj has been configured to generate formatted JSON output
232
+ def pretty?
233
+ @pretty || false
234
+ end
235
+
236
+ # true if this ASObj is operating in lenient mode
237
+ def lenient?
238
+ @lenient
239
+ end
240
+
241
+ # true if this ASObj is operating in strict mode
242
+ def strict?
243
+ !lenient?
244
+ end
245
+
246
+ # the internal object type identifier for this ASObj
247
+ def __object_type
248
+ @object_type
249
+ end
250
+
251
+ # return a frozen copy of the internal hash
252
+ def finish
253
+ @_.dup.freeze
254
+ end
255
+
256
+ # write this thing out, the param must repond to the << operator for appending
257
+ def append_to out
258
+ raise ArgumentError unless out.respond_to?:<<
259
+ out << to_s
260
+ end
261
+ alias :>> append_to
262
+
263
+ # force pretty printing
264
+ def pretty_to out
265
+ out << JSON.pretty_generate(@_)
266
+ end
267
+
268
+ # generates a copy of this object
269
+ def copy_of *without, &block
270
+ ASObj.from self, *without, &block
271
+ end
272
+
273
+ def to_s
274
+ pretty? ? JSON.pretty_generate(@_) : @_.to_json
275
+ end
276
+
277
+ def [] k
278
+ @_[send("alias_for_#{k}".to_sym) || k]
279
+ end
280
+ protected :[]
281
+
282
+ # Sets a value on the internal hash without any type checking
283
+ # if v is an instance of ASObj, finish will be called
284
+ # to get the frozen hash ...
285
+ def set k,v
286
+ key = k.to_s.to_sym
287
+ v = (v.is_a?(ASObj) ? v.finish : v) unless v == nil
288
+ @_[key] = v unless v == nil
289
+ @_.delete key if v == nil
290
+ end
291
+ alias :[]= :set
292
+
293
+ def freeze
294
+ @_.freeze
295
+ super
296
+ end
297
+
298
+ # Within the generator, any method call that has exactly one
299
+ # parameter will be turned into a member of the underlying hash
300
+ def property m, *args, &block
301
+
302
+ # Return nil if it's looking for an unknown alias_for_* method
303
+ return nil if m =~ /^alias\_for\_.*/
304
+
305
+ # Special magic... if an unknown ? method is called, it's treated
306
+ # as a check against the internal hash to see if the given field
307
+ # has been set. For instance, priority? will check to see if the
308
+ # priority field has been set
309
+ return @_.has_key?(m.to_s[0...-1].to_sym) if m =~ /.*\?/
310
+
311
+ # Is it an unknown to_ method? e.g. to_ary ... if so, fall through to default
312
+ if m =~ /^to\_.*/
313
+ super
314
+ return
315
+ end
316
+
317
+ # Once we get past the special cases, check to see if we have the
318
+ # expected number of arguments.
319
+ if !args.length.within?(1..2)
320
+ raise NoMethodError
321
+ return
322
+ end
323
+
324
+ # Now we start to figure out the exact value to set
325
+ transform, alias_for, checker = [:transform_,:alias_for_,:check_].map {|x| "#{x}#{m}".to_sym }
326
+
327
+ v = args[0]
328
+
329
+ # First, if the value given is a ASObj, call finish on it
330
+ v = (v.is_a?(ASObj) ? v.finish : v) unless v == nil
331
+
332
+ # If it's an Enumerable, but not a Hash, convert to an Array using Map,
333
+ # If each of the member items are ASObj's call finish.
334
+ v = v.map {|i| i.is_a?(ASObj) ? i.finish : i } if v.is_a?(Enumerable) && !v.is_a?(Hash)
335
+
336
+ # If the value is a Time object, let's make sure it's ISO 8601
337
+ v = v.iso8601 if v.is_a? Time
338
+
339
+ # Finally, do the object_type specific transform, if any
340
+ # note, this could potentially undo the previous checks if
341
+ # the transform provided by a given spec module isn't well
342
+ # behaved. that's ok tho, we'll trust that it's doing the
343
+ # right thing and just move on ... we're going to be validating
344
+ # next anyway
345
+ v = send transform, v if method? transform
346
+
347
+ # Now let's do validation... unless lenient is set
348
+ if !args[1] && strict?
349
+ ok = method?(checker) ? send(checker,v) : missing_check(v)
350
+ raise ArgumentError unless ok
351
+ end
352
+ m = send alias_for if method? alias_for
353
+ @_[m] = v unless v == nil
354
+ @_.delete m if v == nil
355
+ end
356
+ alias :method_missing :property
357
+
358
+ end
359
+
360
+ class << ASObj
361
+
362
+ # Performs the actual work of creating an ASObj and executing
363
+ # the associated block to build it up, then freezing the
364
+ # ASObj before returning it
365
+ def generate object_type=nil, do_not_set_object_type=false, &block
366
+ m = ASObj.new object_type
367
+ m[:objectType] = object_type unless do_not_set_object_type
368
+ m.instance_eval &block unless not block_given?
369
+ m.freeze
370
+ end
371
+
372
+ # Creates a new ASObj by copying from another one
373
+ def from other, *without, &block
374
+ raise ArgumentError unless other.one_of_type?(ASObj,Hash)
375
+ m = ASObj.new other.__object_type
376
+ m.pretty if other.pretty?
377
+ m.lenient if other.lenient?
378
+ other.finish.each_pair {|k,y| m[k] = y unless without.include? k }
379
+ m.instance_eval &block unless not block_given?
380
+ m.freeze
381
+ end
382
+
383
+ end
384
+
385
+ # The base module for all Validation Spec Modules.. these
386
+ # define the requirements for the various Activity Streams
387
+ # object types
388
+ module Spec
389
+
390
+ # by default, allow all values if a specific check hasn't been provided
391
+ # Spec modules can override this behavior by defining their own missing_check
392
+ def missing_check v
393
+ true
394
+ end
395
+
396
+ # Defines the various utility methods used to build Spec modules
397
+ module Defs
398
+
399
+ # Maps an input symbol to a property name in the hash
400
+ def def_alias sym, name
401
+ define_method("alias_for_#{sym}".to_sym) {
402
+ name
403
+ } if name
404
+ module_function "alias_for_#{sym}".to_sym
405
+ end
406
+
407
+ # Defines the method for validating the value of a
408
+ # specific property.
409
+ def def_checker sym, &block
410
+ sym = "check_#{sym}".to_sym
411
+ define_method sym,&block
412
+ module_function sym
413
+ end
414
+
415
+ # Defines a transform for the value of a specific property
416
+ def def_transform sym, &block
417
+ sym = "transform_#{sym}".to_sym
418
+ if block_given?
419
+ define_method sym,&block
420
+ else
421
+ define_method(sym) {|v|v} # just return self if no transform defined
422
+ end
423
+ module_function sym
424
+ end
425
+
426
+ # Mark def_alias, def_checker and def_transform as private
427
+ # these should only be called from within the Defs module
428
+ private :def_alias, :def_checker, :def_transform
429
+
430
+ # Define a property as being an absolute IRI
431
+ def def_absolute_iri sym, name=nil
432
+ def_transform(sym) {|v|
433
+ next nil if v == nil
434
+ Addressable::URI.parse(v)
435
+ }
436
+ def_checker(sym) { |v|
437
+ # v must be an absolute IRI
438
+ !v || is_absolute_iri?(v)
439
+ }
440
+ def_alias sym, name if name
441
+ end
442
+
443
+ # Define a property as being an ISO 8601 DateTime
444
+ def def_date_time sym, name=nil
445
+ def_transform(sym) {|v|
446
+ next v if v == nil || v.is_a?(Time)
447
+ Time.parse(v.to_s) rescue v
448
+ }
449
+ def_checker(sym) { |v|
450
+ # v must be parseable as a time
451
+ next true if v == nil || v.is_a?(Time)
452
+ Time.parse(v.to_s) rescue next false
453
+ true
454
+ }
455
+ def_alias sym, name if name
456
+ end
457
+
458
+ # Define a property as being an IRI ... does not have to be absolute
459
+ def def_iri sym, name=nil
460
+ def_transform(sym) {|v|
461
+ next nil if v == nill
462
+ Addressable::URI.parse(v)}
463
+ def_checker(sym) { |v|
464
+ # v must be parseable as a URI
465
+ !v || Addressable::URI.parse(v) rescue false
466
+ }
467
+ def_alias sym, name if name
468
+ end
469
+
470
+ # Define a property as being a string, an additional block
471
+ # can be passed in to perform additional checking (e.g. regex matching, etc)
472
+ def def_string sym, name=nil, &block
473
+ def_transform(sym) {|v|
474
+ next nil if v == nil
475
+ v.to_s
476
+ }
477
+ def_checker(sym) { |v|
478
+ # v will be converted to a string, then checked against the optional
479
+ # block... if the block returns false, raise a validation error
480
+ next true if v == nil
481
+ next block.call(v.to_s) if block_given?
482
+ true
483
+ }
484
+ def_alias sym, name if name
485
+ end
486
+
487
+ # Define a property as being an ASObj.
488
+ def def_object sym, object_type=nil, name=nil
489
+ def_transform(sym)
490
+ def_checker(sym) { |v|
491
+ next true if v == nil
492
+ # v must be an instance of the given object_type
493
+ if object_type
494
+ next false if v.__object_type != object_type
495
+ end
496
+ # right now this is pretty strict... we only allow Hash or ASObj
497
+ # instances to be passed. TODO: see if we can relax this to enable
498
+ # more flexible duck typing ...
499
+ v.one_of_type? Hash, ASObj
500
+ }
501
+ def_alias sym, name if name
502
+ def_property(sym, object_type, name) if object_type
503
+ end
504
+
505
+ # Define a property as being an Array of ASObj's
506
+ def def_object_array sym, object_type=nil, name=nil
507
+ def_alias sym, name if name
508
+ def_transform(sym) {|v|
509
+ next nil if v == nil
510
+ orig = self[sym]
511
+ if v.is_a?(Array)
512
+ next orig ? orig + v : v
513
+ end
514
+ orig ? orig << v : [v]
515
+ }
516
+ def_checker(sym) { |v|
517
+ next true if v == nil
518
+ # v must be either an array or enumerable and each item
519
+ # must be either a Hash or ASObj that matches the given
520
+ # object_type, if any
521
+ next false unless (v.one_of_type?(Array, Enumerable) && !v.is_a?(Hash))
522
+ v.each {|x|
523
+ return false unless x.one_of_type? ASObj, Hash
524
+ return false if (object_type && x.__object_type != object_type)
525
+ }
526
+ true
527
+ }
528
+ end
529
+
530
+ # Define a property as being an Array of Strings, an additional
531
+ # block can be passed to perform additional checking
532
+ def def_string_array sym, name=nil, &block
533
+ def_transform(sym) {|v|
534
+ next nil if v == nil
535
+ orig = self[sym]
536
+ if v.one_of_type? Array, Enumerable
537
+ add = v.map {|x| x.to_s}
538
+ next orig ? orig + add : add
539
+ end
540
+ orig ? orig << v.to_s : [v.to_s]
541
+ }
542
+ def_checker(sym) { |v|
543
+ next true if v == nil
544
+ next false unless (v.one_of_type?(Array, Enumerable) && !v.is_a?(Hash))
545
+ v.each {|x|
546
+ return false unless block.call(x)
547
+ } if block_given?
548
+ true
549
+ }
550
+ def_alias sym, name if name
551
+ end
552
+
553
+ def def_boolean sym, name=nil
554
+ def_transform(sym) {|v|
555
+ next false if v == nil
556
+ v ? true : false
557
+ }
558
+ def_checker(sym) { |v|
559
+ v.one_of_type? TrueClass, FalseClass
560
+ }
561
+ def_alias sym, name if name
562
+
563
+ module_eval %Q/def #{sym}() property(:'#{sym}', true) end/
564
+ module_eval %Q/def not_#{sym}() property(:'#{sym}', false) end/
565
+ end
566
+
567
+ # Define a property as being a Numeric
568
+ def def_numeric sym, name=nil, &block
569
+ def_checker(sym) { |v|
570
+ next true if v == nil
571
+ return false unless v.is_a? Numeric
572
+ if block_given?
573
+ next false unless block.call v
574
+ end
575
+ true
576
+ }
577
+ def_alias sym, name if name
578
+ end
579
+
580
+ # Define a property as being a non-negative fixnum
581
+ def def_non_negative_int sym, name=nil
582
+ def_numeric(sym, name) {|v|
583
+ next false unless (v.is_a?(Fixnum) && v >= 0)
584
+ true
585
+ }
586
+ end
587
+
588
+ # Define a property as being a float with a bounded range
589
+ def def_bound_float sym, range, name=nil
590
+ def_numeric(sym, name) {|v|
591
+ next false if (range.respond_to?(:include?) && !range.include?(v))
592
+ true
593
+ }
594
+ end
595
+
596
+ def def_property sym, type=nil, name=nil
597
+ sym = sym.to_s
598
+ module_eval %Q/
599
+ def #{sym} &block
600
+ self[:#{name || sym}] = ASObj.generate(:#{type},true,&block)
601
+ end
602
+ /
603
+ end
604
+ private :def_property
605
+ end
606
+
607
+ # Ensure the the Defs module is included in all spec modules...
608
+ extend Defs
609
+ def self.included(other)
610
+ other.extend Defs
611
+ end
612
+
613
+ end
614
+
615
+ # The base spec for all ASObj's
616
+ module ObjectSpec
617
+ include Spec
618
+ def_string :content
619
+ def_string :display_name, :displayName
620
+ def_string :object_type, :objectType
621
+ def_string :summary
622
+ def_string :aka, :alias
623
+ def_date_time :updated
624
+ def_date_time :published
625
+ def_date_time :start_time, :'start-time'
626
+ def_date_time :end_time, :'end-time'
627
+ def_object :links, :links
628
+ def_object :author
629
+ def_object :img, :media_link, :image
630
+ def_object :source
631
+ def_object :location, :place
632
+ def_object :mood, :mood
633
+ def_bound_float :rating, 0.0..5.0
634
+ def_absolute_iri :id
635
+ def_iri :url
636
+ def_object_array :attachments
637
+ def_object_array :in_reply_to, nil, :inReplyTo
638
+
639
+ check = ->(x){ is_absolute_iri? x }
640
+ def_string_array :downstream_duplicates, :downstreamDuplicates, &check
641
+ def_string_array :upstream_duplicates, :upstreamDuplicates, &check
642
+
643
+ def attachment m, &block
644
+ property :attachments, m, &block
645
+ end
646
+
647
+ def downstream_duplicate m, &block
648
+ property :downstream_duplicates, m, &block
649
+ end
650
+
651
+ def upstream_duplicate m, &block
652
+ property :upstream_duplicates, m, &block
653
+ end
654
+
655
+ # Basic support for external vocabularies..
656
+ # Developers will have to register their own
657
+ # spec modules for these, but we at least
658
+ # provide the constructor methods
659
+ def ext_vocab sym, &block
660
+ self[sym] = ASObj.generate(sym,true,&block)
661
+ end
662
+ [:schema_org, :ld, :dc, :odata, :opengraph].each do |x|
663
+ module_eval "def #{x}(&block) ext_vocab(:#{x},&block) end"
664
+ end
665
+
666
+ # ensure that all spec object include the Defs module...
667
+ include Defs
668
+ def self.included(other)
669
+ other.extend Defs
670
+ other.module_exec {
671
+ def self.included(o)
672
+ o.extend Defs
673
+ end
674
+ }
675
+ end
676
+
677
+ end
678
+
679
+ module ActivitySpec
680
+ include ObjectSpec
681
+ def_string :verb do |v| is_verb? v end
682
+ def_string :content
683
+ def_string :title
684
+ def_object :icon, :media_link
685
+ def_object :generator
686
+ def_object :actor
687
+ def_object :target
688
+ def_object :obj, nil, :object
689
+ def_object :provider
690
+ def_object :context
691
+ def_object :result
692
+ def_object_array :to
693
+ def_object_array :cc
694
+ def_object_array :bto
695
+ def_object_array :bcc
696
+ def_bound_float :priority, 0.0..1.0
697
+ end
698
+
699
+ module MediaLinkSpec
700
+ include Spec
701
+ def_absolute_iri :url
702
+ def_non_negative_int :duration
703
+ def_non_negative_int :width
704
+ def_non_negative_int :height
705
+ end
706
+
707
+ module MoodSpec
708
+ include Spec
709
+ def_string :display_name, :displayName
710
+ def_object :img, :mediaLink, :image
711
+ end
712
+
713
+ module AddressSpec
714
+ include Spec
715
+ def_string :formatted
716
+ def_string :street_address, :streetAddress
717
+ def_string :locality
718
+ def_string :region
719
+ def_string :postal_code, :postalCode
720
+ def_string :country
721
+ end
722
+
723
+ module PositionSpec
724
+ include Spec
725
+ def_numeric :altitude
726
+ def_bound_float :longitude, -180.00..180.00
727
+ def_bound_float :latitude, -90.00..90.00
728
+ end
729
+
730
+ module PlaceSpec
731
+ include ObjectSpec
732
+ def_object :position, :position
733
+ def_object :address, :address
734
+ end
735
+
736
+ module CollectionSpec
737
+ include ObjectSpec
738
+ def_date_time :items_after, :itemsAfter
739
+ def_date_time :items_before, :itemsBefore
740
+ def_non_negative_int :items_per_page, :itemsPerPage
741
+ def_non_negative_int :start_index, :startIndex
742
+ def_non_negative_int :total_items, :totalItems
743
+ def_object_array :items
744
+ def_string_array :object_types, :objectTypes
745
+
746
+ def item m, &block
747
+ property :items, m, &block
748
+ end
749
+
750
+ end
751
+
752
+ module AVSpec
753
+ include ObjectSpec
754
+ def_string :embed_code, :embedCode
755
+ def_object :stream, :media_link
756
+ end
757
+
758
+ module FileSpec
759
+ include ObjectSpec
760
+ def_string :mime_type, :mimeType do |x| is_mime_type? x end
761
+ def_string :md5
762
+ def_absolute_iri :file_url, :fileUrl
763
+ end
764
+
765
+ module BinarySpec
766
+ include FileSpec
767
+ def_string :compression
768
+ def_string :data
769
+ def_non_negative_int :length
770
+
771
+ def init_hasher hash
772
+ require 'Digest'
773
+ hash_name = "#{hash.to_s.upcase}"
774
+ Digest.module_eval "#{hash_name}.new"
775
+ rescue LoadError
776
+ raise ArgumentError.new("Invalid Hash [#{hash}]")
777
+ end
778
+
779
+ def do_compression data, compress, level
780
+ case compress
781
+ when nil then return data
782
+ when :deflate
783
+ data = Zlib::Deflate.deflate(data,level)
784
+ when :gzip
785
+ data = IO.pipe { |r,w|
786
+ gzip = Zlib::GzipWriter.new(w,level)
787
+ gzip.write data
788
+ gzip.close
789
+ r.read
790
+ }
791
+ else raise ArgumentError
792
+ end
793
+ data
794
+ end
795
+
796
+ private :init_hasher, :do_compression
797
+
798
+ # Specify the data for the Binary object. The src must either be an IO object
799
+ # or a string containing a file path and name or an ArgumentError will be raised.
800
+ # Deflate compression by default, level 9, pass in :gzip to use Gzip compression
801
+ # or nil to disable compression entirely. The length and compression fields will
802
+ # automatically be set. This method will NOT close the src IO when it's done, you'll
803
+ # need to handle that yourself. Currently this doesn't do any error handling
804
+ # on the IO read. Also, it currently reads the entire IO stream first,
805
+ # buffers it into memory, then compresses before base64 encoding...
806
+ def data(src, options={:compress=>:deflate, :level=>9, :hash=>:md5})
807
+ compress = options.fetch :compress, :deflate
808
+ level = options.fetch :level, 9
809
+ hash = options.fetch :hash, :md5
810
+
811
+ if src.is_a? String
812
+ File.open(src, 'r') {|f| data f, options }
813
+ else
814
+ raise ArgumentError unless src.is_a? IO
815
+
816
+ # Optionally generate a hash over the data as it is read
817
+ if hash
818
+ hasher = init_hasher(hash)
819
+ d = src.read {|block| hasher.update block }
820
+ self[hash] = hasher.hexdigest
821
+ else
822
+ d = src.read
823
+ end
824
+
825
+ # Set the uncompressed length of the data in octets
826
+ self[:length] = d.length
827
+
828
+ # Apply compression if necessary
829
+ if compress
830
+ d = do_compression d, compress, level
831
+ self[:compress] = compress
832
+ end
833
+
834
+ # Set the data
835
+ self[:data] = Base64.urlsafe_encode64(d)
836
+ end
837
+ end
838
+
839
+ end
840
+
841
+ module EventSpec
842
+ include ObjectSpec
843
+ def_object :attended_by, :collection, :attendedBy
844
+ def_object :attending, :collection
845
+ def_object :invited, :collection
846
+ def_object :maybe_attending, :collection, :maybeAttending
847
+ def_object :not_attended_by, :collection, :notAttendedBy
848
+ def_object :not_attending, :collection, :notAttending
849
+ end
850
+
851
+ module IssueSpec
852
+ include ObjectSpec
853
+ def_string_array(:types) {|v| is_absolute_iri? v }
854
+
855
+ def type m, &block
856
+ property :types, m, &block
857
+ end
858
+ end
859
+
860
+ module PermissionsSpec
861
+ include ObjectSpec
862
+ def_object :scope
863
+ def_string_array :actions
864
+
865
+ def action m, &block
866
+ property :actions, m, &block
867
+ end
868
+ end
869
+
870
+ module RGSpec # For "role" and "group" objects
871
+ include ObjectSpec
872
+ def_object :members, :collection
873
+ end
874
+
875
+ module TaskSpec
876
+ include ActivitySpec
877
+ def_date_time :by
878
+ def_object_array :prerequisites, :task
879
+ def_object_array :supersedes, :task
880
+
881
+ def prerequisite m, &block
882
+ property :prerequisites, m, &block
883
+ end
884
+
885
+ def supersede m, &block
886
+ property :supersedes, m, &block
887
+ end
888
+
889
+ end
890
+
891
+ module ImageSpec
892
+ include ObjectSpec
893
+ def_object :full_image, :media_link, :fullImage
894
+ end
895
+
896
+ module BookmarkSpec
897
+ include ObjectSpec
898
+ def_absolute_iri :target_url, :targetUrl
899
+ end
900
+
901
+ module LinkSpec
902
+ include ObjectSpec
903
+ def_absolute_iri :href
904
+ def_string :title
905
+ def_string :hreflang do |x| is_lang_tag? x end # must be a RFC 4646 tag
906
+ def_string :type do |x| is_mime_type? x end # must be a valid MIME Media Type
907
+ end
908
+
909
+ module LinksSpec
910
+ include Spec
911
+ # Require that all properties on the Links spec are link objects
912
+ def missing_check v
913
+ v.one_of_type? Hash, LinkSpec
914
+ end
915
+
916
+ def link rel, include_object_type=false, &block
917
+ self[rel.to_sym] = ASObj.generate :link, !include_object_type, &block
918
+ end
919
+
920
+ def link_with_object_type rel, &block
921
+ link rel, true, &block
922
+ end
923
+ end
924
+
925
+ # Collect the various Specs and map to their respective object types
926
+ SPECS = {
927
+ nil => ObjectSpec,
928
+ :activity => ActivitySpec,
929
+ :media_link => MediaLinkSpec,
930
+ :mood => MoodSpec,
931
+ :address => AddressSpec,
932
+ :place => PlaceSpec,
933
+ :position => PositionSpec,
934
+ :collection => CollectionSpec,
935
+ :audio => AVSpec,
936
+ :video => AVSpec,
937
+ :binary => BinarySpec,
938
+ :file => FileSpec,
939
+ :event => EventSpec,
940
+ :issue => IssueSpec,
941
+ :permission => PermissionsSpec,
942
+ :role => RGSpec,
943
+ :group => RGSpec,
944
+ :task => TaskSpec,
945
+ :product => ImageSpec,
946
+ :image => ImageSpec,
947
+ :link => LinkSpec,
948
+ :links => LinksSpec
949
+ }
950
+
951
+ # override or add a new spec... be careful here.. the existing
952
+ # spec definitions can be overridden
953
+ def add_spec sym, spec
954
+ SPECS[sym] = spec
955
+ end
956
+
957
+ # create a new Spec module
958
+ def spec *specs, &block
959
+ o = Module.new.extend Spec, Spec::Defs, *specs
960
+ o.module_exec &block
961
+ o
962
+ end
963
+
964
+ # create a new Spec module based on ObjectSpec
965
+ def object_spec *specs, &block
966
+ spec ObjectSpec, *specs, &block
967
+ end
968
+
969
+ # define the template method as an alias to lambda
970
+ alias :template :lambda
971
+
972
+ module_function :add_spec, :spec, :object_spec
973
+
974
+ # syntactic sugar
975
+ LENIENT, STRICT = true, false
976
+
977
+ # basic priorities...
978
+ HIGHEST, HIGH, MEDIUM, NORMAL, LOW, LOWEST, NONE = 1.0, 0.75, 0.50, 0.50, 0.25, 0.00, 0.00
979
+
980
+ # Provide additional , currently experimental object types and features
981
+ # These may change at any time...
982
+ module Experimental
983
+ extend ActivityStreams
984
+
985
+ ANY = :'*'
986
+
987
+ def verb_object &block
988
+ ASObj.generate :verb,false,&block
989
+ end
990
+
991
+ # Experimental!! May change.. see http://goo.gl/x2XZl
992
+ module VerbSpec
993
+ include ObjectSpec
994
+ verb_check = ->(x){is_verb? x}
995
+ def_string :value, &verb_check
996
+ def_string_array :hypernyms, &verb_check
997
+ def_string_array :synonyms, &verb_check
998
+ def_object_array :objects, :object_combination
999
+
1000
+ def combo &block
1001
+ ASObj.generate :object_combination,true,&block
1002
+ end
1003
+
1004
+ def hypernym x
1005
+ property :hypernyms, x
1006
+ end
1007
+
1008
+ def synonym x
1009
+ property :synonyms, x
1010
+ end
1011
+
1012
+ def obj x, &block
1013
+ property :foos, x, &block
1014
+ end
1015
+ end
1016
+
1017
+ module ObjectCombinationSpec
1018
+ include Spec
1019
+ def_string :actor
1020
+ def_string :obj, :object
1021
+ def_string :target
1022
+ def_boolean :target_required, :targetRequired
1023
+ def_object :templates, :object_templates
1024
+
1025
+ def target t, required=false, &block
1026
+ property :target, t, &block
1027
+ target_required if required
1028
+ end
1029
+
1030
+ end
1031
+
1032
+ module ObjectTemplatesSpec
1033
+ include Spec
1034
+ def missing_checker v
1035
+ v.is_a? String
1036
+ end
1037
+ end
1038
+
1039
+ add_spec :verb, VerbSpec
1040
+ add_spec :object_combination, ObjectCombinationSpec
1041
+ add_spec :object_templates, ObjectTemplatesSpec
1042
+
1043
+ end # END EXPERIMENTAL MODULE
1044
+
1045
+ end
1046
+
1047
+ # some syntactic sugar for Fixnums... useful for
1048
+ # working with Time .. e.g. updated now - 1.week #updated one week ago
1049
+ class Fixnum
1050
+
1051
+ # true if this number is within the given range
1052
+ def within? r
1053
+ raise ArgumentError if not r.is_a?Range
1054
+ r.include? self
1055
+ end unless method_defined?(:within?)
1056
+
1057
+ # treats the fixnum as a representation of a number
1058
+ # of milliseconds, returns the approximate total number
1059
+ # of seconds represented e.g. 1000.milliseconds => 1
1060
+ # fractional seconds are truncated (rounded down)
1061
+ def milliseconds
1062
+ self / 1000
1063
+ end unless method_defined?(:milliseconds)
1064
+
1065
+ # treats the fixnum as a representation of a number
1066
+ # of seconds
1067
+ def seconds
1068
+ self
1069
+ end unless method_defined?(:seconds)
1070
+
1071
+ # treats the fixnum as a representation of a
1072
+ # number of minutes and returns the total number
1073
+ # of seconds represented.. e.g. 2.minutes => 120,
1074
+ # 3.minutes => 180
1075
+ def minutes
1076
+ seconds * 60
1077
+ end unless method_defined?(:minutes)
1078
+
1079
+ # treats the fixnum as a representation of a
1080
+ # number of hours and returns the total number
1081
+ # of seconds represented.. e.g. 2.hours => 7200
1082
+ def hours
1083
+ minutes * 60
1084
+ end unless method_defined?(:hours)
1085
+
1086
+ # treats the fixnum as a representation of a
1087
+ # number of days and returns the total number
1088
+ # of seconds represented.. e.g. 2.days => 172800
1089
+ def days
1090
+ hours * 24
1091
+ end unless method_defined?(:days)
1092
+
1093
+ # treats the fixnum as a representatin of a
1094
+ # number of weeks and returns the total number
1095
+ # of seconds represented.. e.g. 2.weeks => 1209600
1096
+ def weeks
1097
+ days * 7
1098
+ end unless method_defined?(:weeks)
1099
+
1100
+ alias second seconds unless method_defined?(:second)
1101
+ alias minute minutes unless method_defined?(:minute)
1102
+ alias hour hours unless method_defined?(:hour)
1103
+ alias day days unless method_defined?(:day)
1104
+ alias week weeks unless method_defined?(:week)
1105
+
1106
+ end