my_episodes 0.1.0

This diff has not been reviewed by any users.
Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: baf01e0820221ce85012d67c4cddcb60fa91fd62
4
+ data.tar.gz: a898c7f434ce68400188daf639ddd278b4df60f6
5
+ SHA512:
6
+ metadata.gz: f3734bcd664a66aa9c5fd9f2c24b56735e0b2f6ed4135f7907ba3a4fa8214e19d29f3181083414f43c85aa0af4516eb40d081fe554226e1d784a79c264fae33d
7
+ data.tar.gz: 5446854bfa20da02ef4ae8576503567292c3c5f7404a11ac8fb5818cd8c52120001c6967f5e587317bfc2321fc8fe028fd040b1da945f780ad15ec5453586277
@@ -0,0 +1,32 @@
1
+ # my episodes
2
+
3
+ [![Build Status](https://travis-ci.org/mthssdrbrg/my_episodes.png?branch=master)](https://travis-ci.org/mthssdrbrg/my_episodes)
4
+ [![Coverage Status](https://coveralls.io/repos/mthssdrbrg/my_episodes/badge.png?branch=master)](https://coveralls.io/r/mthssdrbrg/my_episodes?branch=master)
5
+
6
+ [My Episodes](http://www.myepisodes.com) helps one keep track of one's TV shows,
7
+ but unfortunately there's no API or no built-in functionality for exporting
8
+ one's history, hence this gem.
9
+
10
+ There's a simple bin script, `myeps`, that can be used to dump your history to
11
+ standard out (and then you can pipe it to a file or whatever).
12
+
13
+ ## Installation
14
+
15
+ ```
16
+ gem install my_episodes
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```
22
+ Usage: myeps [options]
23
+ -D, --dump Dump history from myepisodes.com to CSV
24
+ -u, --username=USERNAME Username for myepisodes.com
25
+ -p, --password=PASSWORD Password for myepisodes.com
26
+ -s, --separator=SEP Separator for CSV output
27
+ -h, --help You're looking at it
28
+ ```
29
+
30
+ ## Copyright
31
+
32
+ Released under the [MIT License](http://www.opensource.org/licenses/MIT) :: 2014 Mathias Söderberg.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ $: << File.expand_path('../../lib', __FILE__)
5
+
6
+ require 'my_episodes'
7
+
8
+
9
+ MyEpisodes::Cli.run(ARGV, $stdout)
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ require 'mechanize'
4
+
5
+
6
+ module MyEpisodes
7
+ MyEpisodesError = Class.new(StandardError)
8
+ AuthenticationError = Class.new(MyEpisodesError)
9
+ end
10
+
11
+ require 'my_episodes/cli'
12
+ require 'my_episodes/client'
13
+ require 'my_episodes/dump'
14
+ require 'my_episodes/episode'
15
+ require 'my_episodes/season'
16
+ require 'my_episodes/show'
17
+ require 'my_episodes/shows'
18
+ require 'my_episodes/table_parser'
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+
3
+ require 'optparse'
4
+
5
+
6
+ module MyEpisodes
7
+ class Cli
8
+ def initialize
9
+ @options = {action: :help}
10
+ @parser = create_parser
11
+ end
12
+
13
+ def parse(argv)
14
+ @parser.parse(argv)
15
+ end
16
+
17
+ def self.run(argv, io)
18
+ cli = self.new
19
+ cli.parse(argv)
20
+ cli.run(io)
21
+ end
22
+
23
+ def run(io)
24
+ case @options[:action]
25
+ when :dump
26
+ Dump.new(client, io, @options).execute
27
+ when :help
28
+ io.puts(@parser)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def client
35
+ @client ||= Client.create(*@options.values_at(:username, :password))
36
+ end
37
+
38
+ def create_parser
39
+ OptionParser.new do |opts|
40
+ opts.on('-D', '--dump', 'Dump history from myepisodes.com to CSV') do
41
+ @options[:action] = :dump
42
+ end
43
+
44
+ opts.on('-u', '--username=USERNAME', 'Username for myepisodes.com') do |username|
45
+ @options[:username] = username
46
+ end
47
+
48
+ opts.on('-p', '--password=PASSWORD', 'Password for myepisodes.com') do |passwd|
49
+ @options[:password] = passwd
50
+ end
51
+
52
+ opts.on('-s', '--separator=SEP', 'Separator for CSV output') do |separator|
53
+ @options[:separator] = separator
54
+ end
55
+
56
+ opts.on('-h', '--help', 'You\'re looking at it') do
57
+ @options[:action] = :help
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,73 @@
1
+ # encoding: utf-8
2
+
3
+ module MyEpisodes
4
+ class Client
5
+ def self.create(username, password, agent=Mechanize.new)
6
+ client = self.new(agent)
7
+ client.login(username, password)
8
+ client
9
+ end
10
+
11
+ def initialize(agent=Mechanize.new)
12
+ @agent = agent
13
+ @page = nil
14
+ end
15
+
16
+ def login(username, password)
17
+ unless @page
18
+ page = @agent.get(login_url)
19
+ form = page.form_with(action: 'login.php', method: 'POST')
20
+ form.username = username
21
+ form.password = password
22
+ form.u = life_wasted
23
+ @page = form.submit(form.buttons.first)
24
+ ensure_logged_in
25
+ end
26
+ end
27
+
28
+ def shows
29
+ @shows ||= Shows.new(parser).create(shows_links).to_a
30
+ end
31
+
32
+ def logout
33
+ if @page
34
+ @page.link_with(text: /Logout/).click
35
+ @page = nil
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def parser
42
+ @parser ||= TableParser.new
43
+ end
44
+
45
+ def shows_links
46
+ @shows_links ||= @page.links_with(href: /views\.php\?type=epsbyshow/)
47
+ end
48
+
49
+ def login_url
50
+ @login_url ||= 'http://www.myepisodes.com/login.php'
51
+ end
52
+
53
+ def life_wasted
54
+ @life_wasted ||= 'life_wasted.php'
55
+ end
56
+
57
+ def still_on_login_page?
58
+ @page.title && @page.title.match(/: Login Page/i)
59
+ end
60
+
61
+ def login_warning?
62
+ warning_text = @page.search('#divContainer div.warning').text.strip
63
+ warning_text && warning_text.match(/wrong username.+password/i)
64
+ end
65
+
66
+ def ensure_logged_in
67
+ if still_on_login_page? && login_warning?
68
+ @page = nil
69
+ raise AuthenticationError, 'wrong username / password'
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ module MyEpisodes
4
+ class Dump
5
+ def initialize(client, output, options={})
6
+ @client = client
7
+ @output = output
8
+ @separator = options.fetch(:separator, ';')
9
+ end
10
+
11
+ def execute
12
+ @output.puts(headers.join(@separator))
13
+ @client.shows.each do |show|
14
+ show.seasons.each do |season|
15
+ season.episodes.each do |episode|
16
+ @output.puts(to_line(show, season, episode).join(@separator))
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def to_line(s, se, e)
25
+ [s.name, se.name, e.number, e.name, e.acquired?, e.watched?]
26
+ end
27
+
28
+ def headers
29
+ @headers ||= %w[show season number episode_name acquired watched].freeze
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ module MyEpisodes
4
+ class Episode
5
+ attr_reader :air_date, :show_name, :number, :name
6
+
7
+ def initialize(season)
8
+ @season = season
9
+ end
10
+
11
+ def create(attrs)
12
+ attrs = sanitize(attrs)
13
+ @air_date = text(attrs[AIR_DATE_FIELD])
14
+ @show_name = text(attrs[SHOW_NAME_FIELD])
15
+ @number = text(attrs[NUMBER_FIELD])
16
+ @name = text(attrs[NAME_FIELD])
17
+ @acquired = checked?(attrs[ACQUIRED_FIELD])
18
+ @watched = checked?(attrs[WATCHED_FIELD])
19
+ self
20
+ end
21
+
22
+ def season
23
+ @season.freeze
24
+ end
25
+
26
+ def acquired?
27
+ !!@acquired
28
+ end
29
+
30
+ def watched?
31
+ !!@watched
32
+ end
33
+
34
+ private
35
+
36
+ def sanitize(attrs)
37
+ attrs.reject.with_index { |a, i| i.even? }
38
+ end
39
+
40
+ def text(a)
41
+ a.text.strip
42
+ end
43
+
44
+ def checked?(a)
45
+ a.children.any? && a.children.last.attr(CHECKED) == CHECKED
46
+ end
47
+
48
+ CHECKED = 'checked'.freeze
49
+ AIR_DATE_FIELD = 0
50
+ SHOW_NAME_FIELD = 1
51
+ NUMBER_FIELD = 2
52
+ NAME_FIELD = 3
53
+ ACQUIRED_FIELD = 4
54
+ WATCHED_FIELD = 5
55
+ end
56
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ module MyEpisodes
4
+ class Season
5
+ attr_reader :name
6
+
7
+ def initialize(show=nil)
8
+ @show = show
9
+ @episodes = []
10
+ end
11
+
12
+ def create(row)
13
+ @name = row.text.strip
14
+ self
15
+ end
16
+
17
+ def <<(episode)
18
+ @episodes << episode
19
+ end
20
+
21
+ def episodes
22
+ @episodes.freeze
23
+ end
24
+
25
+ def show
26
+ @show.freeze
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ module MyEpisodes
4
+ class Show
5
+ attr_reader :name
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ @seasons = []
10
+ end
11
+
12
+ def create(rows)
13
+ season = nil
14
+ rows.each do |row|
15
+ if useless?(row)
16
+ next
17
+ elsif new_season?(row)
18
+ @seasons << Season.new(self).create(row)
19
+ season = @seasons.last
20
+ else
21
+ season << Episode.new(season).create(row.children)
22
+ end
23
+ end
24
+ self
25
+ end
26
+
27
+ def seasons
28
+ @seasons.freeze
29
+ end
30
+
31
+ private
32
+
33
+ def useless?(row)
34
+ row.text.strip.empty?
35
+ end
36
+
37
+ def new_season?(row)
38
+ row.attr(CLASS) == HEADER
39
+ end
40
+
41
+ CLASS = 'class'.freeze
42
+ HEADER = 'header'.freeze
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ module MyEpisodes
4
+ class Shows
5
+ include Enumerable
6
+
7
+ def initialize(parser)
8
+ @shows = []
9
+ @parser = parser
10
+ end
11
+
12
+ def create(links)
13
+ links.each do |link|
14
+ name = link.text
15
+ page = link.click
16
+ rows = @parser.parse(page)
17
+ @shows << Show.new(name).create(rows)
18
+ end
19
+
20
+ self
21
+ end
22
+
23
+ def each(&block)
24
+ @shows.each(&block)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ module MyEpisodes
4
+ class TableParser
5
+ def initialize(options={})
6
+ @options = options
7
+ @search_string = 'table.mylist tr'.freeze
8
+ end
9
+
10
+ def parse(page)
11
+ table = page.search(@search_string)
12
+ table.shift unless @options[:keep_header]
13
+ table
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module MyEpisodes
4
+ VERSION = '0.1.0'.freeze
5
+ end
@@ -0,0 +1,77 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module MyEpisodes
7
+ describe Cli, vcr: {cassette_name: 'my_episodes'} do
8
+ let :io do
9
+ StringIO.new
10
+ end
11
+
12
+ describe '-D / --dump' do
13
+ let :argv do
14
+ %w[--dump -u demo -p demo]
15
+ end
16
+
17
+ let :output do
18
+ io.string.split("\n")
19
+ end
20
+
21
+ before do
22
+ described_class.run(argv, io)
23
+ end
24
+
25
+ it 'dumps history to $stdout' do
26
+ expect(output.grep(/Akame ga KILL!/)).to_not be_empty
27
+ expect(output.grep(/Blake's 7/)).to_not be_empty
28
+ expect(output.grep(/Doctor Who/)).to_not be_empty
29
+ expect(output.grep(/Under the Dome/)).to_not be_empty
30
+ expect(output.grep(/Wilfred \(US\)/)).to_not be_empty
31
+ expect(output.size).to eq(587)
32
+ end
33
+
34
+ it 'includes headers' do
35
+ expect(output.first).to eq('show;season;number;episode_name;acquired;watched')
36
+ end
37
+
38
+ context 'with -s / --separator' do
39
+ let :argv do
40
+ %w[--dump -u demo -p demo -s ':']
41
+ end
42
+
43
+ it 'uses given character as separator' do
44
+ expect(output.grep(/.+:.+:.+/)).to_not be_empty
45
+ end
46
+ end
47
+ end
48
+
49
+ describe '-h / --help' do
50
+ let :argv do
51
+ %w[--help]
52
+ end
53
+
54
+ before do
55
+ described_class.run(argv, io)
56
+ end
57
+
58
+ it 'prints usage' do
59
+ expect(io.string).to match(/^Usage: /)
60
+ end
61
+ end
62
+
63
+ describe 'without any action' do
64
+ let :argv do
65
+ []
66
+ end
67
+
68
+ before do
69
+ described_class.run(argv, io)
70
+ end
71
+
72
+ it 'prints usage' do
73
+ expect(io.string).to match(/^Usage: /)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module MyEpisodes
7
+ describe Client do
8
+ let :client do
9
+ described_class.create('demo', 'demo')
10
+ end
11
+
12
+ after do
13
+ client.logout
14
+ end
15
+
16
+ describe '#login' do
17
+ let :client do
18
+ described_class.new
19
+ end
20
+
21
+ context 'when given valid username and password', vcr: {cassette_name: 'login-fail'} do
22
+ it 'does not raise an error' do
23
+ expect { client.login('demo', 'demo') }.to_not raise_error
24
+ end
25
+ end
26
+
27
+ context 'when given invalid username and password', vcr: {cassette_name: 'login-success'} do
28
+ it 'raises an error' do
29
+ expect { client.login('demo', 'this is wrong') }.to raise_error(AuthenticationError)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe '#shows', vcr: {cassette_name: 'my_episodes'} do
35
+ it 'parses the expected number of shows' do
36
+ expect(client.shows.size).to eq(5)
37
+ end
38
+
39
+ it 'parses seasons as expected' do
40
+ show = client.shows.find { |s| s.name == 'Doctor Who' }
41
+ expect(show.seasons.size).to eq(22)
42
+ show.seasons.each do |season|
43
+ expect(season.name).to_not be_nil
44
+ expect(season.name).to_not be_empty
45
+ end
46
+ end
47
+
48
+ it 'parses episodes as expected' do
49
+ show = client.shows.find { |s| s.name == 'Under the Dome' }
50
+ seasons = show.seasons
51
+ expect(seasons.size).to eq(2)
52
+ expect(seasons.first.episodes.size).to eq(1)
53
+ expect(seasons.last.episodes.size).to eq(11)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module MyEpisodes
7
+ describe Episode do
8
+ let :episode do
9
+ described_class.new(season)
10
+ end
11
+
12
+ let :season do
13
+ Season.new('Season 1')
14
+ end
15
+
16
+ let :attributes do
17
+ Nokogiri::HTML(File.read('spec/support/resources/episode.html'))
18
+ .children.last.children.first.children.first.children
19
+ end
20
+
21
+ it 'belongs to a Season' do
22
+ expect(episode.season).to be_a(Season)
23
+ end
24
+
25
+ describe '#create' do
26
+ before do
27
+ episode.create(attributes)
28
+ end
29
+
30
+ it 'parses air date' do
31
+ expect(episode.air_date).to eq('17-Aug-2014')
32
+ end
33
+
34
+ it 'parses show name' do
35
+ expect(episode.show_name).to eq('Akame ga KILL!')
36
+ end
37
+
38
+ it 'parses number' do
39
+ expect(episode.number).to eq('S01E07')
40
+ end
41
+
42
+ it 'parses name' do
43
+ expect(episode.name).to eq('Season 1, Episode 7')
44
+ end
45
+
46
+ it 'parses `acquired` state' do
47
+ expect(episode).to be_acquired
48
+ end
49
+
50
+ it 'parses `watched` state' do
51
+ expect(episode).to_not be_watched
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module MyEpisodes
7
+ describe Season do
8
+ let :season do
9
+ described_class.new(show)
10
+ end
11
+
12
+ let :show do
13
+ Show.new('Show')
14
+ end
15
+
16
+ describe '#create' do
17
+ it 'sets name' do
18
+ row = double(:row, text: 'Season #1')
19
+ season.create(row)
20
+ expect(season.name).to eq('Season #1')
21
+ end
22
+
23
+ it 'strips whitespace characters' do
24
+ row = double(:row, text: " Season #1\t\t\n")
25
+ season.create(row)
26
+ expect(season.name).to eq('Season #1')
27
+ end
28
+
29
+ it 'returns self' do
30
+ expect(season.create(double(:row, text: 'Season #1'))).to eq(season)
31
+ end
32
+ end
33
+
34
+ describe '#show' do
35
+ it 'returns given show, but frozen' do
36
+ expect(season.show).to eq(show)
37
+ expect(season.show).to be_frozen
38
+ end
39
+ end
40
+
41
+ describe '#<<' do
42
+ it 'adds an Episode to list of episodes' do
43
+ episode = Episode.new(season)
44
+ season << episode
45
+ expect(season.episodes.size).to eq(1)
46
+ expect(season.episodes).to eq([episode])
47
+ end
48
+ end
49
+
50
+ describe '#episodes' do
51
+ it 'returns a frozen list of episodes' do
52
+ expect(season.episodes).to be_frozen
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module MyEpisodes
7
+ describe Show do
8
+ let :show do
9
+ described_class.new('Show')
10
+ end
11
+
12
+ let :rows do
13
+ Nokogiri::HTML(File.read('spec/support/resources/show.html'))
14
+ .children.last.children.first.children
15
+ end
16
+
17
+ describe '#name' do
18
+ it 'returns the name of the show' do
19
+ expect(show.name).to eq('Show')
20
+ end
21
+ end
22
+
23
+ describe '#create' do
24
+ before do
25
+ show.create(rows)
26
+ end
27
+
28
+ it 'parses consecutive seasons' do
29
+ expect(show.seasons.size).to eq(2)
30
+ end
31
+
32
+ it 'parses episodes correctly' do
33
+ seasons = show.seasons
34
+ expect(seasons.first.episodes.size).to eq(3)
35
+ expect(seasons.last.episodes.size).to eq(1)
36
+ end
37
+ end
38
+
39
+ describe '#seasons' do
40
+ it 'returns a frozen list of seasons' do
41
+ expect(show.seasons).to be_frozen
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ require 'vcr'
4
+ require 'simplecov'
5
+ require 'coveralls'
6
+ require 'stringio'
7
+
8
+
9
+ VCR.configure do |c|
10
+ c.cassette_library_dir = 'spec/support/cassettes'
11
+ c.hook_into :webmock
12
+ c.configure_rspec_metadata!
13
+ end
14
+
15
+ if ENV.include?('TRAVIS')
16
+ Coveralls.wear!
17
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
18
+ end
19
+
20
+ SimpleCov.start do
21
+ add_group 'Source', 'lib'
22
+ add_group 'Unit tests', 'spec/my_episodes'
23
+ add_group 'Integration tests', 'spec/integration'
24
+ end
25
+
26
+ require 'my_episodes'
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: my_episodes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mathias Söderberg
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mechanize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
27
+ description: Export history from myepisodes.com
28
+ email: mths@sdrbrg.se
29
+ executables:
30
+ - myeps
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - bin/myeps
36
+ - lib/my_episodes.rb
37
+ - lib/my_episodes/cli.rb
38
+ - lib/my_episodes/client.rb
39
+ - lib/my_episodes/dump.rb
40
+ - lib/my_episodes/episode.rb
41
+ - lib/my_episodes/season.rb
42
+ - lib/my_episodes/show.rb
43
+ - lib/my_episodes/shows.rb
44
+ - lib/my_episodes/table_parser.rb
45
+ - lib/my_episodes/version.rb
46
+ - spec/integration/cli_integration_spec.rb
47
+ - spec/integration/client_integration_spec.rb
48
+ - spec/my_episodes/episode_spec.rb
49
+ - spec/my_episodes/season_spec.rb
50
+ - spec/my_episodes/show_spec.rb
51
+ - spec/spec_helper.rb
52
+ homepage: https://github.com/mthssdrbrg/my_episodes
53
+ licenses:
54
+ - MIT
55
+ metadata: {}
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 1.9.3
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 2.2.2
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: Simple myepisodes.com client
76
+ test_files:
77
+ - spec/integration/cli_integration_spec.rb
78
+ - spec/integration/client_integration_spec.rb
79
+ - spec/my_episodes/episode_spec.rb
80
+ - spec/my_episodes/season_spec.rb
81
+ - spec/my_episodes/show_spec.rb
82
+ - spec/spec_helper.rb