lotus 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/lotus/author.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'lotus/activity'
|
2
|
+
|
3
|
+
module Lotus
|
4
|
+
require 'atom'
|
5
|
+
|
6
|
+
# Holds information about the author of the Feed.
|
7
|
+
class Author
|
8
|
+
require 'date'
|
9
|
+
|
10
|
+
# Holds the id that represents this contact.
|
11
|
+
attr_reader :id
|
12
|
+
|
13
|
+
# Holds the nickname of this contact.
|
14
|
+
attr_reader :nickname
|
15
|
+
|
16
|
+
# Holds a hash representing information about the name of this contact.
|
17
|
+
#
|
18
|
+
# contains one or more of the following:
|
19
|
+
# :formatted => The full name of the contact
|
20
|
+
# :family_name => The family name. "Last name" in Western contexts.
|
21
|
+
# :given_name => The given name. "First name" in Western contexts.
|
22
|
+
# :middle_name => The middle name.
|
23
|
+
# :honorific_prefix => "Title" in Western contexts. (e.g. "Mr." "Mrs.")
|
24
|
+
# :honorific_suffix => "Suffix" in Western contexts. (e.g. "Esq.")
|
25
|
+
attr_reader :extended_name
|
26
|
+
|
27
|
+
# The uri that uniquely identifies the author.
|
28
|
+
attr_reader :uri
|
29
|
+
|
30
|
+
# The email address of the author.
|
31
|
+
attr_reader :email
|
32
|
+
|
33
|
+
# The name of the author
|
34
|
+
attr_reader :name
|
35
|
+
|
36
|
+
# Holds a hash representing the address of the contact.
|
37
|
+
#
|
38
|
+
# contains one or more of the following:
|
39
|
+
# :formatted => A formatted representating of the address. May
|
40
|
+
# contain newlines.
|
41
|
+
# :street_address => The full street address. May contain newlines.
|
42
|
+
# :locality => The city or locality component.
|
43
|
+
# :region => The state or region component.
|
44
|
+
# :postal_code => The zipcode or postal code component.
|
45
|
+
# :country => The country name component.
|
46
|
+
attr_reader :address
|
47
|
+
|
48
|
+
# Holds a hash representing an organization for this contact.
|
49
|
+
#
|
50
|
+
# contains one or more of the following:
|
51
|
+
# :name => The name of the organization (e.g. company, school,
|
52
|
+
# etc) This field is required. Will be used for sorting.
|
53
|
+
# :department => The department within the organization.
|
54
|
+
# :title => The title or role within the organization.
|
55
|
+
# :type => The type of organization. Canonical values include
|
56
|
+
# "job" or "school"
|
57
|
+
# :start_date => A DateTime representing when the contact joined
|
58
|
+
# the organization.
|
59
|
+
# :end_date => A DateTime representing when the contact left the
|
60
|
+
# organization.
|
61
|
+
# :location => The physical location of this organization.
|
62
|
+
# :description => A free-text description of the role this contact
|
63
|
+
# played in this organization.
|
64
|
+
attr_reader :organization
|
65
|
+
|
66
|
+
# Holds a hash representing information about an account held by this
|
67
|
+
# contact.
|
68
|
+
#
|
69
|
+
# contains one or more of the following:
|
70
|
+
# :domain => The top-most authoriative domain for this account. (e.g.
|
71
|
+
# "twitter.com") This is the primary field. Is required.
|
72
|
+
# Used for sorting.
|
73
|
+
# :username => An alphanumeric username, typically chosen by the user.
|
74
|
+
# :userid => A user id, typically assigned, that uniquely refers to
|
75
|
+
# the user.
|
76
|
+
attr_reader :account
|
77
|
+
|
78
|
+
# Holds the gender of this contact.
|
79
|
+
attr_reader :gender
|
80
|
+
|
81
|
+
# Holds a note for this contact.
|
82
|
+
attr_reader :note
|
83
|
+
|
84
|
+
# Holds the display name for this contact.
|
85
|
+
attr_reader :display_name
|
86
|
+
|
87
|
+
# Holds the preferred username of this contact.
|
88
|
+
attr_reader :preferred_username
|
89
|
+
|
90
|
+
# Holds a DateTime that represents when this contact was last modified.
|
91
|
+
attr_reader :updated
|
92
|
+
|
93
|
+
# Holds a DateTime that represents when this contact was originally
|
94
|
+
# published.
|
95
|
+
attr_reader :published
|
96
|
+
|
97
|
+
# Holds a DateTime representing this contact's birthday.
|
98
|
+
attr_reader :birthday
|
99
|
+
|
100
|
+
# Holds a DateTime representing a contact's anniversary.
|
101
|
+
attr_reader :anniversary
|
102
|
+
|
103
|
+
# Creates a representating of an author.
|
104
|
+
#
|
105
|
+
# options:
|
106
|
+
# name => The name of the author. Defaults: "anonymous"
|
107
|
+
# id => The identifier that uniquely identifies the
|
108
|
+
# contact.
|
109
|
+
# nickname => The nickname of the contact.
|
110
|
+
# gender => The gender of the contact.
|
111
|
+
# note => A note for this contact.
|
112
|
+
# display_name => The display name for this contact.
|
113
|
+
# preferred_username => The preferred username for this contact.
|
114
|
+
# updated => A DateTime representing when this contact was
|
115
|
+
# last updated.
|
116
|
+
# published => A DateTime representing when this contact was
|
117
|
+
# originally created.
|
118
|
+
# birthday => A DateTime representing a birthday for this
|
119
|
+
# contact.
|
120
|
+
# anniversary => A DateTime representing an anniversary for this
|
121
|
+
# contact.
|
122
|
+
# extended_name => A Hash representing the name of the contact.
|
123
|
+
# organization => A Hash representing the organization of which the
|
124
|
+
# contact belongs.
|
125
|
+
# account => A Hash describing the authorative account for the
|
126
|
+
# author.
|
127
|
+
# address => A Hash describing the address of the contact.
|
128
|
+
# uri => The uri that uniquely identifies this author.
|
129
|
+
# email => The email of the author.
|
130
|
+
def initialize(options = {})
|
131
|
+
@uri = options[:uri]
|
132
|
+
@name = options[:name] || "anonymous"
|
133
|
+
@email = options[:email]
|
134
|
+
|
135
|
+
@id = options[:id]
|
136
|
+
@name = options[:name]
|
137
|
+
@gender = options[:gender]
|
138
|
+
@note = options[:note]
|
139
|
+
@nickname = options[:nickname]
|
140
|
+
@display_name = options[:display_name]
|
141
|
+
@preferred_username = options[:preferred_username]
|
142
|
+
@updated = options[:updated]
|
143
|
+
@published = options[:published]
|
144
|
+
@birthday = options[:birthday]
|
145
|
+
@anniversary = options[:anniversary]
|
146
|
+
|
147
|
+
@extended_name = options[:extended_name]
|
148
|
+
@organization = options[:organization]
|
149
|
+
@account = options[:account]
|
150
|
+
@address = options[:address]
|
151
|
+
end
|
152
|
+
|
153
|
+
def to_hash
|
154
|
+
{
|
155
|
+
:uri => self.uri,
|
156
|
+
:email => self.email,
|
157
|
+
:name => self.name,
|
158
|
+
|
159
|
+
:id => self.id,
|
160
|
+
:gender => self.gender,
|
161
|
+
:note => self.note,
|
162
|
+
:nickname => self.nickname,
|
163
|
+
:display_name => self.display_name,
|
164
|
+
:preferred_username => self.preferred_username,
|
165
|
+
:updated => self.updated,
|
166
|
+
:published => self.published,
|
167
|
+
:birthday => self.birthday,
|
168
|
+
:anniversary => self.anniversary,
|
169
|
+
|
170
|
+
:extended_name => self.extended_name,
|
171
|
+
:organization => self.organization,
|
172
|
+
:account => self.account,
|
173
|
+
:address => self.address
|
174
|
+
}
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Lotus
|
2
|
+
# This element conveys information about a category associated with an entry
|
3
|
+
# or feed. There is no defined meaning to the content according to the Atom
|
4
|
+
# specification.
|
5
|
+
class Category
|
6
|
+
# Holds the base URI for relative URIs contained in scheme.
|
7
|
+
attr_reader :base
|
8
|
+
|
9
|
+
# Holds the language of term and label, when it exists. The language
|
10
|
+
# should be specified as RFC 3066 as either 2 or 3 letter codes.
|
11
|
+
# For example: 'en' for English or more specifically 'en-us'
|
12
|
+
attr_reader :lang
|
13
|
+
|
14
|
+
# Holds the optional scheme used for categorization.
|
15
|
+
attr_reader :scheme
|
16
|
+
|
17
|
+
# Holds the string identifying the category to which the entry or
|
18
|
+
# feed belongs.
|
19
|
+
attr_reader :term
|
20
|
+
|
21
|
+
# Holds the string that provides a human-readable label for display in
|
22
|
+
# end-user applications. The content of this field is language sensitive.
|
23
|
+
attr_reader :label
|
24
|
+
|
25
|
+
# Create a Category to apply to a feed or entry.
|
26
|
+
def initialize(options = {})
|
27
|
+
@base = options[:base]
|
28
|
+
@lang = options[:lang]
|
29
|
+
@scheme = options[:scheme]
|
30
|
+
@term = options[:term]
|
31
|
+
@label = options[:label]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Yields a Hash that represents this category.
|
35
|
+
def to_hash
|
36
|
+
{
|
37
|
+
:base => @base,
|
38
|
+
:lang => @lang,
|
39
|
+
:scheme => @scheme,
|
40
|
+
:term => @term,
|
41
|
+
:label => @label
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/lotus/crypto.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
module Lotus
|
2
|
+
module Crypto
|
3
|
+
require 'openssl'
|
4
|
+
require 'rsa'
|
5
|
+
require 'base64'
|
6
|
+
|
7
|
+
KeyPair = Struct.new(:public_key, :private_key)
|
8
|
+
|
9
|
+
# Generate a new RSA keypair with the given bitlength.
|
10
|
+
def self.new_keypair(bits = 2048)
|
11
|
+
keypair = KeyPair.new
|
12
|
+
|
13
|
+
key = RSA::KeyPair.generate(bits)
|
14
|
+
|
15
|
+
public_key = key.public_key
|
16
|
+
m = public_key.modulus
|
17
|
+
e = public_key.exponent
|
18
|
+
|
19
|
+
modulus = ""
|
20
|
+
until m == 0 do
|
21
|
+
modulus << [m % 256].pack("C")
|
22
|
+
m >>= 8
|
23
|
+
end
|
24
|
+
modulus.reverse!
|
25
|
+
|
26
|
+
exponent = ""
|
27
|
+
until e == 0 do
|
28
|
+
exponent << [e % 256].pack("C")
|
29
|
+
e >>= 8
|
30
|
+
end
|
31
|
+
exponent.reverse!
|
32
|
+
|
33
|
+
keypair.public_key = "RSA.#{Base64::urlsafe_encode64(modulus)}.#{Base64::urlsafe_encode64(exponent)}"
|
34
|
+
|
35
|
+
tmp_private_key = key.private_key
|
36
|
+
m = tmp_private_key.modulus
|
37
|
+
e = tmp_private_key.exponent
|
38
|
+
|
39
|
+
modulus = ""
|
40
|
+
until m == 0 do
|
41
|
+
modulus << [m % 256].pack("C")
|
42
|
+
m >>= 8
|
43
|
+
end
|
44
|
+
modulus.reverse!
|
45
|
+
|
46
|
+
exponent = ""
|
47
|
+
until e == 0 do
|
48
|
+
exponent << [e % 256].pack("C")
|
49
|
+
e >>= 8
|
50
|
+
end
|
51
|
+
exponent.reverse!
|
52
|
+
|
53
|
+
keypair.private_key = "RSA.#{Base64::urlsafe_encode64(modulus)}.#{Base64::urlsafe_encode64(exponent)}"
|
54
|
+
|
55
|
+
keypair
|
56
|
+
end
|
57
|
+
|
58
|
+
# Creates an EMSA signature for the given plaintext and key.
|
59
|
+
def self.emsa_sign(text, private_key)
|
60
|
+
private_key = generate_key(private_key) unless private_key.is_a? RSA::Key
|
61
|
+
|
62
|
+
signature = self.emsa_signature(text, private_key)
|
63
|
+
|
64
|
+
self.decrypt(private_key, signature)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Verifies an existing EMSA signature.
|
68
|
+
def self.emsa_verify(text, signature, public_key)
|
69
|
+
# RSA encryption is needed to compare the signatures
|
70
|
+
public_key = generate_key(public_key) unless public_key.is_a? RSA::Key
|
71
|
+
|
72
|
+
# Get signature to check
|
73
|
+
emsa = self.emsa_signature(text, public_key)
|
74
|
+
|
75
|
+
# Get signature in payload
|
76
|
+
emsa_signature = self.encrypt(public_key, signature)
|
77
|
+
|
78
|
+
# RSA gem drops leading 0s since it does math upon an Integer
|
79
|
+
# As a workaround, I check for what I expect the second byte to be (\x01)
|
80
|
+
# This workaround will also handle seeing a \x00 first if the RSA gem is
|
81
|
+
# fixed.
|
82
|
+
if emsa_signature.getbyte(0) == 1
|
83
|
+
emsa_signature = "\x00#{emsa_signature}"
|
84
|
+
end
|
85
|
+
|
86
|
+
# Does the signature match?
|
87
|
+
# Return the result.
|
88
|
+
emsa_signature == emsa
|
89
|
+
end
|
90
|
+
|
91
|
+
# Decrypts the given data with the given private key.
|
92
|
+
def self.decrypt(private_key, data)
|
93
|
+
private_key = generate_key(private_key) unless private_key.is_a? RSA::Key
|
94
|
+
keypair = generate_keypair(nil, private_key)
|
95
|
+
keypair.decrypt(data)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Encrypts the given data with the given public key.
|
99
|
+
def self.encrypt(public_key, data)
|
100
|
+
public_key = generate_key(public_key) unless public_key.is_a? RSA::Key
|
101
|
+
keypair = generate_keypair(public_key, nil)
|
102
|
+
keypair.encrypt(data)
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# :nodoc:
|
108
|
+
def self.emsa_signature(text, key)
|
109
|
+
modulus_byte_count = key.modulus.size
|
110
|
+
|
111
|
+
plaintext = Digest::SHA2.new(256).digest(text)
|
112
|
+
|
113
|
+
prefix = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20"
|
114
|
+
padding_count = modulus_byte_count - prefix.bytes.count - plaintext.bytes.count - 3
|
115
|
+
|
116
|
+
padding = ""
|
117
|
+
padding_count.times do
|
118
|
+
padding = padding + "\xff"
|
119
|
+
end
|
120
|
+
|
121
|
+
"\x00\x01#{padding}\x00#{prefix}#{plaintext}".force_encoding('binary')
|
122
|
+
end
|
123
|
+
|
124
|
+
# :nodoc:
|
125
|
+
def self.generate_key(key_string)
|
126
|
+
return nil unless key_string
|
127
|
+
|
128
|
+
key_string.match /^RSA\.(.*?)\.(.*)$/
|
129
|
+
|
130
|
+
modulus = decode_key($1)
|
131
|
+
exponent = decode_key($2)
|
132
|
+
|
133
|
+
RSA::Key.new(modulus, exponent)
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.generate_keypair(public_key, private_key)
|
137
|
+
RSA::KeyPair.new(private_key, public_key)
|
138
|
+
end
|
139
|
+
|
140
|
+
# :nodoc:
|
141
|
+
def self.decode_key(encoded_key_part)
|
142
|
+
modulus = Base64::urlsafe_decode64(encoded_key_part)
|
143
|
+
modulus.bytes.inject(0) {|num, byte| (num << 8) | byte }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/lotus/feed.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'lotus/activity'
|
2
|
+
require 'lotus/author'
|
3
|
+
|
4
|
+
module Lotus
|
5
|
+
require 'atom'
|
6
|
+
|
7
|
+
# This class represents a Lotus::Feed object.
|
8
|
+
class Feed
|
9
|
+
require 'open-uri'
|
10
|
+
require 'date'
|
11
|
+
|
12
|
+
# Holds the id that uniquely represents this feed.
|
13
|
+
attr_reader :id
|
14
|
+
|
15
|
+
# Holds the url that represents this feed.
|
16
|
+
attr_reader :url
|
17
|
+
|
18
|
+
# Holds the list of categories for this feed as Lotus::Category.
|
19
|
+
attr_reader :categories
|
20
|
+
|
21
|
+
# Holds human-readable information about the content rights of the entries
|
22
|
+
# in the feed without an explicit rights field of their own. SHOULD NOT be
|
23
|
+
# machine interpreted.
|
24
|
+
attr_reader :rights
|
25
|
+
|
26
|
+
# Holds the title for this feed.
|
27
|
+
attr_reader :title
|
28
|
+
|
29
|
+
# Holds the content-type for the title.
|
30
|
+
attr_reader :title_type
|
31
|
+
|
32
|
+
# Holds the subtitle for this feed.
|
33
|
+
attr_reader :subtitle
|
34
|
+
|
35
|
+
# Holds the content-type for the subtitle.
|
36
|
+
attr_reader :subtitle_type
|
37
|
+
|
38
|
+
# Holds the URL for the icon representing this feed.
|
39
|
+
attr_reader :icon
|
40
|
+
|
41
|
+
# Holds the URL for the logo representing this feed.
|
42
|
+
attr_reader :logo
|
43
|
+
|
44
|
+
# Holds the generator for this content as an Lotus::Generator.
|
45
|
+
attr_reader :generator
|
46
|
+
|
47
|
+
# Holds the list of contributors, if any, that are involved in this feed
|
48
|
+
# as Lotus::Author.
|
49
|
+
attr_reader :contributors
|
50
|
+
|
51
|
+
# Holds the DateTime when this feed was originally created.
|
52
|
+
attr_reader :published
|
53
|
+
|
54
|
+
# Holds the DateTime when this feed was last modified.
|
55
|
+
attr_reader :updated
|
56
|
+
|
57
|
+
# Holds the list of authors as Lotus::Author responsible for this feed.
|
58
|
+
attr_reader :authors
|
59
|
+
|
60
|
+
# Holds the list of entries as Lotus::Activity contained within this feed.
|
61
|
+
attr_reader :entries
|
62
|
+
|
63
|
+
# Holds the list of hubs that are available to manage subscriptions to this
|
64
|
+
# feed.
|
65
|
+
attr_reader :hubs
|
66
|
+
|
67
|
+
# Holds the salmon url that handles notifications for this feed.
|
68
|
+
attr_reader :salmon_url
|
69
|
+
|
70
|
+
# Holds links to other resources as an array of Lotus::Link
|
71
|
+
attr_reader :links
|
72
|
+
|
73
|
+
# Creates a new representation of a feed.
|
74
|
+
#
|
75
|
+
# options:
|
76
|
+
# id => The unique identifier for this feed.
|
77
|
+
# url => The url that represents this feed.
|
78
|
+
# title => The title for this feed. Defaults: "Untitled"
|
79
|
+
# title_type => The content type for the title.
|
80
|
+
# subtitle => The subtitle for this feed.
|
81
|
+
# subtitle_type => The content type for the subtitle.
|
82
|
+
# authors => The list of Lotus::Author's for this feed.
|
83
|
+
# Defaults: []
|
84
|
+
# contributors => The list of Lotus::Author's that contributed to this
|
85
|
+
# feed. Defaults: []
|
86
|
+
# entries => The list of Lotus::Activity's for this feed.
|
87
|
+
# Defaults: []
|
88
|
+
# icon => The url of the icon that represents this feed. It
|
89
|
+
# should have an aspect ratio of 1 horizontal to 1
|
90
|
+
# vertical and optimized for presentation at a
|
91
|
+
# small size.
|
92
|
+
# logo => The url of the logo that represents this feed. It
|
93
|
+
# should have an aspect ratio of 2 horizontal to 1
|
94
|
+
# vertical.
|
95
|
+
# categories => An array of Lotus::Category's that describe how to
|
96
|
+
# categorize and describe the content of the feed.
|
97
|
+
# Defaults: []
|
98
|
+
# rights => A String depicting the rights of entries without
|
99
|
+
# explicit rights of their own. SHOULD NOT be machine
|
100
|
+
# interpreted.
|
101
|
+
# updated => The DateTime representing when this feed was last
|
102
|
+
# modified.
|
103
|
+
# published => The DateTime representing when this feed was originally
|
104
|
+
# published.
|
105
|
+
# salmon_url => The url of the salmon endpoint, if one exists, for this
|
106
|
+
# feed.
|
107
|
+
# links => An array of Lotus::Link that adds relations to other
|
108
|
+
# resources.
|
109
|
+
# generator => A Lotus::Generator representing the agent
|
110
|
+
# responsible for generating this feed.
|
111
|
+
#
|
112
|
+
# Usage:
|
113
|
+
#
|
114
|
+
# author = Lotus::Author.new(:name => "Kelly")
|
115
|
+
#
|
116
|
+
# feed = Lotus::Feed.new(:title => "My Feed",
|
117
|
+
# :id => "1",
|
118
|
+
# :url => "http://example.com/feeds/1",
|
119
|
+
# :authors => [author])
|
120
|
+
def initialize(options = {})
|
121
|
+
@id = options[:id]
|
122
|
+
@url = options[:url]
|
123
|
+
@icon = options[:icon]
|
124
|
+
@logo = options[:logo]
|
125
|
+
@rights = options[:rights]
|
126
|
+
@title = options[:title] || "Untitled"
|
127
|
+
@title_type = options[:title_type]
|
128
|
+
@subtitle = options[:subtitle]
|
129
|
+
@subtitle_type = options[:subtitle_type]
|
130
|
+
@authors = options[:authors] || []
|
131
|
+
@categories = options[:categories] || []
|
132
|
+
@contributors = options[:contributors] || []
|
133
|
+
@entries = options[:entries] || []
|
134
|
+
@updated = options[:updated]
|
135
|
+
@published = options[:published]
|
136
|
+
@salmon_url = options[:salmon_url]
|
137
|
+
@hubs = options[:hubs] || []
|
138
|
+
@generator = options[:generator]
|
139
|
+
end
|
140
|
+
|
141
|
+
# Yields a Lotus::Link to this feed.
|
142
|
+
#
|
143
|
+
# options: Can override Lotus::Link properties, such as rel.
|
144
|
+
#
|
145
|
+
# Usage:
|
146
|
+
#
|
147
|
+
# feed = Lotus::Feed.new(:title => "Foo", :url => "http://example.com")
|
148
|
+
# feed.to_link(:rel => "alternate", :title => "Foo's Feed")
|
149
|
+
#
|
150
|
+
# Generates a link with:
|
151
|
+
# <Lotus::Link rel="alternate" title="Foo's Feed" url="http://example.com">
|
152
|
+
def to_link(options = {})
|
153
|
+
options = { :title => self.title,
|
154
|
+
:href => self.url }.merge(options)
|
155
|
+
|
156
|
+
Lotus::Link.new(options)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns a hash of the properties of the feed.
|
160
|
+
def to_hash
|
161
|
+
{
|
162
|
+
:id => self.id,
|
163
|
+
:url => self.url,
|
164
|
+
:hubs => self.hubs.dup,
|
165
|
+
:icon => self.icon,
|
166
|
+
:logo => self.logo,
|
167
|
+
:rights => self.rights,
|
168
|
+
:title => self.title,
|
169
|
+
:title_type => self.title_type,
|
170
|
+
:subtitle => self.subtitle,
|
171
|
+
:subtitle_type => self.subtitle_type,
|
172
|
+
:authors => self.authors.dup,
|
173
|
+
:categories => self.categories.dup,
|
174
|
+
:contributors => self.contributors.dup,
|
175
|
+
:entries => self.entries.dup,
|
176
|
+
:updated => self.updated,
|
177
|
+
:salmon_url => self.salmon_url,
|
178
|
+
:published => self.published,
|
179
|
+
:generator => self.generator
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns a string containing an Atom representation of the feed.
|
184
|
+
def to_atom
|
185
|
+
require 'lotus/atom/feed'
|
186
|
+
|
187
|
+
Lotus::Atom::Feed.from_canonical(self).to_xml
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|