episode 1.0.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
+ SHA256:
3
+ metadata.gz: e4f2ae42c41aa916a30370e05e9150baa621cbdffcf1e668f8afc81889f91af8
4
+ data.tar.gz: 2abf7c83c530be2d76497a682e5605370fb57cec86fe066246a840e934ead5ee
5
+ SHA512:
6
+ metadata.gz: 83caee9209e83b7b02a1085bcb33e6f795652436b2470af80366d5b12a231e25bf69194c43c3f4e09b2bdf72d45f7439a6edda6f26f9e135ef8c2db8d01f3493
7
+ data.tar.gz: 483aa5505b1fd3db50b94cdc198f120e394c0fde02675d29df1666a4410ae4a5d193a21e8b36c0eeb8508051905664cfce0a9dbaa6ecf45c1f43bbde2496542d
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Maksim Esterkin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,121 @@
1
+ # Episode (ep)
2
+ Remembers what file in the directory you viewed last time.
3
+ Enumerates all files in the directory and allows to reference them only by a number.
4
+
5
+ ## Installation
6
+
7
+ #### Using `gem`
8
+ ```
9
+ gem install episode
10
+ ```
11
+
12
+ #### Manual
13
+ Clone this repository and add `bin/ep` to your `$PATH`.
14
+
15
+ ## Quick Start
16
+
17
+ Episode creates `.episode` file in the current directory when you open some file with it. By default it will be looking for `mkv`, `avi` or `mp4` files and will use `mpv` as the viewer. Read [examples](#viewing-different-file-formats) below to see how to change this behavior.
18
+
19
+ List episodes in the current directory.
20
+ ```
21
+ ep
22
+ ```
23
+
24
+ Watch episode #7
25
+ ```
26
+ ep 7
27
+ ```
28
+
29
+ Watch with VLC
30
+ ```
31
+ ep 7 -v vlc
32
+ ```
33
+
34
+ Make VLC default player for this directory (add `-g` to make it global)
35
+ ```
36
+ ep set viewer vlc
37
+ ```
38
+
39
+ Show config for the current directory
40
+ ```
41
+ ep cfg
42
+ ```
43
+
44
+ Show global config
45
+ ```
46
+ ep cfg -g
47
+ ```
48
+
49
+ ## Usage (`ep help`)
50
+ ```
51
+ Usage: ep <command> [options]
52
+
53
+ Quick start:
54
+ ep ls show episodes in the current directory with their indexes
55
+ ep same as `ep ls`
56
+ ep 7 play episode #7
57
+ ep next play next episode (or first)
58
+ ep set viewer vlc -g use VLC as default video player (by default it's mpv)
59
+
60
+ Commands:
61
+ ls List all episodes and their idnexes
62
+ (s) status Show information about last view
63
+ (l) last Re-play episode watched last time
64
+ (n) next Play next episode
65
+ (p) prev Play previous episode (one before 'last')
66
+ <number> Same as `ep no <number>` (i.e. `ep 11`)
67
+ no <number> Play episode by number (i.e. `ep no 11`)
68
+ (c) cfg Display config for the current directory
69
+ set <param> <value> Set config parameter (i.e. `ep set last 11`)
70
+ (r) reset [param] Reset config parameter (i.e. `ep reset last`)
71
+ (h) help Show this help
72
+
73
+ Options for `last`, `next`, `prev`, and `no`:
74
+ -n, --name Show episode name, but don't play it (i.e. `ep -n 11`)
75
+ -o, --no-update Don't update .episode file
76
+ -v, --viewer <program> Set viewer
77
+
78
+ Options for `cfg`, `set` and `reset`:
79
+ -g, --global Edit (or show) global config ($HOME/.config/episode)
80
+ ```
81
+
82
+ ## More Examples
83
+
84
+ #### Coloring the 'last' pointer
85
+ ```
86
+ ep -g set pointer '\u001b[33;1m*\u001b[0m'
87
+ ```
88
+ <img src="https://static.hedlx.org/episode_coloring_pointer.png">
89
+
90
+ #### Purging all `episode` data from the directory
91
+ ```
92
+ ep r
93
+ ```
94
+
95
+ #### Restoring global default settings
96
+ ```
97
+ ep r -g
98
+ ```
99
+
100
+ #### Viewing different file formats
101
+
102
+ ###### Pictures
103
+ ```
104
+ ep set formats png,jpg
105
+ ep set viewer feh
106
+ ```
107
+
108
+ ###### PDF
109
+ ```
110
+ ep set formats pdf
111
+ ep set viewer zathura
112
+ ```
113
+
114
+ ###### Any
115
+ ```
116
+ ep set formats ''
117
+ ep set viewer 'hexdump -C'
118
+
119
+ # Invoking:
120
+ ep next | less
121
+ ```
data/bin/ep ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require_relative '../lib/episode'
5
+
6
+ PROGRAM_NAME = File.basename($PROGRAM_NAME)
7
+ CFG_FILENAME = '.episode'
8
+ CFG_GLOBAL_DIR = ENV['XDG_CONFIG_HOME'] || File.join(ENV['HOME'], '.config')
9
+ CFG_GLOBAL_PATH = File.join(CFG_GLOBAL_DIR, 'episode')
10
+
11
+ options = {}
12
+ opt_parser =
13
+ OptionParser.new do |opts|
14
+ opts.program_name = 'episode'
15
+ opts.version = "v#{Episode::VERSION}"
16
+ opts.banner = <<~EOS
17
+ Usage: #{PROGRAM_NAME} <command> [options]
18
+
19
+ Quick start:
20
+ ep ls show episodes in the current directory with their indexes
21
+ ep same as `ep ls`
22
+ ep 7 play episode #7
23
+ ep next play next episode (or first)
24
+ ep set viewer vlc -g use VLC as default video player (by default it's mpv)
25
+
26
+ Commands:
27
+ ls List all episodes and their idnexes
28
+ (s) status Show information about last view
29
+ (l) last Re-play episode watched last time
30
+ (n) next Play next episode
31
+ (p) prev Play previous episode (one before 'last')
32
+ <number> Same as `#{PROGRAM_NAME} no <number>` (i.e. `#{PROGRAM_NAME} 11`)
33
+ no <number> Play episode by number (i.e. `#{PROGRAM_NAME} no 11`)
34
+ (c) cfg Display config for the current directory
35
+ set <param> <value> Set config parameter (i.e. `#{PROGRAM_NAME} set last 11`)
36
+ (r) reset [param] Reset config parameter (i.e. `#{PROGRAM_NAME} reset last`)
37
+ (h) help Show this help
38
+ EOS
39
+
40
+ opts.separator("\n Options for `last`, `next`, `prev`, and `no`:")
41
+
42
+ opts.on('-n', '--name', "Show episode name, but don't play it (i.e. `#{PROGRAM_NAME} -n 11`)") do
43
+ options[:name] = true
44
+ end
45
+
46
+ opts.on('-o', '--no-update', "Don't update .episode file") do
47
+ options[:update] = false
48
+ end
49
+
50
+ opts.on('-v', '--viewer <program>', 'Set viewer') do |viewer|
51
+ options[:viewer] = viewer
52
+ end
53
+
54
+ opts.separator("\n Options for `cfg`, `set` and `reset`:")
55
+
56
+ opts.on('-g', '--global', "Edit (or show) global config (#{CFG_GLOBAL_PATH})") do
57
+ options[:global] = true
58
+ end
59
+ end
60
+
61
+ begin
62
+ opt_parser.parse!
63
+
64
+ case
65
+ when ARGV[0].nil?
66
+ command = 'ls'
67
+ args = []
68
+ when %w[h help].include?(ARGV[0])
69
+ $stderr.puts opt_parser.help
70
+ return
71
+ when ARGV[0] =~ /^\d+$/
72
+ command = 'no'
73
+ args = [ARGV[0]]
74
+ else
75
+ command = ARGV[0]
76
+ args = ARGV[1..]
77
+ end
78
+
79
+ Episode.new(options).public_send(command, *args)
80
+ rescue OptionParser::ParseError => e
81
+ $stderr.puts <<~EOS
82
+ Error: #{e.message}
83
+ Run `#{PROGRAM_NAME} -h` to see the list of available options.
84
+ EOS
85
+ rescue Episode::NoCommandError => e
86
+ $stderr.puts <<~EOS
87
+ Error: Unknown command '#{e.message}'.
88
+ Run `#{PROGRAM_NAME} -h` to see the list of available commands.
89
+ EOS
90
+ rescue Episode::CommandError => e
91
+ $stderr.puts "Error: #{e.message}"
92
+ end
@@ -0,0 +1,31 @@
1
+ require_relative "lib/episode"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'episode'
5
+ s.version = "#{Episode::VERSION}.0.0"
6
+ s.summary = 'Console assistant app for watching series'
7
+ s.license = 'MIT'
8
+ s.description = 'Episode (ep) remembers what file in the directory you viewed last time. Enumerates all files in the directory and allows to reference them only by a number.'
9
+ s.authors = ['Maksim Esterkin']
10
+ s.email = 'esterkimx@gmail.com'
11
+ s.homepage = 'https://github.com/esterkimx/episode'
12
+
13
+ s.files = [
14
+ 'lib/episode.rb',
15
+ 'lib/episode/config.rb',
16
+ 'README.md',
17
+ 'LICENSE.md',
18
+ 'episode.gemspec'
19
+ ]
20
+
21
+ s.required_ruby_version = '>= 2.3.0'
22
+ s.bindir = 'bin'
23
+ s.executables = ['ep']
24
+
25
+ s.metadata = {
26
+ 'bug_tracker_uri' => 'https://github.com/esterkimx/episode/issues',
27
+ 'documentation_uri' => 'https://github.com/esterkimx/episode/blob/master/README.md',
28
+ 'homepage_uri' => 'https://github.com/esterkimx/episode',
29
+ 'source_code_uri' => 'https://github.com/esterkimx/episode'
30
+ }
31
+ end
@@ -0,0 +1,277 @@
1
+ require 'fileutils'
2
+ require 'time'
3
+ require 'io/console'
4
+ require_relative 'episode/config'
5
+
6
+ class Episode
7
+ VERSION = 1
8
+
9
+ class NoCommandError < StandardError; end
10
+ class CommandError < StandardError; end
11
+
12
+ def initialize(opts)
13
+ @is_name = opts[:name] || false
14
+ @is_update = opts[:update] != false
15
+ @is_global = opts[:global] || false
16
+ @viewer = opts[:viewer]
17
+ end
18
+
19
+ def ls
20
+ if episodes.empty?
21
+ raise CommandError, 'No episodes found in the directory'
22
+ end
23
+
24
+ total = episodes.size
25
+ padding = Math.log10(config.index_from_zero ? total - 1 : total).floor + 1
26
+
27
+ episodes.each_with_index do |filename, id|
28
+ id_fixed = config.index_from_zero ? id : id + 1
29
+ id_formatted = id_fixed.to_s.rjust(padding, '0')
30
+ separator = (config.last && id == last_id) ? config.pointer : '|'
31
+ puts "#{id_formatted} #{separator} #{filename}"
32
+ end
33
+ end
34
+
35
+ def status
36
+ puts "#{config.index_from_zero ? last_id : last_id + 1} #{config.pointer} #{config.last}"
37
+
38
+ unless config.last_played_at
39
+ puts 'Time unknown'
40
+ return
41
+ end
42
+
43
+ seconds_ago = (Time.now - config.last_played_at).floor
44
+ days_ago = seconds_ago / (3600 * 24)
45
+ hours_ago = (seconds_ago % (3600 * 24)) / 3600
46
+ minutes_ago = (seconds_ago % 3600) / 60
47
+ time_passed =
48
+ if seconds_ago < 60
49
+ "#{seconds_ago} second#{seconds_ago == 1 ? '' : 's'}"
50
+ else
51
+ [
52
+ days_ago > 0 ? "#{days_ago} day#{days_ago == 1 ? '' : 's'}" : nil,
53
+ hours_ago > 0 ? "#{hours_ago} hour#{hours_ago == 1 ? '' : 's'}" : nil,
54
+ minutes_ago > 0 ? "#{minutes_ago} minute#{minutes_ago == 1 ? '' : 's'}" : nil
55
+ ].compact.join(" ")
56
+ end
57
+
58
+ puts "#{config.last_played_at} (#{time_passed} ago)"
59
+ end
60
+
61
+ alias s status
62
+
63
+ def last
64
+ play
65
+ end
66
+
67
+ alias l last
68
+
69
+ def next
70
+ config.last = config.last ? episode_by_id(last_id + 1) : episodes.first
71
+ play
72
+ end
73
+
74
+ alias n next
75
+
76
+ def prev
77
+ config.last = episode_by_id(last_id - 1)
78
+ play
79
+ end
80
+
81
+ alias p prev
82
+
83
+ def no(n_str)
84
+ n =
85
+ begin
86
+ Integer(n_str.gsub /^0*(\d)/, '\\1')
87
+ rescue ArgumentError
88
+ raise CommandError, <<~EOS
89
+ Invalid value '#{n_str}'.
90
+ `no` command expects natural number.
91
+ EOS
92
+ end
93
+ config.last = episode_by_id(n - 1)
94
+ play
95
+ end
96
+
97
+ def cfg
98
+ cfg_h = global? ? config.global.to_h : config.to_h
99
+ cfg_h.each { |param, val| puts "#{param}: #{val}" }
100
+ end
101
+
102
+ alias c cfg
103
+
104
+ def set(param, value)
105
+ unless config.respond_to? param
106
+ raise CommandError, <<~EOS
107
+ Unknown config parameter '#{param}'.
108
+ Run `#{PROGRAM_NAME} cfg` to see the list of available parameters.
109
+ EOS
110
+ end
111
+
112
+ cfg, cfg_path =
113
+ if global?
114
+ [config.global, CFG_GLOBAL_PATH]
115
+ else
116
+ [config, CFG_FILENAME]
117
+ end
118
+
119
+ cfg.send "#{param}=", parse_config_value(param, value)
120
+ config_save_safe(cfg_path, cfg)
121
+ rescue NotLocal
122
+ raise CommandError, "Parameter '#{param}' is not global"
123
+ end
124
+
125
+ def reset(param = nil)
126
+ cfg_path = global? ? CFG_GLOBAL_PATH : CFG_FILENAME
127
+
128
+ if param.nil?
129
+ puts "Reset all config parameters (delete #{cfg_path})? (y|N)"
130
+ FileUtils.rm_f(cfg_path) if 'y' == $stdin.getch
131
+ else
132
+ set(param, nil)
133
+ end
134
+ end
135
+
136
+ alias r reset
137
+
138
+ private
139
+
140
+ def method_missing(*args)
141
+ raise NoCommandError, args.first
142
+ end
143
+
144
+ def name?
145
+ @is_name
146
+ end
147
+
148
+ def update?
149
+ @is_update
150
+ end
151
+
152
+ def global?
153
+ @is_global
154
+ end
155
+
156
+ def config
157
+ return @config if @config
158
+
159
+ global_cfg =
160
+ if File.exists? CFG_GLOBAL_PATH
161
+ File.open(CFG_GLOBAL_PATH, 'r') { |io| Config.load io }
162
+ end
163
+
164
+ @config =
165
+ if File.exists? CFG_FILENAME
166
+ File.open(CFG_FILENAME, 'r') { |io| Config.load io, global: global_cfg }
167
+ else
168
+ Config.new(global: global_cfg)
169
+ end
170
+ end
171
+
172
+ def viewer
173
+ @viewer || config.viewer
174
+ end
175
+
176
+ def episodes
177
+ @episodes ||=
178
+ Dir["./*{#{config.formats.join(',')}}"]
179
+ .select { |path| File.file? path }
180
+ .map { |path| File.basename(path) }.sort
181
+ end
182
+
183
+ def episode_by_id(id)
184
+ ep = episodes[id] if id >= 0
185
+ ep || raise(CommandError, 'Episode not found')
186
+ end
187
+
188
+ def parse_episode_ref(ref)
189
+ case
190
+ when File.file?(ref)
191
+ ref
192
+ when ref =~ /^\d+$/
193
+ ref_i = ref.to_i
194
+ id = config.index_from_zero ? ref_i : ref_i - 1
195
+ episode_by_id(id)
196
+ else
197
+ raise CommandError, "Can't parse episode reference '#{ref}'"
198
+ end
199
+ end
200
+
201
+ def last_safe
202
+ unless config.last
203
+ raise CommandError, <<~EOS
204
+ Last episode is undefined.
205
+ Please run:
206
+ `#{PROGRAM_NAME} #{config.index_from_zero ? 0 : 1}` or `ep next` -- to watch first episode
207
+ `#{PROGRAM_NAME} set last <episode-number>` or `#{PROGRAM_NAME} set last <file-name>` -- to define where to start from
208
+ EOS
209
+ end
210
+
211
+ unless File.file? config.last
212
+ raise CommandError, <<~EOS
213
+ '#{config.last}' doesn't exist.
214
+ To fix it run:
215
+ `#{PROGRAM_NAME} set last <file-name>` or `#{PROGRAM_NAME} set last <episode-number>` -- to define last file
216
+ `#{PROGRAM_NAME} reset last` -- to erase erroneous value
217
+ EOS
218
+ end
219
+
220
+ config.last
221
+ end
222
+
223
+ def last_id
224
+ @last_id ||= episodes.find_index(last_safe)
225
+ end
226
+
227
+ def play
228
+ if name?
229
+ puts last_safe
230
+ else
231
+ $stderr.puts "Viewing #{last_safe}"
232
+ system(*viewer.split(' '), File.join(config.dir, last_safe))
233
+ end
234
+
235
+ if update?
236
+ config.last_played_at = Time.now
237
+ config_save_safe(CFG_FILENAME, config)
238
+ end
239
+ end
240
+
241
+ def parse_config_value(param, value)
242
+ return if value.nil?
243
+
244
+ case param
245
+ when 'last'
246
+ parse_episode_ref(value)
247
+ when 'index_from_zero'
248
+ unless %w[true false].include? value
249
+ raise CommandError, <<~EOS
250
+ Invalid value '#{value}' for 'index_from_zero'.
251
+ Should be true or false.
252
+ EOS
253
+ end
254
+ value == "true"
255
+ when 'last_played_at'
256
+ begin
257
+ Time.parse value
258
+ rescue ArgumentError
259
+ raise CommandError, "Can't parse time"
260
+ end
261
+ when 'pointer'
262
+ "\"#{value}\"".undump
263
+ when 'formats'
264
+ value.split ','
265
+ else
266
+ value
267
+ end
268
+ end
269
+
270
+ def config_save_safe(path, cfg)
271
+ File.open(path, 'w') { |io| cfg.save(io) }
272
+ rescue Errno::EACCES
273
+ raise CommandError, <<~EOS
274
+ Failed to save the episode data: can't write to this directory.
275
+ EOS
276
+ end
277
+ end
@@ -0,0 +1,133 @@
1
+ require 'json'
2
+ require 'time'
3
+
4
+ class Episode
5
+ DEFAULT_CFG = {
6
+ viewer: 'mpv',
7
+ index_from_zero: false,
8
+ pointer: '*',
9
+ formats: %w[mkv mp4 avi]
10
+ }
11
+
12
+ class NotLocal < StandardError; end
13
+
14
+ class Config
15
+ attr_writer :viewer
16
+ attr_writer :index_from_zero
17
+ attr_writer :pointer
18
+ attr_writer :formats
19
+
20
+ def self.load(io, opts = {})
21
+ new JSON.parse(io.read, symbolize_names: true).merge(opts)
22
+ end
23
+
24
+ def initialize(opts = {})
25
+ @global_cfg = opts[:global]
26
+
27
+ unless global?
28
+ @local =
29
+ {
30
+ dir: opts[:dir],
31
+ last: opts[:last],
32
+ last_played_at: (Time.parse opts[:last_played_at] rescue nil)
33
+ }
34
+ end
35
+
36
+ @viewer = opts[:viewer]
37
+ @index_from_zero = opts[:index_from_zero]
38
+ @pointer = opts[:pointer]
39
+ @formats = opts[:formats]
40
+ end
41
+
42
+ def save(io)
43
+ io.write JSON.pretty_generate(to_h(remove_defaults: true))
44
+ end
45
+
46
+ def to_h(remove_defaults: false)
47
+ if remove_defaults
48
+ (@local || {}).merge({
49
+ viewer: @viewer,
50
+ index_from_zero: @index_from_zero,
51
+ pointer: @pointer,
52
+ formats: @formats
53
+ }).compact
54
+ else
55
+ local_h = {
56
+ dir: dir,
57
+ last: last,
58
+ last_played_at: last_played_at
59
+ } unless global?
60
+
61
+ (local_h || {}).merge({
62
+ viewer: viewer,
63
+ index_from_zero: index_from_zero,
64
+ pointer: pointer,
65
+ formats: formats
66
+ })
67
+ end
68
+ end
69
+
70
+ def global?
71
+ @global_cfg.nil?
72
+ end
73
+
74
+ def global
75
+ @global_cfg
76
+ end
77
+
78
+ def dir
79
+ get_local(:dir) { Dir.pwd }
80
+ end
81
+
82
+ def dir=(new_val)
83
+ set_local :dir, new_val
84
+ end
85
+
86
+ def last
87
+ get_local :last
88
+ end
89
+
90
+ def last=(new_val)
91
+ set_local :last, new_val
92
+ end
93
+
94
+ def last_played_at
95
+ get_local :last_played_at
96
+ end
97
+
98
+ def last_played_at=(new_val)
99
+ set_local :last_played_at, new_val
100
+ end
101
+
102
+ def viewer
103
+ @viewer || default(:viewer)
104
+ end
105
+
106
+ def index_from_zero
107
+ @index_from_zero || default(:index_from_zero)
108
+ end
109
+
110
+ def pointer
111
+ @pointer || default(:pointer)
112
+ end
113
+
114
+ def formats
115
+ @formats || default(:formats)
116
+ end
117
+
118
+ private
119
+
120
+ def get_local(param)
121
+ @local && @local[param] || (block_given? ? yield : default(param))
122
+ end
123
+
124
+ def set_local(param, new_val)
125
+ raise NotLocal if global?
126
+ @local[param] = new_val
127
+ end
128
+
129
+ def default(param)
130
+ @global_cfg&.public_send(param) || DEFAULT_CFG[param]
131
+ end
132
+ end
133
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: episode
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Maksim Esterkin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-12-05 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Episode (ep) remembers what file in the directory you viewed last time.
14
+ Enumerates all files in the directory and allows to reference them only by a number.
15
+ email: esterkimx@gmail.com
16
+ executables:
17
+ - ep
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE.md
22
+ - README.md
23
+ - bin/ep
24
+ - episode.gemspec
25
+ - lib/episode.rb
26
+ - lib/episode/config.rb
27
+ homepage: https://github.com/esterkimx/episode
28
+ licenses:
29
+ - MIT
30
+ metadata:
31
+ bug_tracker_uri: https://github.com/esterkimx/episode/issues
32
+ documentation_uri: https://github.com/esterkimx/episode/blob/master/README.md
33
+ homepage_uri: https://github.com/esterkimx/episode
34
+ source_code_uri: https://github.com/esterkimx/episode
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.3.0
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.0.6
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Console assistant app for watching series
54
+ test_files: []