my_episodes 0.1.0

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.
@@ -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