streams 0.1.0

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 (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: []