lotus 0.0.12

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 (48) hide show
  1. data/.gitignore +6 -0
  2. data/.travis.yml +9 -0
  3. data/Gemfile +16 -0
  4. data/README.md +233 -0
  5. data/Rakefile +7 -0
  6. data/lib/lotus.rb +232 -0
  7. data/lib/lotus/activity.rb +134 -0
  8. data/lib/lotus/atom/account.rb +50 -0
  9. data/lib/lotus/atom/address.rb +56 -0
  10. data/lib/lotus/atom/author.rb +167 -0
  11. data/lib/lotus/atom/category.rb +41 -0
  12. data/lib/lotus/atom/entry.rb +159 -0
  13. data/lib/lotus/atom/feed.rb +174 -0
  14. data/lib/lotus/atom/generator.rb +40 -0
  15. data/lib/lotus/atom/link.rb +79 -0
  16. data/lib/lotus/atom/name.rb +57 -0
  17. data/lib/lotus/atom/organization.rb +62 -0
  18. data/lib/lotus/atom/portable_contacts.rb +117 -0
  19. data/lib/lotus/atom/source.rb +168 -0
  20. data/lib/lotus/atom/thread.rb +60 -0
  21. data/lib/lotus/author.rb +177 -0
  22. data/lib/lotus/category.rb +45 -0
  23. data/lib/lotus/crypto.rb +146 -0
  24. data/lib/lotus/feed.rb +190 -0
  25. data/lib/lotus/generator.rb +53 -0
  26. data/lib/lotus/identity.rb +59 -0
  27. data/lib/lotus/link.rb +56 -0
  28. data/lib/lotus/notification.rb +220 -0
  29. data/lib/lotus/publisher.rb +40 -0
  30. data/lib/lotus/subscription.rb +117 -0
  31. data/lib/lotus/version.rb +3 -0
  32. data/lotus.gemspec +27 -0
  33. data/spec/activity_spec.rb +84 -0
  34. data/spec/atom/feed_spec.rb +681 -0
  35. data/spec/author_spec.rb +150 -0
  36. data/spec/crypto_spec.rb +138 -0
  37. data/spec/feed_spec.rb +252 -0
  38. data/spec/helper.rb +8 -0
  39. data/spec/identity_spec.rb +67 -0
  40. data/spec/link_spec.rb +30 -0
  41. data/spec/notification_spec.rb +77 -0
  42. data/test/example_feed.atom +393 -0
  43. data/test/example_feed_empty_author.atom +336 -0
  44. data/test/example_feed_false_connected.atom +359 -0
  45. data/test/example_feed_link_without_href.atom +134 -0
  46. data/test/example_page.html +4 -0
  47. data/test/mime_type_bug_feed.atom +874 -0
  48. metadata +204 -0
@@ -0,0 +1,159 @@
1
+ module Lotus
2
+ module Atom
3
+ require 'atom'
4
+
5
+ class Entry < ::Atom::Entry
6
+ require 'lotus/activity'
7
+ require 'lotus/author'
8
+ require 'lotus/link'
9
+
10
+ require 'lotus/atom/author'
11
+ require 'lotus/atom/thread'
12
+ require 'lotus/atom/link'
13
+ require 'lotus/atom/source'
14
+
15
+ require 'libxml'
16
+
17
+ # The XML namespace that identifies the conforming specification of 'thr'
18
+ # elements.
19
+ THREAD_NAMESPACE = "http://purl.org/syndication/thread/1.0"
20
+
21
+ # The XML namespace that identifies the conforming specification.
22
+ ACTIVITY_NAMESPACE = 'http://activitystrea.ms/spec/1.0/'
23
+
24
+ # The XML schema that identifies the conforming schema for objects.
25
+ SCHEMA_ROOT = 'http://activitystrea.ms/schema/1.0/'
26
+
27
+ include ::Atom::SimpleExtensions
28
+
29
+ add_extension_namespace :activity, ACTIVITY_NAMESPACE
30
+ element 'activity:object-type'
31
+ element 'activity:object', :class => Lotus::Atom::Author
32
+ element 'activity:verb'
33
+ element 'activity:target'
34
+
35
+ add_extension_namespace :thr, THREAD_NAMESPACE
36
+ elements 'thr:in-reply-to', :class => Lotus::Atom::Thread
37
+
38
+ # This is for backwards compatibility with some implementations of Activity
39
+ # Streams. It should not be generated for Atom representation of Activity
40
+ # Streams (although it is used in JSON)
41
+ element 'activity:actor', :class => Lotus::Atom::Author
42
+
43
+ element :source, :class => Lotus::Atom::Source
44
+
45
+ namespace ::Atom::NAMESPACE
46
+ element :title, :id, :summary
47
+ element :updated, :published, :class => DateTime, :content_only => true
48
+ elements :links, :class => Lotus::Atom::Link
49
+
50
+ elements :categories, :class => ::Atom::Category
51
+ element :content, :class => ::Atom::Content
52
+ element :author, :class => Lotus::Atom::Author
53
+
54
+ def url
55
+ if links.alternate
56
+ links.alternate.href
57
+ elsif links.self
58
+ links.self.href
59
+ else
60
+ links.map.each do |l|
61
+ l.href
62
+ end.compact.first
63
+ end
64
+ end
65
+
66
+ def link
67
+ links.group_by { |l| l.rel.intern if l.rel }
68
+ end
69
+
70
+ def link= options
71
+ links.clear << ::Atom::Link.new(options)
72
+ end
73
+
74
+ def self.from_canonical(obj)
75
+ entry_hash = obj.to_hash
76
+
77
+ # Ensure that the content type is encoded.
78
+ node = XML::Node.new("content")
79
+ node['type'] = entry_hash[:content_type] if entry_hash[:content_type]
80
+ node << entry_hash[:content]
81
+
82
+ xml = XML::Reader.string(node.to_s)
83
+ xml.read
84
+ entry_hash[:content] = ::Atom::Content.parse(xml)
85
+ entry_hash.delete :content_type
86
+
87
+ if entry_hash[:source]
88
+ entry_hash[:source] = Lotus::Atom::Source.from_canonical(entry_hash[:source])
89
+ end
90
+
91
+ if entry_hash[:actor]
92
+ entry_hash[:author] = Lotus::Atom::Author.from_canonical(entry_hash[:actor])
93
+ end
94
+ entry_hash.delete :actor
95
+
96
+ # Encode in-reply-to fields
97
+ entry_hash[:thr_in_reply_to] = entry_hash[:in_reply_to].map do |t|
98
+ Lotus::Atom::Thread.new(:href => t.url,
99
+ :ref => t.id)
100
+ end
101
+ entry_hash.delete :in_reply_to
102
+
103
+ entry_hash[:links] ||= []
104
+
105
+ if entry_hash[:url]
106
+ entry_hash[:links] << ::Atom::Link.new(:rel => "self", :href => entry_hash[:url])
107
+ end
108
+ entry_hash.delete :url
109
+
110
+ object_type = entry_hash[:type]
111
+ if object_type
112
+ entry_hash[:activity_object_type] = SCHEMA_ROOT + object_type.to_s
113
+ end
114
+ entry_hash[:activity_object] = entry_hash[:object] if entry_hash[:object]
115
+ if entry_hash[:verb]
116
+ entry_hash[:activity_verb] = SCHEMA_ROOT + entry_hash[:verb].to_s
117
+ end
118
+ entry_hash[:activity_target] = entry_hash[:target] if entry_hash[:target]
119
+
120
+ entry_hash.delete :object
121
+ entry_hash.delete :verb
122
+ entry_hash.delete :target
123
+ entry_hash.delete :type
124
+
125
+ self.new(entry_hash)
126
+ end
127
+
128
+ def to_canonical
129
+ # Reform the activity type
130
+ # TODO: Add new Base schema verbs
131
+ object_type = self.activity_object_type
132
+ if object_type && object_type.start_with?(SCHEMA_ROOT)
133
+ object_type.gsub!(/^#{Regexp.escape(SCHEMA_ROOT)}/, "")
134
+ end
135
+
136
+ object = nil
137
+ object = self.activity_object.to_canonical if self.activity_object
138
+
139
+ source = self.source
140
+ source = source.to_canonical if source
141
+ Lotus::Activity.new(:actor => self.author ? self.author.to_canonical : nil,
142
+ :id => self.id,
143
+ :url => self.url,
144
+ :title => self.title,
145
+ :source => source,
146
+ :in_reply_to => self.thr_in_reply_to.map(&:to_canonical),
147
+ :content => self.content.to_s,
148
+ :content_type => self.content.type,
149
+ :link => self.link,
150
+ :object => object,
151
+ :type => object_type,
152
+ :verb => self.activity_verb,
153
+ :target => self.activity_target,
154
+ :published => self.published,
155
+ :updated => self.updated)
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,174 @@
1
+ require 'lotus/activity'
2
+ require 'lotus/author'
3
+ require 'lotus/category'
4
+ require 'lotus/generator'
5
+
6
+ require 'lotus/atom/entry'
7
+ require 'lotus/atom/generator'
8
+ require 'lotus/atom/category'
9
+ require 'lotus/atom/author'
10
+ require 'lotus/atom/link'
11
+
12
+ module Lotus
13
+ require 'atom'
14
+
15
+ module Atom
16
+ # This class represents an OStatus Feed object.
17
+ class Feed < ::Atom::Feed
18
+ require 'open-uri'
19
+
20
+ include ::Atom::SimpleExtensions
21
+
22
+ # The XML namespace the specifies this content.
23
+ POCO_NAMESPACE = 'http://portablecontacts.net/spec/1.0'
24
+
25
+ # The XML namespace that identifies the conforming specification.
26
+ ACTIVITY_NAMESPACE = 'http://activitystrea.ms/spec/1.0/'
27
+
28
+ namespace ::Atom::NAMESPACE
29
+
30
+ add_extension_namespace :poco, POCO_NAMESPACE
31
+ add_extension_namespace :activity, ACTIVITY_NAMESPACE
32
+ element :id, :rights, :icon, :logo
33
+ element :generator, :class => Lotus::Atom::Generator
34
+ element :title, :class => ::Atom::Content
35
+ element :subtitle, :class => ::Atom::Content
36
+ element :published, :class => Time, :content_only => true
37
+ element :updated, :class => Time, :content_only => true
38
+ elements :links, :class => ::Atom::Link
39
+ elements :authors, :class => Lotus::Atom::Author
40
+ elements :contributors, :class => Lotus::Atom::Author
41
+ elements :categories, :class => Lotus::Atom::Category
42
+ elements :entries, :class => Lotus::Atom::Entry
43
+
44
+ # Creates an Atom generator for the given Lotus::Feed.
45
+ def self.from_canonical(obj)
46
+ hash = obj.to_hash
47
+ hash[:entries].map! {|e|
48
+ Lotus::Atom::Entry.from_canonical(e)
49
+ }
50
+
51
+ # Ensure that the generator is encoded.
52
+ if hash[:generator]
53
+ hash[:generator] = Lotus::Atom::Generator.from_canonical(hash[:generator])
54
+ end
55
+
56
+ hash[:links] ||= []
57
+
58
+ if hash[:salmon_url]
59
+ hash[:links] << ::Atom::Link.new(:rel => "salmon", :href => hash[:salmon_url])
60
+ end
61
+ hash.delete :salmon_url
62
+
63
+ if hash[:url]
64
+ hash[:links] << ::Atom::Link.new(:rel => "self", :href => hash[:url])
65
+ end
66
+ hash.delete :url
67
+
68
+ hash[:hubs].each {|h|
69
+ hash[:links] << ::Atom::Link.new(:rel => "hub", :href => h)
70
+ }
71
+ hash.delete :hubs
72
+
73
+ hash[:authors].map! {|a|
74
+ Lotus::Atom::Author.from_canonical(a)
75
+ }
76
+
77
+ hash[:contributors].map! {|a|
78
+ Lotus::Atom::Author.from_canonical(a)
79
+ }
80
+
81
+ hash[:categories].map! {|c|
82
+ Lotus::Atom::Category.from_canonical(c)
83
+ }
84
+
85
+ # title/subtitle content type
86
+ node = XML::Node.new("title")
87
+ node['type'] = hash[:title_type] if hash[:title_type]
88
+ node << hash[:title]
89
+
90
+ xml = XML::Reader.string(node.to_s)
91
+ xml.read
92
+ hash[:title] = ::Atom::Content.parse(xml)
93
+ hash.delete :title_type
94
+
95
+ if hash[:subtitle]
96
+ node = XML::Node.new("subtitle")
97
+ node['type'] = hash[:subtitle_type] if hash[:subtitle_type]
98
+ node << hash[:subtitle]
99
+
100
+ xml = XML::Reader.string(node.to_s)
101
+ xml.read
102
+ hash[:subtitle] = ::Atom::Content.parse(xml)
103
+ else
104
+ hash.delete :subtitle
105
+ end
106
+ hash.delete :subtitle_type
107
+
108
+ self.new(hash)
109
+ end
110
+
111
+ def to_canonical
112
+ generator = nil
113
+ generator = self.generator.to_canonical if self.generator
114
+
115
+ salmon_url = nil
116
+ if self.link('salmon').any?
117
+ salmon_url = self.link('salmon').first.href
118
+ end
119
+
120
+ url = self.url
121
+
122
+ categories = self.categories.map(&:to_canonical)
123
+
124
+ Lotus::Feed.new(:title => self.title.to_s,
125
+ :title_type => self.title ? self.title.type : nil,
126
+ :subtitle => self.subtitle.to_s,
127
+ :subtitle_type => self.subtitle ? self.subtitle.type : nil,
128
+ :id => self.id,
129
+ :url => url,
130
+ :categories => categories,
131
+ :icon => self.icon,
132
+ :logo => self.logo,
133
+ :rights => self.rights,
134
+ :published => self.published,
135
+ :updated => self.updated,
136
+ :entries => self.entries.map(&:to_canonical),
137
+ :authors => self.authors.map(&:to_canonical),
138
+ :contributors => self.contributors.map(&:to_canonical),
139
+ :hubs => self.hubs,
140
+ :salmon_url => salmon_url,
141
+ :generator => generator)
142
+ end
143
+
144
+ # Returns an array of ::Atom::Link instances for all link tags
145
+ # that have a rel equal to that given by attribute.
146
+ #
147
+ # For example:
148
+ # link(:hub).first.href -- Gets the first link tag with rel="hub" and
149
+ # returns the contents of the href attribute.
150
+ #
151
+ def link(attribute)
152
+ links.find_all { |l| l.rel == attribute.to_s }
153
+ end
154
+
155
+ # Returns an array of URLs for each hub link tag.
156
+ def hubs
157
+ link('hub').map { |link| link.href }
158
+ end
159
+
160
+ # Returns a string of the url for this feed.
161
+ def url
162
+ if links.alternate
163
+ links.alternate.href
164
+ elsif links.self
165
+ links.self.href
166
+ else
167
+ links.map.each do |l|
168
+ l.href
169
+ end.compact.first
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,40 @@
1
+ require 'lotus/generator'
2
+
3
+ module Lotus
4
+ require 'atom'
5
+
6
+ module Atom
7
+ # This class represents an OStatus Generator object.
8
+ class Generator < ::Atom::Generator
9
+ require 'open-uri'
10
+
11
+ attribute :'xml:base'
12
+ attribute :'xml:lang'
13
+ attribute :version
14
+ attribute :uri
15
+
16
+ def self.from_canonical(obj)
17
+ hash = obj.to_hash
18
+ if hash[:base]
19
+ hash[:xml_base] = hash[:base]
20
+ end
21
+ if hash[:lang]
22
+ hash[:xml_lang] = hash[:lang]
23
+ end
24
+ hash.delete :base
25
+ hash.delete :lang
26
+ self.new(hash)
27
+ end
28
+
29
+ def to_canonical
30
+ Lotus::Generator.new(:base => self.xml_base,
31
+ :lang => self.xml_lang,
32
+ :version => self.version,
33
+ :name => self.name,
34
+ :uri => self.uri)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+
@@ -0,0 +1,79 @@
1
+ module Lotus
2
+ require 'atom'
3
+
4
+ module Atom
5
+
6
+ # This represents an Atom parser/generator for <link> tags.
7
+ class Link < ::Atom::Link
8
+ include ::Atom::Xml::Parseable
9
+
10
+ attribute :rel, :type, :length, :hreflang, :title, :text
11
+
12
+ uri_attribute :href
13
+
14
+ # Create a link.
15
+ #
16
+ # +o+:: An XML::Reader containing a link element or a Hash of attributes.
17
+ #
18
+ def initialize(o)
19
+ case o
20
+ when XML::Reader
21
+ if current_node_is?(o, 'link')
22
+ self.text = o.read_string
23
+ parse(o, :once => true)
24
+ else
25
+ raise ArgumentError, "Link created with node other than atom:link: #{o.name}"
26
+ end
27
+ when Hash
28
+ [:href, :rel, :type, :length, :hreflang, :title].each do |attr|
29
+ self.send("#{attr}=", o[attr])
30
+ end
31
+ else
32
+ raise ArgumentError, "Don't know how to handle #{o}"
33
+ end
34
+ end
35
+
36
+ remove_method :length=
37
+ def length=(v)
38
+ @length = v.to_i
39
+ end
40
+
41
+ # Returns the href of the link, or the content of the link if no href is
42
+ # provided.
43
+ def href
44
+ @href || self.text
45
+ end
46
+
47
+ # Reports the href of the link.
48
+ def to_s
49
+ self.href
50
+ end
51
+
52
+ # Two links are equal when their href is exactly the same regardless of
53
+ # case.
54
+ def ==(o)
55
+ o.respond_to?(:href) && o.href == self.href
56
+ end
57
+
58
+ # This will fetch the URL referenced by the link.
59
+ #
60
+ # If the URL contains a valid feed, a Feed will be returned, otherwise,
61
+ # the body of the response will be returned.
62
+ #
63
+ # TODO: Handle redirects.
64
+ #
65
+ def fetch(options = {})
66
+ begin
67
+ ::Atom::Feed.load_feed(URI.parse(self.href), options)
68
+ rescue ArgumentError
69
+ Net::HTTP.get_response(URI.parse(self.href)).body
70
+ end
71
+ end
72
+
73
+ # :nodoc:
74
+ def inspect
75
+ "<Lotus::Link href:'#{href}' type:'#{type}'>"
76
+ end
77
+ end
78
+ end
79
+ end