nelumba 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. data/.gitignore +6 -0
  2. data/.travis.yml +9 -0
  3. data/Gemfile +20 -0
  4. data/README.md +242 -0
  5. data/Rakefile +7 -0
  6. data/assets/lotus_logo_purple.png +0 -0
  7. data/assets/lotus_logo_purple.svg +262 -0
  8. data/lib/nelumba.rb +47 -0
  9. data/lib/nelumba/activity.rb +250 -0
  10. data/lib/nelumba/application.rb +11 -0
  11. data/lib/nelumba/article.rb +11 -0
  12. data/lib/nelumba/atom/account.rb +50 -0
  13. data/lib/nelumba/atom/address.rb +56 -0
  14. data/lib/nelumba/atom/author.rb +176 -0
  15. data/lib/nelumba/atom/category.rb +41 -0
  16. data/lib/nelumba/atom/comment.rb +96 -0
  17. data/lib/nelumba/atom/entry.rb +216 -0
  18. data/lib/nelumba/atom/feed.rb +198 -0
  19. data/lib/nelumba/atom/generator.rb +40 -0
  20. data/lib/nelumba/atom/link.rb +79 -0
  21. data/lib/nelumba/atom/name.rb +57 -0
  22. data/lib/nelumba/atom/organization.rb +62 -0
  23. data/lib/nelumba/atom/person.rb +179 -0
  24. data/lib/nelumba/atom/portable_contacts.rb +117 -0
  25. data/lib/nelumba/atom/source.rb +179 -0
  26. data/lib/nelumba/atom/thread.rb +60 -0
  27. data/lib/nelumba/audio.rb +39 -0
  28. data/lib/nelumba/badge.rb +11 -0
  29. data/lib/nelumba/binary.rb +52 -0
  30. data/lib/nelumba/bookmark.rb +30 -0
  31. data/lib/nelumba/category.rb +49 -0
  32. data/lib/nelumba/collection.rb +34 -0
  33. data/lib/nelumba/comment.rb +47 -0
  34. data/lib/nelumba/crypto.rb +144 -0
  35. data/lib/nelumba/device.rb +11 -0
  36. data/lib/nelumba/discover.rb +362 -0
  37. data/lib/nelumba/event.rb +57 -0
  38. data/lib/nelumba/feed.rb +173 -0
  39. data/lib/nelumba/file.rb +43 -0
  40. data/lib/nelumba/generator.rb +53 -0
  41. data/lib/nelumba/group.rb +11 -0
  42. data/lib/nelumba/identity.rb +63 -0
  43. data/lib/nelumba/image.rb +30 -0
  44. data/lib/nelumba/link.rb +56 -0
  45. data/lib/nelumba/note.rb +34 -0
  46. data/lib/nelumba/notification.rb +229 -0
  47. data/lib/nelumba/object.rb +251 -0
  48. data/lib/nelumba/person.rb +306 -0
  49. data/lib/nelumba/place.rb +34 -0
  50. data/lib/nelumba/product.rb +30 -0
  51. data/lib/nelumba/publisher.rb +44 -0
  52. data/lib/nelumba/question.rb +30 -0
  53. data/lib/nelumba/review.rb +30 -0
  54. data/lib/nelumba/service.rb +11 -0
  55. data/lib/nelumba/subscription.rb +117 -0
  56. data/lib/nelumba/version.rb +3 -0
  57. data/lib/nelumba/video.rb +43 -0
  58. data/nelumba.gemspec +28 -0
  59. data/spec/activity_spec.rb +116 -0
  60. data/spec/application_spec.rb +136 -0
  61. data/spec/article_spec.rb +136 -0
  62. data/spec/atom/comment_spec.rb +455 -0
  63. data/spec/atom/feed_spec.rb +684 -0
  64. data/spec/audio_spec.rb +164 -0
  65. data/spec/badge_spec.rb +136 -0
  66. data/spec/binary_spec.rb +218 -0
  67. data/spec/bookmark.rb +150 -0
  68. data/spec/collection_spec.rb +152 -0
  69. data/spec/comment_spec.rb +128 -0
  70. data/spec/crypto_spec.rb +126 -0
  71. data/spec/device_spec.rb +136 -0
  72. data/spec/event_spec.rb +239 -0
  73. data/spec/feed_spec.rb +252 -0
  74. data/spec/file_spec.rb +190 -0
  75. data/spec/group_spec.rb +136 -0
  76. data/spec/helper.rb +10 -0
  77. data/spec/identity_spec.rb +67 -0
  78. data/spec/image_spec.rb +150 -0
  79. data/spec/link_spec.rb +30 -0
  80. data/spec/note_spec.rb +163 -0
  81. data/spec/notification_spec.rb +89 -0
  82. data/spec/person_spec.rb +244 -0
  83. data/spec/place_spec.rb +162 -0
  84. data/spec/product_spec.rb +150 -0
  85. data/spec/question_spec.rb +156 -0
  86. data/spec/review_spec.rb +149 -0
  87. data/spec/service_spec.rb +136 -0
  88. data/spec/video_spec.rb +164 -0
  89. data/test/example_feed.atom +393 -0
  90. data/test/example_feed_empty_author.atom +336 -0
  91. data/test/example_feed_false_connected.atom +359 -0
  92. data/test/example_feed_link_without_href.atom +134 -0
  93. data/test/example_page.html +4 -0
  94. data/test/mime_type_bug_feed.atom +874 -0
  95. metadata +288 -0
@@ -0,0 +1,41 @@
1
+ require 'nelumba/category'
2
+
3
+ module Nelumba
4
+ require 'atom'
5
+
6
+ module Atom
7
+ # This class represents an Nelumba Category object.
8
+ class Category < ::Atom::Category
9
+ require 'open-uri'
10
+
11
+ attribute :'xml:base'
12
+ attribute :'xml:lang'
13
+ attribute :scheme
14
+ attribute :term
15
+ attribute :label
16
+
17
+ def self.from_canonical(obj)
18
+ hash = obj.to_hash
19
+ if hash[:base]
20
+ hash[:xml_base] = hash[:base]
21
+ end
22
+ if hash[:lang]
23
+ hash[:xml_lang] = hash[:lang]
24
+ end
25
+ hash.delete :base
26
+ hash.delete :lang
27
+ self.new(hash)
28
+ end
29
+
30
+ def to_canonical
31
+ Nelumba::Category.new(:base => self.xml_base,
32
+ :lang => self.xml_lang,
33
+ :scheme => self.scheme,
34
+ :lable => self.label,
35
+ :term => self.term)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+
@@ -0,0 +1,96 @@
1
+ module Nelumba
2
+ require 'atom'
3
+ require 'nelumba/atom/thread'
4
+
5
+ module Atom
6
+ # This class represents an ActivityStreams Comment object.
7
+ class Comment
8
+ include ::Atom::Xml::Parseable
9
+
10
+ # The XML namespace that identifies the conforming specification.
11
+ ACTIVITY_NAMESPACE = 'http://activitystrea.ms/spec/1.0/'
12
+
13
+ # The XML namespace that identifies the conforming specification of 'thr'
14
+ # elements.
15
+ THREAD_NAMESPACE = "http://purl.org/syndication/thread/1.0"
16
+
17
+ # The XML schema that identifies the conforming schema for objects.
18
+ SCHEMA_ROOT = 'http://activitystrea.ms/schema/1.0/'
19
+
20
+ add_extension_namespace :activity, ACTIVITY_NAMESPACE
21
+ element 'activity:object-type'
22
+
23
+ element :author, :class => Nelumba::Atom::Author
24
+ element :content, :class => ::Atom::Content
25
+ element :displayName
26
+ element :id
27
+ element :title
28
+ element :url
29
+ element :summary
30
+ element :updated, :published, :class => DateTime, :content_only => true
31
+
32
+ add_extension_namespace :thr, THREAD_NAMESPACE
33
+ elements 'thr:in-reply-to', :class => Nelumba::Atom::Thread
34
+
35
+ def initialize(o = {})
36
+ o[:activity_object_type] = SCHEMA_ROOT + "comment"
37
+
38
+ case o
39
+ when XML::Reader
40
+ o.read
41
+ parse(o)
42
+ when Hash
43
+ o.each do |k, v|
44
+ self.send("#{k.to_s}=", v)
45
+ end
46
+ else
47
+ raise ArgumentError, "Got #{o.class} but expected a Hash or XML::Reader"
48
+ end
49
+
50
+ yield(self) if block_given?
51
+ end
52
+
53
+ def to_hash
54
+ {
55
+ }
56
+ end
57
+
58
+ def self.from_canonical(obj)
59
+ entry_hash = obj.to_hash
60
+
61
+ entry_hash.delete :text
62
+ entry_hash.delete :html
63
+
64
+ entry_hash[:id] = entry_hash[:uid]
65
+ entry_hash.delete :uid
66
+
67
+ entry_hash[:displayName] = entry_hash[:display_name]
68
+ entry_hash.delete :display_name
69
+
70
+ entry_hash[:thr_in_reply_to] = entry_hash[:in_reply_to].map do |t|
71
+ Nelumba::Atom::Thread.new(:href => t.url,
72
+ :ref => t.uid)
73
+ end
74
+ entry_hash.delete :in_reply_to
75
+
76
+ if entry_hash[:author]
77
+ entry_hash[:author] = Nelumba::Atom::Author.from_canonical(entry_hash[:author])
78
+ end
79
+
80
+ node = XML::Node.new("content")
81
+ node['type'] = "html"
82
+ node << entry_hash[:content]
83
+
84
+ xml = XML::Reader.string(node.to_s)
85
+ xml.read
86
+ entry_hash[:content] = ::Atom::Content.parse(xml)
87
+
88
+ self.new entry_hash
89
+ end
90
+
91
+ def to_canonical
92
+ to_hash
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,216 @@
1
+ module Nelumba
2
+ module Atom
3
+ require 'atom'
4
+
5
+ class Entry < ::Atom::Entry
6
+ require 'nelumba/activity'
7
+ require 'nelumba/person'
8
+ require 'nelumba/link'
9
+
10
+ require 'nelumba/atom/person'
11
+ require 'nelumba/atom/author'
12
+ require 'nelumba/atom/thread'
13
+ require 'nelumba/atom/link'
14
+ require 'nelumba/atom/comment'
15
+ require 'nelumba/atom/source'
16
+
17
+ require 'libxml'
18
+
19
+ # The XML namespace that identifies the conforming specification of 'thr'
20
+ # elements.
21
+ THREAD_NAMESPACE = "http://purl.org/syndication/thread/1.0"
22
+
23
+ # The XML namespace that identifies the conforming specification.
24
+ ACTIVITY_NAMESPACE = 'http://activitystrea.ms/spec/1.0/'
25
+
26
+ # The XML schema that identifies the conforming schema for objects.
27
+ SCHEMA_ROOT = 'http://activitystrea.ms/schema/1.0/'
28
+
29
+ include ::Atom::SimpleExtensions
30
+
31
+ add_extension_namespace :activity, ACTIVITY_NAMESPACE
32
+ element 'activity:object-type'
33
+ element 'activity:object'
34
+ element 'activity:verb'
35
+ element 'activity:target'
36
+
37
+ add_extension_namespace :thr, THREAD_NAMESPACE
38
+ elements 'thr:in-reply-to', :class => Nelumba::Atom::Thread
39
+
40
+ # This is for backwards compatibility with some implementations of Activity
41
+ # Streams. It should not be generated for Atom representation of Activity
42
+ # Streams (although it is used in JSON)
43
+ element 'activity:actor', :class => Nelumba::Atom::Author
44
+
45
+ element :source, :class => Nelumba::Atom::Source
46
+
47
+ namespace ::Atom::NAMESPACE
48
+ element :title, :id, :summary
49
+ element :updated, :published, :class => DateTime, :content_only => true
50
+ elements :links, :class => Nelumba::Atom::Link
51
+
52
+ elements :replies, :class => Nelumba::Atom::Entry
53
+
54
+ elements :shares, :class => Nelumba::Atom::Person
55
+ elements :likes, :class => Nelumba::Atom::Person
56
+ elements :mentions, :class => Nelumba::Atom::Person
57
+
58
+ elements :categories, :class => ::Atom::Category
59
+ element :content, :class => ::Atom::Content
60
+ elements :authors, :class => Nelumba::Atom::Author
61
+
62
+ # ActivityStreams
63
+ element :displayName
64
+
65
+ def url
66
+ if links.alternate
67
+ links.alternate.href
68
+ elsif links.self
69
+ links.self.href
70
+ else
71
+ links.map.each do |l|
72
+ l.href
73
+ end.compact.first
74
+ end
75
+ end
76
+
77
+ def link
78
+ link_list = links.group_by { |l| l.rel.intern if l.rel }
79
+ end
80
+
81
+ def link= options
82
+ links.clear << ::Atom::Link.new(options)
83
+ end
84
+
85
+ def self.from_canonical(obj)
86
+ entry_hash = obj.to_hash
87
+
88
+ # Ensure that the content type is encoded.
89
+ object = obj.object
90
+
91
+ title = object.title
92
+
93
+ content = object.text
94
+ content = object.html if object.html
95
+
96
+ content_type = nil
97
+ content_type = "html" if object.html
98
+
99
+ if object.is_a? Nelumba::Note
100
+ elsif object.is_a? Nelumba::Comment
101
+ content = nil
102
+ content_type = nil
103
+ title = nil
104
+ object = Nelumba::Atom::Comment.from_canonical(object.to_hash)
105
+ entry_hash[:activity_object] = object
106
+ else
107
+ content = nil
108
+ content_type = nil
109
+ title = nil
110
+ entry_hash[:activity_object] = object if object.is_a? Nelumba::Person
111
+ end
112
+
113
+ if content
114
+ node = XML::Node.new("content")
115
+ node['type'] = content_type if content_type
116
+ node << content
117
+
118
+ xml = XML::Reader.string(node.to_s)
119
+ xml.read
120
+ entry_hash[:content] = ::Atom::Content.parse(xml)
121
+ entry_hash.delete :content_type
122
+ end
123
+
124
+ entry_hash[:title] = title if title
125
+
126
+ if entry_hash[:source]
127
+ entry_hash[:source] = Nelumba::Atom::Source.from_canonical(entry_hash[:source])
128
+ end
129
+
130
+ if entry_hash[:actor]
131
+ entry_hash[:authors] = [Nelumba::Atom::Author.from_canonical(entry_hash[:actor])]
132
+ end
133
+ entry_hash.delete :actor
134
+
135
+ # Encode in-reply-to fields
136
+ entry_hash[:thr_in_reply_to] = entry_hash[:in_reply_to].map do |t|
137
+ Nelumba::Atom::Thread.new(:href => t.url,
138
+ :ref => t.uid)
139
+ end
140
+ entry_hash.delete :in_reply_to
141
+
142
+ entry_hash[:links] ||= []
143
+
144
+ if entry_hash[:url]
145
+ entry_hash[:links] << ::Atom::Link.new(:rel => "self", :href => entry_hash[:url])
146
+ end
147
+ entry_hash.delete :url
148
+
149
+ object_type = entry_hash[:type]
150
+ if object_type
151
+ entry_hash[:activity_object_type] = SCHEMA_ROOT + object_type.to_s
152
+ end
153
+ if entry_hash[:verb]
154
+ entry_hash[:activity_verb] = SCHEMA_ROOT + entry_hash[:verb].to_s
155
+ end
156
+ entry_hash[:activity_target] = entry_hash[:target] if entry_hash[:target]
157
+
158
+ entry_hash[:id] = entry_hash[:uid]
159
+ entry_hash.delete :uid
160
+
161
+ entry_hash.delete :object
162
+ entry_hash.delete :verb
163
+ entry_hash.delete :target
164
+ entry_hash.delete :type
165
+
166
+ entry_hash[:displayName] = entry_hash[:display_name]
167
+ entry_hash.delete :display_name
168
+
169
+ # Remove empty entries
170
+ entry_hash.keys.each do |key|
171
+ if entry_hash[key].nil? || entry_hash[key] == ""
172
+ entry_hash.delete key
173
+ end
174
+ end
175
+
176
+ self.new(entry_hash)
177
+ end
178
+
179
+ def to_canonical
180
+ # Reform the activity type
181
+ # TODO: Add new Base schema verbs
182
+ object_type = self.activity_object_type
183
+ if object_type && object_type.start_with?(SCHEMA_ROOT)
184
+ object_type.gsub!(/^#{Regexp.escape(SCHEMA_ROOT)}/, "")
185
+ end
186
+
187
+ object_type = "note" if object_type == "status"
188
+
189
+ if self.activity_object
190
+ object = self.activity_object.to_canonical
191
+ else
192
+ case object_type
193
+ when "note"
194
+ object = Nelumba::Note.new(:html => self.content.to_s,
195
+ :title => self.title)
196
+ end
197
+ end
198
+
199
+ source = self.source
200
+ source = source.to_canonical if source
201
+ Nelumba::Activity.new(:actor => self.authors ? self.authors.first.to_canonical : nil,
202
+ :uid => self.id,
203
+ :url => self.url,
204
+ :source => source,
205
+ :display_name => self.displayName,
206
+ :in_reply_to => self.thr_in_reply_to.map(&:to_canonical),
207
+ :object => object,
208
+ :type => object_type,
209
+ :verb => self.activity_verb,
210
+ :target => self.activity_target,
211
+ :published => self.published,
212
+ :updated => self.updated)
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,198 @@
1
+ require 'nelumba/activity'
2
+ require 'nelumba/person'
3
+ require 'nelumba/category'
4
+ require 'nelumba/generator'
5
+
6
+ require 'nelumba/atom/entry'
7
+ require 'nelumba/atom/generator'
8
+ require 'nelumba/atom/category'
9
+ require 'nelumba/atom/person'
10
+ require 'nelumba/atom/author'
11
+ require 'nelumba/atom/link'
12
+
13
+ module Nelumba
14
+ require 'atom'
15
+
16
+ module Atom
17
+ # This class represents an OStatus Feed object.
18
+ class Feed < ::Atom::Feed
19
+ require 'open-uri'
20
+
21
+ include ::Atom::SimpleExtensions
22
+
23
+ # The XML namespace the specifies this content.
24
+ POCO_NAMESPACE = 'http://portablecontacts.net/spec/1.0'
25
+
26
+ # The XML namespace that identifies the conforming specification.
27
+ ACTIVITY_NAMESPACE = 'http://activitystrea.ms/spec/1.0/'
28
+
29
+ namespace ::Atom::NAMESPACE
30
+
31
+ add_extension_namespace :poco, POCO_NAMESPACE
32
+ add_extension_namespace :activity, ACTIVITY_NAMESPACE
33
+ element :id, :rights, :icon, :logo
34
+ element :generator, :class => Nelumba::Atom::Generator
35
+ element :title, :class => ::Atom::Content
36
+ element :subtitle, :class => ::Atom::Content
37
+ element :published, :class => Time, :content_only => true
38
+ element :updated, :class => Time, :content_only => true
39
+ elements :links, :class => ::Atom::Link
40
+ elements :authors, :class => Nelumba::Atom::Author
41
+ elements :contributors, :class => Nelumba::Atom::Person
42
+ elements :categories, :class => Nelumba::Atom::Category
43
+ elements :entries, :class => Nelumba::Atom::Entry
44
+
45
+ # Activity Streams
46
+ element :totalItems
47
+ element :displayName
48
+ element :content
49
+ element :summary
50
+
51
+ # Creates an Atom generator for the given Nelumba::Feed.
52
+ def self.from_canonical(obj)
53
+ hash = obj.to_hash
54
+ hash[:items].map! {|e|
55
+ Nelumba::Atom::Entry.from_canonical(e)
56
+ }
57
+
58
+ hash.delete :text
59
+ hash.delete :html
60
+
61
+ # Ensure that the generator is encoded.
62
+ if hash[:generator]
63
+ hash[:generator] = Nelumba::Atom::Generator.from_canonical(hash[:generator])
64
+ end
65
+
66
+ hash[:links] ||= []
67
+
68
+ if hash[:salmon_url]
69
+ hash[:links] << ::Atom::Link.new(:rel => "salmon", :href => hash[:salmon_url])
70
+ end
71
+ hash.delete :salmon_url
72
+
73
+ if hash[:url]
74
+ hash[:links] << ::Atom::Link.new(:rel => "self", :href => hash[:url])
75
+ end
76
+ hash.delete :url
77
+
78
+ hash[:hubs].each {|h|
79
+ hash[:links] << ::Atom::Link.new(:rel => "hub", :href => h)
80
+ }
81
+ hash.delete :hubs
82
+
83
+ hash[:authors].map! {|a|
84
+ Nelumba::Atom::Author.from_canonical(a)
85
+ }
86
+
87
+ hash[:contributors].map! {|a|
88
+ Nelumba::Atom::Person.from_canonical(a)
89
+ }
90
+
91
+ hash[:categories].map! {|c|
92
+ Nelumba::Atom::Category.from_canonical(c)
93
+ }
94
+
95
+ hash.delete :author
96
+
97
+ hash[:displayName] = hash[:display_name]
98
+ hash.delete :display_name
99
+
100
+ hash[:entries] = hash[:items]
101
+ hash.delete :items
102
+
103
+ hash[:totalItems] = hash[:total_items]
104
+ hash.delete :total_items
105
+
106
+ # title/subtitle content type
107
+ node = XML::Node.new("title")
108
+ node['type'] = hash[:title_type] if hash[:title_type]
109
+ node << hash[:title]
110
+
111
+ xml = XML::Reader.string(node.to_s)
112
+ xml.read
113
+ hash[:title] = ::Atom::Content.parse(xml)
114
+ hash.delete :title_type
115
+
116
+ hash[:id] = hash[:uid]
117
+ hash.delete :uid
118
+
119
+ if hash[:subtitle]
120
+ node = XML::Node.new("subtitle")
121
+ node['type'] = hash[:subtitle_type] if hash[:subtitle_type]
122
+ node << hash[:subtitle]
123
+
124
+ xml = XML::Reader.string(node.to_s)
125
+ xml.read
126
+ hash[:subtitle] = ::Atom::Content.parse(xml)
127
+ else
128
+ hash.delete :subtitle
129
+ end
130
+ hash.delete :subtitle_type
131
+
132
+ self.new(hash)
133
+ end
134
+
135
+ def to_canonical
136
+ generator = nil
137
+ generator = self.generator.to_canonical if self.generator
138
+
139
+ salmon_url = nil
140
+ if self.link('salmon').any?
141
+ salmon_url = self.link('salmon').first.href
142
+ end
143
+
144
+ url = self.url
145
+
146
+ categories = self.categories.map(&:to_canonical)
147
+
148
+ Nelumba::Feed.new(:title => self.title.to_s,
149
+ :title_type => self.title ? self.title.type : nil,
150
+ :subtitle => self.subtitle.to_s,
151
+ :subtitle_type => self.subtitle ? self.subtitle.type : nil,
152
+ :uid => self.id,
153
+ :url => url,
154
+ :categories => categories,
155
+ :icon => self.icon,
156
+ :logo => self.logo,
157
+ :rights => self.rights,
158
+ :published => self.published,
159
+ :updated => self.updated,
160
+ :items => self.entries.map(&:to_canonical),
161
+ :authors => self.authors.map(&:to_canonical),
162
+ :contributors => self.contributors.map(&:to_canonical),
163
+ :hubs => self.hubs,
164
+ :salmon_url => salmon_url,
165
+ :generator => generator)
166
+ end
167
+
168
+ # Returns an array of ::Atom::Link instances for all link tags
169
+ # that have a rel equal to that given by attribute.
170
+ #
171
+ # For example:
172
+ # link(:hub).first.href -- Gets the first link tag with rel="hub" and
173
+ # returns the contents of the href attribute.
174
+ #
175
+ def link(attribute)
176
+ links.find_all { |l| l.rel == attribute.to_s }
177
+ end
178
+
179
+ # Returns an array of URLs for each hub link tag.
180
+ def hubs
181
+ link('hub').map { |link| link.href }
182
+ end
183
+
184
+ # Returns a string of the url for this feed.
185
+ def url
186
+ if links.alternate
187
+ links.alternate.href
188
+ elsif links.self
189
+ links.self.href
190
+ else
191
+ links.map.each do |l|
192
+ l.href
193
+ end.compact.first
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end