nelumba 0.0.13

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 (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