├── .gitignore
├── .travis.yml
├── README.md
├── config
└── config.exs
├── lib
├── feeder_ex.ex
└── feeder_ex
│ └── parser.ex
├── mix.exs
├── mix.lock
└── test
├── feeder_ex
└── parser_test.exs
├── feeder_ex_test.exs
├── sample-rss-2.xml
└── test_helper.exs
/.gitignore:
--------------------------------------------------------------------------------
1 | /_build
2 | /deps
3 | erl_crash.dump
4 | *.ez
5 | /doc
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: elixir
2 | notifications:
3 | email:
4 | - mk@manukall.de
5 | elixir:
6 | - 1.2.6
7 | - 1.3.4
8 | env: MIX_ENV=test
9 | otp_release:
10 | - 18.3
11 | - 19.2
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | FeederEx
2 | ========
3 | [](https://travis-ci.org/manukall/feeder_ex)
4 |
5 | Simple wrapper for https://github.com/michaelnisi/feeder
6 |
7 | ## Usage
8 |
9 | ```
10 | iex> HTTPoison.start
11 | iex> {:ok, %HTTPoison.Response{body: body}} = HTTPoison.get("https://www.rssboard.org/files/sample-rss-2.xml")
12 | iex> {:ok, feed, _} = FeederEx.parse(body)
13 | ...
14 | iex> feed.title
15 | "Liftoff News"
16 | iex> Enum.map feed.entries, fn(entry) -> entry.title end
17 | ["Star City", :undefined, "The Engine That Does More",
18 | "Astronauts' Dirty Laundry"]
19 | ```
20 |
21 | ## Results
22 |
23 | ### FeederEx.Feed
24 |
25 | - author
26 | - id
27 | - image
28 | - link
29 | - language
30 | - subtitle
31 | - summary
32 | - title
33 | - updated
34 | - url
35 | - entries
36 |
37 | ### FeederEx.Entry
38 |
39 | - author
40 | - categories
41 | - duration
42 | - enclosure
43 | - id
44 | - image
45 | - link
46 | - subtitle
47 | - summary
48 | - title
49 | - updated
50 |
51 |
52 | ### FeederEx.Enclosure
53 |
54 | - url
55 | - size
56 | - type
57 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | use Mix.Config
4 |
5 | # This configuration is loaded before any dependency and is restricted
6 | # to this project. If another project depends on this project, this
7 | # file won't be loaded nor affect the parent project. For this reason,
8 | # if you want to provide default values for your application for third-
9 | # party users, it should be done in your mix.exs file.
10 |
11 | # Sample configuration:
12 | #
13 | # config :logger, :console,
14 | # level: :info,
15 | # format: "$date $time [$level] $metadata$message\n",
16 | # metadata: [:user_id]
17 |
18 | # It is also possible to import configuration files, relative to this
19 | # directory. For example, you can emulate configuration per environment
20 | # by uncommenting the line below and defining dev.exs, test.exs and such.
21 | # Configuration from the imported file will override the ones defined
22 | # here (which is why it is important to import them last).
23 | #
24 | # import_config "#{Mix.env}.exs"
25 |
--------------------------------------------------------------------------------
/lib/feeder_ex.ex:
--------------------------------------------------------------------------------
1 | defmodule FeederEx do
2 | defmodule Feed do
3 | defstruct author: nil, id: nil, image: nil, link: nil, language: nil,
4 | subtitle: nil, summary: nil, title: nil, updated: nil, url: nil, entries: []
5 | end
6 |
7 | defmodule Entry do
8 | defstruct author: nil, categories: [], duration: nil, enclosure: nil, id: nil,
9 | image: nil, link: nil, subtitle: nil, summary: nil, title: nil,
10 | updated: nil
11 | end
12 |
13 | defmodule Enclosure do
14 | defstruct url: nil, size: nil, type: nil
15 | end
16 |
17 | def parse_file(filename) do
18 | :feeder.file filename, opts()
19 | end
20 |
21 | def parse(feed_bin) do
22 | :feeder.stream feed_bin, opts()
23 | end
24 |
25 | def parse!(feed_bin) do
26 | {:ok, feed, _} = parse(feed_bin)
27 | feed
28 | end
29 |
30 | defp opts do
31 | [event_state: {nil, []}, event_fun: &FeederEx.Parser.event/2]
32 | end
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/lib/feeder_ex/parser.ex:
--------------------------------------------------------------------------------
1 | defmodule FeederEx.Parser do
2 |
3 | def event({:feed,
4 | {:feed, author, id, image, language, link, subtitle, summary, title, updated, url}},
5 | {_, entries}) do
6 | feed = %FeederEx.Feed{
7 | author: undefined_to_nil(author),
8 | id: undefined_to_nil(id),
9 | image: undefined_to_nil(image),
10 | language: undefined_to_nil(language),
11 | link: undefined_to_nil(link),
12 | subtitle: undefined_to_nil(subtitle),
13 | summary: undefined_to_nil(summary),
14 | title: undefined_to_nil(title),
15 | updated: undefined_to_nil(updated),
16 | url: undefined_to_nil(url)
17 | }
18 | {feed, entries}
19 | end
20 |
21 | def event({:entry,
22 | {:entry, author, categories, duration, enclosure, id, image, link, subtitle, summary, title, updated}},
23 | {feed, entries}) do
24 | entry = %FeederEx.Entry{
25 | author: undefined_to_nil(author),
26 | categories: undefined_to_list(categories),
27 | duration: undefined_to_nil(duration),
28 | enclosure: parse_enclosure(enclosure),
29 | id: undefined_to_nil(id),
30 | image: undefined_to_nil(image),
31 | link: undefined_to_nil(link),
32 | subtitle: undefined_to_nil(subtitle),
33 | summary: undefined_to_nil(summary),
34 | title: undefined_to_nil(title),
35 | updated: undefined_to_nil(updated)
36 | }
37 | {feed, [entry | entries]}
38 | end
39 |
40 | def event(:endFeed, {nil, entries}), do: %FeederEx.Feed{entries: entries}
41 | def event(:endFeed, {feed, entries}) do
42 | %{feed | entries: Enum.reverse(entries)}
43 | end
44 |
45 | defp undefined_to_list(:undefined), do: []
46 | defp undefined_to_list(value), do: value
47 |
48 | defp undefined_to_nil(:undefined), do: nil
49 | defp undefined_to_nil(value), do: value
50 |
51 | defp parse_enclosure(:undefined), do: nil
52 | defp parse_enclosure({:enclosure, url, size, type}) do
53 | %FeederEx.Enclosure{
54 | url: undefined_to_nil(url),
55 | size: undefined_to_nil(size),
56 | type: undefined_to_nil(type)
57 | }
58 | end
59 |
60 | end
61 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule FeederEx.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [app: :feeder_ex,
6 | version: "1.1.0",
7 | elixir: "~> 1.0",
8 | deps: deps(),
9 | package: package()]
10 | end
11 |
12 | # Configuration for the OTP application
13 | #
14 | # Type `mix help compile.app` for more information
15 | def application do
16 | [applications: [:logger, :feeder]]
17 | end
18 |
19 | # Dependencies can be Hex packages:
20 | #
21 | # {:mydep, "~> 0.3.0"}
22 | #
23 | # Or git/path repositories:
24 | #
25 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
26 | #
27 | # Type `mix help deps` for more examples and options
28 | defp deps do
29 | [{:feeder, "~> 2.2"},
30 | {:ex_doc, ">= 0.0.0", only: :dev}]
31 | end
32 |
33 | defp package do
34 | [maintainers: ["Manuel Kallenbach"],
35 | licenses: ["MIT"],
36 | description: "RSS feed parser. Simple wrapper for feeder.",
37 | links: %{"GitHub" => "https://github.com/manukall/feeder_ex",
38 | "Feeder" => "https://github.com/michaelnisi/feeder"}]
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{"earmark": {:hex, :earmark, "1.0.1", "2c2cd903bfdc3de3f189bd9a8d4569a075b88a8981ded9a0d95672f6e2b63141", [:mix], []},
2 | "ex_doc": {:hex, :ex_doc, "0.13.0", "aa2f8fe4c6136a2f7cfc0a7e06805f82530e91df00e2bff4b4362002b43ada65", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]},
3 | "feeder": {:hex, :feeder, "2.2.1", "9b1236d32cf971a049968b4c3955fa4808bd132b347af6e30a06fc2093751796", [:make], []}}
4 |
--------------------------------------------------------------------------------
/test/feeder_ex/parser_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FeederEx.ParserExTest do
2 | use ExUnit.Case
3 |
4 | test "feed event" do
5 | event_data = {
6 | :feed,
7 | "the author",
8 | "the id",
9 | "the image",
10 | "the language",
11 | "the link",
12 | "the subtitle",
13 | "the summary",
14 | "the title",
15 | "the updated date",
16 | "the feed url"
17 | }
18 | {feed, :entries} = FeederEx.Parser.event({:feed, event_data}, {nil, :entries})
19 |
20 | assert feed.author == "the author"
21 | assert feed.id == "the id"
22 | assert feed.image == "the image"
23 | assert feed.language == "the language"
24 | assert feed.link == "the link"
25 | assert feed.subtitle == "the subtitle"
26 | assert feed.summary == "the summary"
27 | assert feed.title == "the title"
28 | assert feed.updated == "the updated date"
29 | assert feed.url == "the feed url"
30 | end
31 |
32 | test "entry event without enclosure" do
33 | event_data = {
34 | :entry,
35 | "the author",
36 | ["News"],
37 | "the duration",
38 | :undefined,
39 | "the id",
40 | "the image",
41 | "the link",
42 | "the subtitle",
43 | "the summary",
44 | "the title",
45 | "the updated date"
46 | }
47 | entries = [:entry_1, :entry_2]
48 | {:feed, [entry, :entry_1, :entry_2]} =
49 | FeederEx.Parser.event({:entry, event_data}, {:feed, entries})
50 |
51 | assert entry.author == "the author"
52 | assert entry.duration == "the duration"
53 | assert entry.enclosure == nil
54 | assert entry.id == "the id"
55 | assert entry.image == "the image"
56 | assert entry.link == "the link"
57 | assert entry.subtitle == "the subtitle"
58 | assert entry.summary == "the summary"
59 | assert entry.title == "the title"
60 | assert entry.updated == "the updated date"
61 | end
62 |
63 | test "entry event with enclosure" do
64 | event_data = {
65 | :entry,
66 | "the author",
67 | ["News"],
68 | "the duration",
69 | {:enclosure, "http://www.example.com/enclosure.mp3", "123456", "audio/mpeg"},
70 | "the id",
71 | "the image",
72 | "the link",
73 | "the subtitle",
74 | "the summary",
75 | "the title",
76 | "the updated date"
77 | }
78 | {:feed, [entry]} =
79 | FeederEx.Parser.event({:entry, event_data}, {:feed, []})
80 |
81 | assert entry.enclosure == %FeederEx.Enclosure{url: "http://www.example.com/enclosure.mp3",
82 | size: "123456", type: "audio/mpeg"}
83 | end
84 |
85 | end
86 |
--------------------------------------------------------------------------------
/test/feeder_ex_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FeederExTest do
2 | use ExUnit.Case
3 |
4 | @sample_file "test/sample-rss-2.xml"
5 |
6 | test "parsing a file" do
7 | {:ok, feed, _} = FeederEx.parse_file(@sample_file)
8 | assert feed.author == nil
9 | assert feed.id == nil
10 | assert feed.image == nil
11 | assert feed.link == "http://liftoff.msfc.nasa.gov/"
12 | assert feed.language == "en-us"
13 | assert feed.subtitle == nil
14 | assert feed.summary == "Liftoff to Space Exploration."
15 | assert feed.title == "Liftoff News"
16 | assert feed.updated == "Tue, 10 Jun 2003 04:00:00 GMT"
17 | assert feed.url == "http://liftoff.msfc.nasa.gov/feed/"
18 | assert length(feed.entries) == 4
19 |
20 | [entry | _] = feed.entries
21 | assert entry.author == nil
22 | assert entry.duration == nil
23 | assert entry.enclosure == nil
24 | assert entry.categories == ["Current Events", "News"]
25 | assert entry.id == "http://liftoff.msfc.nasa.gov/2003/06/03.html#item573"
26 | assert entry.image == nil
27 | assert entry.link == "http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp"
28 | assert entry.subtitle == nil
29 | assert entry.summary == "How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's Star City."
30 | assert entry.title == "Star City"
31 | assert entry.updated == "Tue, 03 Jun 2003 09:39:21 GMT"
32 |
33 | end
34 |
35 | test "parsing a binary" do
36 | bin_feed = File.read! @sample_file
37 | {:ok, feed, _} = FeederEx.parse(bin_feed)
38 | assert feed.title == "Liftoff News"
39 | end
40 |
41 | test "parse! returns just the parsed results on success" do
42 | parsed_feed = FeederEx.parse!(File.read!(@sample_file))
43 | assert parsed_feed.title == "Liftoff News"
44 | end
45 |
46 | test "parse! raises an error on failure" do
47 | assert_raise MatchError, fn ->
48 | FeederEx.parse!("")
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/test/sample-rss-2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Liftoff News
5 | http://liftoff.msfc.nasa.gov/
6 | http://liftoff.msfc.nasa.gov/feed/
7 | Liftoff to Space Exploration.
8 | en-us
9 | Tue, 10 Jun 2003 04:00:00 GMT
10 | Tue, 10 Jun 2003 09:41:01 GMT
11 | http://blogs.law.harvard.edu/tech/rss
12 | Weblog Editor 2.0
13 | editor@example.com
14 | webmaster@example.com
15 | -
16 | Star City
17 |
18 |
19 | http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp
20 | How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's <a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm">Star City</a>.
21 | Tue, 03 Jun 2003 09:39:21 GMT
22 | http://liftoff.msfc.nasa.gov/2003/06/03.html#item573
23 |
24 | -
25 |
26 | Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a <a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm">partial eclipse of the Sun</a> on Saturday, May 31st.
27 | Fri, 30 May 2003 11:06:42 GMT
28 | http://liftoff.msfc.nasa.gov/2003/05/30.html#item572
29 |
30 | -
31 | The Engine That Does More
32 | http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp
33 | Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that.
34 | Tue, 27 May 2003 08:37:32 GMT
35 | http://liftoff.msfc.nasa.gov/2003/05/27.html#item571
36 |
37 | -
38 | Astronauts' Dirty Laundry
39 |
40 | http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp
41 | Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options.
42 | Tue, 20 May 2003 08:56:02 GMT
43 | http://liftoff.msfc.nasa.gov/2003/05/20.html#item570
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------