streams 0.1.0

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