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.
- data/.gitignore +6 -0
- data/.travis.yml +9 -0
- data/Gemfile +16 -0
- data/README.md +233 -0
- data/Rakefile +7 -0
- data/lib/lotus.rb +232 -0
- data/lib/lotus/activity.rb +134 -0
- data/lib/lotus/atom/account.rb +50 -0
- data/lib/lotus/atom/address.rb +56 -0
- data/lib/lotus/atom/author.rb +167 -0
- data/lib/lotus/atom/category.rb +41 -0
- data/lib/lotus/atom/entry.rb +159 -0
- data/lib/lotus/atom/feed.rb +174 -0
- data/lib/lotus/atom/generator.rb +40 -0
- data/lib/lotus/atom/link.rb +79 -0
- data/lib/lotus/atom/name.rb +57 -0
- data/lib/lotus/atom/organization.rb +62 -0
- data/lib/lotus/atom/portable_contacts.rb +117 -0
- data/lib/lotus/atom/source.rb +168 -0
- data/lib/lotus/atom/thread.rb +60 -0
- data/lib/lotus/author.rb +177 -0
- data/lib/lotus/category.rb +45 -0
- data/lib/lotus/crypto.rb +146 -0
- data/lib/lotus/feed.rb +190 -0
- data/lib/lotus/generator.rb +53 -0
- data/lib/lotus/identity.rb +59 -0
- data/lib/lotus/link.rb +56 -0
- data/lib/lotus/notification.rb +220 -0
- data/lib/lotus/publisher.rb +40 -0
- data/lib/lotus/subscription.rb +117 -0
- data/lib/lotus/version.rb +3 -0
- data/lotus.gemspec +27 -0
- data/spec/activity_spec.rb +84 -0
- data/spec/atom/feed_spec.rb +681 -0
- data/spec/author_spec.rb +150 -0
- data/spec/crypto_spec.rb +138 -0
- data/spec/feed_spec.rb +252 -0
- data/spec/helper.rb +8 -0
- data/spec/identity_spec.rb +67 -0
- data/spec/link_spec.rb +30 -0
- data/spec/notification_spec.rb +77 -0
- data/test/example_feed.atom +393 -0
- data/test/example_feed_empty_author.atom +336 -0
- data/test/example_feed_false_connected.atom +359 -0
- data/test/example_feed_link_without_href.atom +134 -0
- data/test/example_page.html +4 -0
- data/test/mime_type_bug_feed.atom +874 -0
- 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
|