lotus 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
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