feedjira-podcast 0.9.8 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.hound.yml +2 -0
- data/.rubocop.yml +1 -0
- data/.ruby-style.yml +1260 -0
- data/.travis.yml +4 -0
- data/Gemfile +1 -1
- data/README.md +7 -3
- data/Rakefile +8 -4
- data/feedjira-podcast.gemspec +19 -18
- data/lib/feedjira/parser/podcast_item.rb +0 -1
- data/lib/feedjira/podcast.rb +32 -37
- data/lib/feedjira/podcast/channel/apple.rb +39 -33
- data/lib/feedjira/podcast/channel/apple_category.rb +60 -1
- data/lib/feedjira/podcast/channel/apple_subcategory.rb +3 -0
- data/lib/feedjira/podcast/channel/atom.rb +14 -12
- data/lib/feedjira/podcast/channel/cloud.rb +16 -0
- data/lib/feedjira/podcast/channel/feedburner.rb +8 -6
- data/lib/feedjira/podcast/channel/image.rb +2 -8
- data/lib/feedjira/podcast/channel/optional.rb +59 -33
- data/lib/feedjira/podcast/channel/required.rb +0 -2
- data/lib/feedjira/podcast/channel/skip_days.rb +1 -3
- data/lib/feedjira/podcast/channel/skip_hours.rb +1 -3
- data/lib/feedjira/podcast/channel/syndication.rb +1 -2
- data/lib/feedjira/podcast/error.rb +0 -1
- data/lib/feedjira/podcast/item/apple.rb +36 -36
- data/lib/feedjira/podcast/item/content.rb +6 -4
- data/lib/feedjira/podcast/item/dublin.rb +6 -4
- data/lib/feedjira/podcast/item/guid.rb +2 -3
- data/lib/feedjira/podcast/item/optional.rb +16 -16
- data/lib/feedjira/podcast/item/required.rb +0 -2
- data/lib/feedjira/podcast/item/source.rb +0 -1
- data/lib/feedjira/podcast/version.rb +1 -1
- data/lib/feedjira/podcast/warning.rb +0 -1
- metadata +21 -3
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -42,9 +42,9 @@ In cases where an element can repeat in a given context, the accessor will be pl
|
|
42
42
|
|
43
43
|
#### Typecasting
|
44
44
|
|
45
|
-
In nearly all cases the value types of podcast feed data is predictable. As such, the parser will cast values appropriately. In cases where a feed is malformed, the result of the type cast may be somewhat unpredictable, but no more so than the original data was. A bad value should never prevent parsing of the rest of the document, but it bust a specific value (e.g. a date that is human-readable but not parseable will end up as `nil` rather than fallback to an ambiguous `String` value).
|
45
|
+
In nearly all cases the value types of podcast feed data is predictable. As such, the parser will cast values appropriately. In cases where a feed is malformed, the result of the type cast may be somewhat unpredictable, but no more so than the original data was. A bad value should never prevent parsing of the rest of the document, but it could bust a specific value (e.g. a date that is human-readable but not parseable will end up as `nil` rather than fallback to an ambiguous `String` value).
|
46
46
|
|
47
|
-
Date values are parsed as `Time` objects, number values become `Float` objects, hrefs, URIs and URLs are parsed using [Addressable](https://github.com/sporkmonger/addressable), and boolean values will return `true` or `false
|
47
|
+
Date values are parsed as `Time` objects, number values become `Float` objects, hrefs, URIs and URLs are parsed using [Addressable](https://github.com/sporkmonger/addressable), and boolean values will return `true` or `false`.
|
48
48
|
|
49
49
|
In the case of the `<itunes:explicit>` tag, there are three mutually exclusive options, `"yes"`, `"clean"`, and any other value (representing `"no"`). This element gets expanded to two properties through parsing, `explicit?` and `clean?`, allowing both to be simple boolean values.
|
50
50
|
|
@@ -56,12 +56,16 @@ The parser will try its best to parse whatever data it has available, with a str
|
|
56
56
|
|
57
57
|
Besides standard typecasting, the parser won't try to clean up any data. For example, many elements (such as `<description>`) explicitly allow only plaintext, but it is common for them to include markup in the wild. The parser will assume that markup to be plaintext, according to the spec. If you want to sanitize those parts of feeds, you should handle that on your end.
|
58
58
|
|
59
|
+
#### Parsing
|
60
|
+
|
61
|
+
During parsing, some aspects of the original feed will not be maintained in such a way that a functionally identical feed could be generated from the result. For example. `CDATA` declarations in XML elements are lost, as the parser converts them (correctly) to strings. If the resulting data is being used to generate feeds, wrapping values in CDATA would be the responsibility of the whatever is constructing the feed, based on the values of the strings. Similarly, Apple expects iTunes category tag values to include encoded ampersands, eg `TV & Film`. Parsing the feed will decode those values (to `TV & Film`), so they would have to be re-encoded before being used somewhere that iTunes would be reading from.
|
62
|
+
|
59
63
|
### More Information
|
60
64
|
|
61
65
|
For more detailed information about specific aspects of feeds, how they are spec'd, and how they are handled by the parser, see the [wiki](https://github.com/scour/feedjira-podcast/wiki).
|
62
66
|
|
63
67
|
## In Progress
|
64
68
|
|
65
|
-
Coverage of RSS, iTunes, and the other common constituents of podcast feeds is very high, but there are some bits that need to be addressed. Several rarely-used RSS elements (`<
|
69
|
+
Coverage of RSS, iTunes, and the other common constituents of podcast feeds is very high, but there are some bits that need to be addressed. Several rarely-used RSS elements (`<rating>`, etc) are not supported. More esoteric elements, such a host-specific tags, or various parts of Dublin Core, are added based on their prevalence in real world feeds.
|
66
70
|
|
67
71
|
Experimental feed elements may be added over time, but they should be used with caution until they reach a critical mass or become standardized.
|
data/Rakefile
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
3
|
|
4
4
|
Rake::TestTask.new(:test) do |t|
|
5
|
-
t.pattern =
|
5
|
+
t.pattern = "test/**/test_*.rb"
|
6
6
|
end
|
7
7
|
|
8
|
-
|
8
|
+
require "rubocop/rake_task"
|
9
|
+
RuboCop::RakeTask.new(:analyze)
|
10
|
+
|
11
|
+
task checks: [:test, :analyze]
|
12
|
+
task default: :checks
|
data/feedjira-podcast.gemspec
CHANGED
@@ -1,33 +1,34 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
lib = File.expand_path(
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require "feedjira/podcast/version"
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = "feedjira-podcast"
|
8
8
|
spec.version = Feedjira::Podcast::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
9
|
+
spec.authors = ["Chris Kalafarski"]
|
10
|
+
spec.email = ["chris@farski.com"]
|
11
11
|
|
12
|
-
spec.summary =
|
13
|
-
spec.description =
|
14
|
-
spec.homepage =
|
15
|
-
spec.license =
|
12
|
+
spec.summary = "Podcast feed parsing with Feedjira"
|
13
|
+
spec.description = "Podcast feed parsing with Feedjira"
|
14
|
+
spec.homepage = "https://github.com/scour/feedjira-podcast"
|
15
|
+
spec.license = "MIT"
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
-
spec.bindir =
|
18
|
+
spec.bindir = "exe"
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
-
spec.require_paths = [
|
20
|
+
spec.require_paths = ["lib"]
|
21
21
|
|
22
22
|
if spec.respond_to?(:metadata)
|
23
|
-
spec.metadata[
|
23
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
24
24
|
end
|
25
25
|
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.8"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "minitest", "~> 5.5"
|
29
|
+
spec.add_development_dependency "coveralls", "~> 0"
|
30
|
+
spec.add_development_dependency "rubocop", "~> 0"
|
30
31
|
|
31
|
-
spec.add_runtime_dependency
|
32
|
-
spec.add_runtime_dependency
|
32
|
+
spec.add_runtime_dependency "feedjira", "~> 2.0"
|
33
|
+
spec.add_runtime_dependency "addressable", "~> 2.3"
|
33
34
|
end
|
data/lib/feedjira/podcast.rb
CHANGED
@@ -1,39 +1,34 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
|
4
|
-
require
|
5
|
-
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
|
14
|
-
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
19
|
-
require
|
20
|
-
|
21
|
-
|
22
|
-
require
|
23
|
-
|
24
|
-
|
25
|
-
require
|
26
|
-
require
|
27
|
-
require
|
28
|
-
require
|
29
|
-
|
30
|
-
|
31
|
-
require
|
32
|
-
|
33
|
-
module Feedjira
|
34
|
-
module Podcast
|
35
|
-
# Your code goes here...
|
36
|
-
end
|
37
|
-
end
|
1
|
+
require "feedjira"
|
2
|
+
require "addressable/uri"
|
3
|
+
|
4
|
+
require "feedjira/podcast/version"
|
5
|
+
|
6
|
+
require "feedjira/podcast/channel/cloud"
|
7
|
+
require "feedjira/podcast/channel/image"
|
8
|
+
require "feedjira/podcast/channel/skip_hours"
|
9
|
+
require "feedjira/podcast/channel/skip_days"
|
10
|
+
require "feedjira/podcast/channel/text_input"
|
11
|
+
require "feedjira/podcast/channel/apple_owner"
|
12
|
+
require "feedjira/podcast/channel/apple_subcategory"
|
13
|
+
require "feedjira/podcast/channel/apple_category"
|
14
|
+
|
15
|
+
require "feedjira/podcast/channel/required"
|
16
|
+
require "feedjira/podcast/channel/optional"
|
17
|
+
require "feedjira/podcast/channel/atom"
|
18
|
+
require "feedjira/podcast/channel/feedburner"
|
19
|
+
require "feedjira/podcast/channel/apple"
|
20
|
+
require "feedjira/podcast/channel/syndication"
|
21
|
+
|
22
|
+
require "feedjira/podcast/item/guid"
|
23
|
+
require "feedjira/podcast/item/source"
|
24
|
+
|
25
|
+
require "feedjira/podcast/item/required"
|
26
|
+
require "feedjira/podcast/item/optional"
|
27
|
+
require "feedjira/podcast/item/apple"
|
28
|
+
require "feedjira/podcast/item/dublin"
|
29
|
+
require "feedjira/podcast/item/content"
|
30
|
+
|
31
|
+
require "feedjira/parser/podcast_item"
|
32
|
+
require "feedjira/parser/podcast"
|
38
33
|
|
39
34
|
Feedjira::Feed.add_feed_class(Feedjira::Parser::Podcast)
|
@@ -2,35 +2,7 @@ module Feedjira
|
|
2
2
|
module Podcast
|
3
3
|
module Channel
|
4
4
|
module Apple
|
5
|
-
|
6
|
-
|
7
|
-
base.element :"itunes:author", as: :itunes_author
|
8
|
-
|
9
|
-
base.element :"itunes:block", as: :_itunes_block
|
10
|
-
|
11
|
-
base.elements :"itunes:category", as: :itunes_categories, class: AppleCategory
|
12
|
-
|
13
|
-
base.element :"itunes:image", as: :itunes_image_href, value: :href do |href|
|
14
|
-
Addressable::URI.parse(href)
|
15
|
-
end
|
16
|
-
|
17
|
-
base.element :"itunes:explicit", as: :_itunes_explicit
|
18
|
-
base.element :"itunes:complete", as: :_itunes_complete
|
19
|
-
|
20
|
-
base.element :"itunes:new_feed_url", as: :itunes_new_feed_url do |url|
|
21
|
-
Addressable::URI.parse(url)
|
22
|
-
end
|
23
|
-
|
24
|
-
base.element :"itunes:owner", as: :_itunes_owner, class: AppleOwner
|
25
|
-
base.element :"itunes:subtitle", as: :itunes_subtitle
|
26
|
-
base.element :"itunes:summary", as: :itunes_summary
|
27
|
-
|
28
|
-
# Legacy support
|
29
|
-
|
30
|
-
base.element :"itunes:keywords", as: :itunes_keywords do |keywords|
|
31
|
-
keywords.split(',').map(&:strip).select { |k| !k.empty? }
|
32
|
-
end
|
33
|
-
|
5
|
+
module InstanceMethods
|
34
6
|
def itunes
|
35
7
|
@itunes ||= Struct.new(
|
36
8
|
:author,
|
@@ -68,19 +40,23 @@ module Feedjira
|
|
68
40
|
end
|
69
41
|
|
70
42
|
def itunes_block
|
71
|
-
@itunes_block ||= (_itunes_block ==
|
43
|
+
@itunes_block ||= (_itunes_block == "yes")
|
44
|
+
end
|
45
|
+
|
46
|
+
def itunes_categories
|
47
|
+
@itunes_categories ||= _itunes_categories.select(&:valid?)
|
72
48
|
end
|
73
49
|
|
74
50
|
def itunes_complete
|
75
|
-
@itunes_complete ||= (_itunes_complete ==
|
51
|
+
@itunes_complete ||= (_itunes_complete == "yes")
|
76
52
|
end
|
77
53
|
|
78
54
|
def itunes_explicit
|
79
|
-
@itunes_explicit ||= (_itunes_explicit ==
|
55
|
+
@itunes_explicit ||= (_itunes_explicit == "yes")
|
80
56
|
end
|
81
57
|
|
82
58
|
def itunes_clean
|
83
|
-
@itunes_clean ||=
|
59
|
+
@itunes_clean ||= (_itunes_explicit == "clean")
|
84
60
|
end
|
85
61
|
|
86
62
|
def itunes_owner
|
@@ -89,7 +65,37 @@ module Feedjira
|
|
89
65
|
_itunes_owner && _itunes_owner.name,
|
90
66
|
)
|
91
67
|
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.included(base)
|
71
|
+
base.include(InstanceMethods)
|
72
|
+
|
73
|
+
base.element :"itunes:author", as: :itunes_author
|
74
|
+
|
75
|
+
base.element :"itunes:block", as: :_itunes_block
|
76
|
+
|
77
|
+
base.elements :"itunes:category", as: :_itunes_categories, class: AppleCategory
|
78
|
+
|
79
|
+
base.element :"itunes:image", as: :itunes_image_href, value: :href do |href|
|
80
|
+
Addressable::URI.parse(href)
|
81
|
+
end
|
82
|
+
|
83
|
+
base.element :"itunes:explicit", as: :_itunes_explicit
|
84
|
+
base.element :"itunes:complete", as: :_itunes_complete
|
85
|
+
|
86
|
+
base.element :"itunes:new_feed_url", as: :itunes_new_feed_url do |url|
|
87
|
+
Addressable::URI.parse(url)
|
88
|
+
end
|
89
|
+
|
90
|
+
base.element :"itunes:owner", as: :_itunes_owner, class: AppleOwner
|
91
|
+
base.element :"itunes:subtitle", as: :itunes_subtitle
|
92
|
+
base.element :"itunes:summary", as: :itunes_summary
|
92
93
|
|
94
|
+
# Legacy support
|
95
|
+
|
96
|
+
base.element :"itunes:keywords", as: :itunes_keywords do |keywords|
|
97
|
+
keywords.split(",").map(&:strip).select { |k| !k.empty? }
|
98
|
+
end
|
93
99
|
end
|
94
100
|
end
|
95
101
|
end
|
@@ -5,10 +5,69 @@ module Feedjira
|
|
5
5
|
include SAXMachine
|
6
6
|
include FeedUtilities
|
7
7
|
|
8
|
+
CATEGORIES = {
|
9
|
+
"Arts" => [
|
10
|
+
"Design",
|
11
|
+
"Fashion & Beauty",
|
12
|
+
"Food",
|
13
|
+
"Literature",
|
14
|
+
"Performing Arts",
|
15
|
+
"Visual Arts"
|
16
|
+
],
|
17
|
+
"Business" => [
|
18
|
+
"Business News",
|
19
|
+
"Careers",
|
20
|
+
"Investing",
|
21
|
+
"Management & Marketing",
|
22
|
+
"Shopping"
|
23
|
+
],
|
24
|
+
"Comedy" => [],
|
25
|
+
"Education" => [
|
26
|
+
|
27
|
+
],
|
28
|
+
"Games & Hobbies" => [
|
29
|
+
|
30
|
+
],
|
31
|
+
"Government & Organizations" => [
|
32
|
+
|
33
|
+
],
|
34
|
+
"Health" => [
|
35
|
+
|
36
|
+
],
|
37
|
+
"Kids & Family" => [],
|
38
|
+
"Music" => [],
|
39
|
+
"News & Politics" => [],
|
40
|
+
"Religion & Spirituality" => [
|
41
|
+
|
42
|
+
],
|
43
|
+
"Science & Medicine" => [
|
44
|
+
|
45
|
+
],
|
46
|
+
"Society & Culture" => [
|
47
|
+
|
48
|
+
],
|
49
|
+
"Sports & Recreation" => [
|
50
|
+
|
51
|
+
],
|
52
|
+
"Technology" => [
|
53
|
+
|
54
|
+
],
|
55
|
+
"TV & Film" => []
|
56
|
+
}
|
57
|
+
|
8
58
|
attribute :text
|
9
59
|
|
10
|
-
|
60
|
+
elements :"itunes:category", as: :subcategories, class: AppleSubcategory
|
61
|
+
|
62
|
+
def subcategory
|
63
|
+
if subcategories.first && subcategories.first.valid?(self)
|
64
|
+
subcategories.first
|
65
|
+
end
|
66
|
+
end
|
11
67
|
|
68
|
+
def valid?
|
69
|
+
CATEGORIES.include?(text)
|
70
|
+
end
|
12
71
|
end
|
13
72
|
end
|
14
73
|
end
|
@@ -2,18 +2,7 @@ module Feedjira
|
|
2
2
|
module Podcast
|
3
3
|
module Channel
|
4
4
|
module Atom
|
5
|
-
|
6
|
-
|
7
|
-
['self', 'hub'].each do |rel|
|
8
|
-
[:"atom:link", :"atom10:link"].each do |ns|
|
9
|
-
base.element ns, with: { rel: rel }, as: "atom_#{rel}_link_href".to_sym, value: :href do |href|
|
10
|
-
Addressable::URI.parse(href)
|
11
|
-
end
|
12
|
-
base.element ns, with: { rel: rel }, as: "atom_#{rel}_link_rel".to_sym, value: :rel
|
13
|
-
base.element ns, with: { rel: rel }, as: "atom_#{rel}_link_type".to_sym, value: :type
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
5
|
+
module InstanceMethods
|
17
6
|
def atom
|
18
7
|
@atom ||= Struct.new(:link).new(atom_link)
|
19
8
|
end
|
@@ -42,7 +31,20 @@ module Feedjira
|
|
42
31
|
atom_hub_link_type,
|
43
32
|
)
|
44
33
|
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.included(base)
|
37
|
+
base.include(InstanceMethods)
|
45
38
|
|
39
|
+
["self", "hub"].each do |rel|
|
40
|
+
[:"atom:link", :"atom10:link"].each do |ns|
|
41
|
+
base.element ns, with: { rel: rel }, as: "atom_#{rel}_link_href".to_sym, value: :href do |href|
|
42
|
+
Addressable::URI.parse(href)
|
43
|
+
end
|
44
|
+
base.element ns, with: { rel: rel }, as: "atom_#{rel}_link_rel".to_sym, value: :rel
|
45
|
+
base.element ns, with: { rel: rel }, as: "atom_#{rel}_link_type".to_sym, value: :type
|
46
|
+
end
|
47
|
+
end
|
46
48
|
end
|
47
49
|
end
|
48
50
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Feedjira
|
2
|
+
module Podcast
|
3
|
+
module Channel
|
4
|
+
class Cloud
|
5
|
+
include SAXMachine
|
6
|
+
include FeedUtilities
|
7
|
+
|
8
|
+
attribute :domain
|
9
|
+
attribute :port
|
10
|
+
attribute :path
|
11
|
+
attribute :registerProcedure
|
12
|
+
attribute :protocol
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -2,17 +2,19 @@ module Feedjira
|
|
2
2
|
module Podcast
|
3
3
|
module Channel
|
4
4
|
module Feedburner
|
5
|
-
|
6
|
-
|
7
|
-
base.element :"feedburner:info", as: :feedburner_info_uri, value: :uri do |uri|
|
8
|
-
Addressable::URI.parse(uri)
|
9
|
-
end
|
10
|
-
|
5
|
+
module InstanceMethods
|
11
6
|
def feedburner
|
12
7
|
info = Struct.new(:uri).new(feedburner_info_uri)
|
13
8
|
@feedburner ||= Struct.new(:info).new(info)
|
14
9
|
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.include(InstanceMethods)
|
15
14
|
|
15
|
+
base.element :"feedburner:info", as: :feedburner_info_uri, value: :uri do |uri|
|
16
|
+
Addressable::URI.parse(uri)
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|