episode 1.0.1 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e65279af7dc3120562b0eedba7e01d9dc1fe9b2a20936c8a74222800446cf7f
4
- data.tar.gz: 71bb10b4f5b61e51e3c129418a6a92f51fbdb262b59e188a3771b6cc3a6361fc
3
+ metadata.gz: 263e396c9794568fd7737300004b2db81ae475e1debd03e5b5d40757748c90e9
4
+ data.tar.gz: b5679b21c41ba6aa3adfb51a298b36645a2e449d3e92a6714b15933e3eee68a0
5
5
  SHA512:
6
- metadata.gz: 9ae5ac631b343ded9fd35b1f21b10bc810c2b49522cb237107f1bb233c4cc547193527421070eea8d3a37ec9e71d27a4600db0c939a6c816e7daef4b12c7fd0d
7
- data.tar.gz: d9026415809722fcaa7d55c4631d07434b91ef529fe377308ebe81d63c9d998b8d4748989d007c614da571ddd3b1e519a5e0e9b938cb3d1ddc478024a901886e
6
+ metadata.gz: 5c608e982171334b9244748ff82ff494a458ba425ee515723e4c52741c2df9303627208e1eb7965923640e5c91ad459aca5727c46ee56dd9347e9e11624397c8
7
+ data.tar.gz: 6c5e34367b68c5abbc8b87a0abb6013cd91074c25aa3989732db78ed4362814e42945d05f3825fa2b78787f4da44c0e60c30dc2a308a893003f7a8f274f9c71e
data/README.md CHANGED
@@ -3,6 +3,7 @@ Remembers what file in the directory you viewed last time.
3
3
  Enumerates all files in the directory and allows to reference them only by a number.
4
4
 
5
5
  ## Installation
6
+ Requires Ruby >= 2.5.0.
6
7
 
7
8
  #### Using `gem`
8
9
  ```
@@ -10,11 +11,11 @@ gem install episode
10
11
  ```
11
12
 
12
13
  #### Manual
13
- Clone this repository and add `bin/ep` to your `$PATH`.
14
+ Clone this repository and add `episode/bin` to your `$PATH`.
14
15
 
15
16
  ## Quick Start
16
17
 
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
+ 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 `xdg-open` as the viewer. Read [examples](#viewing-different-file-formats) below to see how to change this behavior.
18
19
 
19
20
  List episodes in the current directory.
20
21
  ```
@@ -26,14 +27,19 @@ Watch episode #7
26
27
  ep 7
27
28
  ```
28
29
 
29
- Watch with VLC
30
+ Watch next episode
30
31
  ```
31
- ep 7 -v vlc
32
+ ep next
33
+ ```
34
+
35
+ Watch with mpv
36
+ ```
37
+ ep 7 -v mpv
32
38
  ```
33
39
 
34
- Make VLC default player for this directory (add `-g` to make it global)
40
+ Make mpv default player for this directory (add `-g` to make it global)
35
41
  ```
36
- ep set viewer vlc
42
+ ep set viewer mpv
37
43
  ```
38
44
 
39
45
  Show config for the current directory
@@ -46,36 +52,36 @@ Show global config
46
52
  ep cfg -g
47
53
  ```
48
54
 
49
- ## Usage (`ep help`)
55
+ ## Usage (`ep -h`)
50
56
  ```
51
- Usage: ep <command> [options]
52
-
57
+ Usage: ep <command> [options]
58
+
53
59
  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)
60
+ ep ls Show episodes in the current directory with their numbers
61
+ ep Same as `ep ls`
62
+ ep 7 Play episode #7
63
+ ep next Play next episode (or first)
64
+ ep set viewer mpv -g Use mpv as default file viewer (by default it's xdg-open)
59
65
 
60
66
  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`:
67
+ ls List all episodes and their numbers
68
+ <number-or-file> Same as `ep play <number-or-file>` (i.e. `ep 11`)
69
+ play <number-or-file> Play episode (i.e. `ep play 11`)
70
+ (s) status Show information about last view
71
+ (l) last Re-play episode watched last time
72
+ (n) next Play next episode
73
+ (p) prev Play previous episode (one before 'last')
74
+ (c) cfg Show config
75
+ set <param> <value> Set config parameter (i.e. `ep set last 11`)
76
+ (r) reset [param] Reset config parameter (i.e. `ep reset last`)
77
+
78
+ Options for `play`, `last`, `next`, and `prev`:
74
79
  -n, --name Show episode name, but don't play it (i.e. `ep -n 11`)
75
80
  -o, --no-update Don't update .episode file
76
- -v, --viewer <program> Set viewer
81
+ -v, --viewer <program> Specify what viewer to use (i.e. `ep 7 -v mpv`)
77
82
 
78
- Options for `cfg`, `set` and `reset`:
83
+
84
+ Options for `cfg`, `set`, and `reset`:
79
85
  -g, --global Edit (or show) global config ($HOME/.config/episode)
80
86
  ```
81
87
 
@@ -119,3 +125,19 @@ ep set viewer 'hexdump -C'
119
125
  # Invoking:
120
126
  ep next | less
121
127
  ```
128
+
129
+ #### Viewing files from read-only directories
130
+ Since `episode` creates an `.episode` file with local configuration in the directory, you can't use it in read-only directories.
131
+ However, we can work around this limitation by changing `dir` parameter.
132
+
133
+ First, create a new directory
134
+ ```
135
+ mkdir placeholder && cd placeholder
136
+ ```
137
+
138
+ Then set parameter `dir` to point to the target directory
139
+ ```
140
+ ep set dir /path/to/readonly/directory
141
+ ```
142
+
143
+ This way `episode` will list and track files from `/path/to/readonly/directory` instead of `placeholder` directory.
data/bin/ep CHANGED
@@ -9,6 +9,7 @@ CFG_GLOBAL_DIR = ENV['XDG_CONFIG_HOME'] || File.join(ENV['HOME'], '.config')
9
9
  CFG_GLOBAL_PATH = File.join(CFG_GLOBAL_DIR, 'episode')
10
10
 
11
11
  options = {}
12
+
12
13
  opt_parser =
13
14
  OptionParser.new do |opts|
14
15
  opts.program_name = 'episode'
@@ -17,27 +18,26 @@ opt_parser =
17
18
  Usage: #{PROGRAM_NAME} <command> [options]
18
19
 
19
20
  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)
21
+ ep ls Show episodes in the current directory with their numbers
22
+ ep Same as `ep ls`
23
+ ep 7 Play episode #7
24
+ ep next Play next episode (or first)
25
+ ep set viewer mpv -g Use mpv as default file viewer (by default it's xdg-open)
25
26
 
26
27
  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
28
+ ls List all episodes and their numbers
29
+ <number-or-file> Same as `#{PROGRAM_NAME} play <number-or-file>` (i.e. `#{PROGRAM_NAME} 11`)
30
+ play <number-or-file> Play episode (i.e. `#{PROGRAM_NAME} play 11`)
31
+ (s) status Show information about last view
32
+ (l) last Re-play episode watched last time
33
+ (n) next Play next episode
34
+ (p) prev Play previous episode (one before 'last')
35
+ (c) cfg Show config
36
+ set <param> <value> Set config parameter (i.e. `#{PROGRAM_NAME} set last 11`)
37
+ (r) reset [param] Reset config parameter (i.e. `#{PROGRAM_NAME} reset last`)
38
38
  EOS
39
39
 
40
- opts.separator("\n Options for `last`, `next`, `prev`, and `no`:")
40
+ opts.separator("\n Options for `play`, `last`, `next`, and `prev`:")
41
41
 
42
42
  opts.on('-n', '--name', "Show episode name, but don't play it (i.e. `#{PROGRAM_NAME} -n 11`)") do
43
43
  options[:name] = true
@@ -47,11 +47,11 @@ opt_parser =
47
47
  options[:update] = false
48
48
  end
49
49
 
50
- opts.on('-v', '--viewer <program>', 'Set viewer') do |viewer|
50
+ opts.on('-v', '--viewer <program>', "Specify what viewer to use (i.e. `#{PROGRAM_NAME} 7 -v mpv`)") do |viewer|
51
51
  options[:viewer] = viewer
52
52
  end
53
53
 
54
- opts.separator("\n Options for `cfg`, `set` and `reset`:")
54
+ opts.separator("\n Options for `cfg`, `set`, and `reset`:")
55
55
 
56
56
  opts.on('-g', '--global', "Edit (or show) global config (#{CFG_GLOBAL_PATH})") do
57
57
  options[:global] = true
@@ -60,22 +60,8 @@ opt_parser =
60
60
 
61
61
  begin
62
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
-
63
+ command = ARGV[0] || 'ls'
64
+ args = ARGV[1..-1]
79
65
  Episode.new(options).public_send(command, *args)
80
66
  rescue OptionParser::ParseError => e
81
67
  $stderr.puts <<~EOS
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  'episode.gemspec'
19
19
  ]
20
20
 
21
- s.required_ruby_version = '>= 2.3.0'
21
+ s.required_ruby_version = '>= 2.5.0'
22
22
  s.bindir = 'bin'
23
23
  s.executables = ['ep']
24
24
 
@@ -4,13 +4,13 @@ require 'io/console'
4
4
  require_relative 'episode/config'
5
5
 
6
6
  class Episode
7
- VERSION = 1
8
- VERSION_PATCH = 1
7
+ VERSION = 2
8
+ VERSION_PATCH = 0
9
9
 
10
10
  class NoCommandError < StandardError; end
11
11
  class CommandError < StandardError; end
12
12
 
13
- def initialize(opts)
13
+ def initialize(opts = {})
14
14
  @is_name = opts[:name] || false
15
15
  @is_update = opts[:update] != false
16
16
  @is_global = opts[:global] || false
@@ -19,22 +19,33 @@ class Episode
19
19
 
20
20
  def ls
21
21
  if episodes.empty?
22
- raise CommandError, 'No episodes found in the directory'
22
+ raise CommandError, <<~EOS
23
+ No episodes found in the directory.
24
+ Currently #{PROGRAM_NAME} is looking for the following formats: #{config.formats.join ', '}.
25
+ To change this run:
26
+ `ep set formats fmt1,fmt2,fmt3` -- to set list of formats for the current directory
27
+ `ep set formats fmt1,fmt2,fmt3 -g` -- to make it global
28
+ EOS
23
29
  end
24
30
 
25
- total = episodes.size
26
- padding = Math.log10(config.index_from_zero ? total - 1 : total).floor + 1
31
+ largest_id = episodes.size - 1 + config.index_from
32
+ id_length = Math.log10([largest_id, 1].max).floor + 1
27
33
 
28
34
  episodes.each_with_index do |filename, id|
29
- id_fixed = config.index_from_zero ? id : id + 1
30
- id_formatted = id_fixed.to_s.rjust(padding, '0')
31
- separator = (config.last && id == last_id) ? config.pointer : '|'
35
+ id_fixed = id + config.index_from
36
+ id_formatted = id_fixed.to_s.rjust id_length, '0'
37
+ separator =
38
+ if config.last && id_fixed == last_id
39
+ config.pointer
40
+ else
41
+ '|'
42
+ end
32
43
  puts "#{id_formatted} #{separator} #{filename}"
33
44
  end
34
45
  end
35
46
 
36
47
  def status
37
- puts "#{config.index_from_zero ? last_id : last_id + 1} #{config.pointer} #{config.last}"
48
+ puts "#{last_id} #{config.pointer} #{config.last}"
38
49
 
39
50
  unless config.last_played_at
40
51
  puts 'Time unknown'
@@ -47,13 +58,13 @@ class Episode
47
58
  minutes_ago = (seconds_ago % 3600) / 60
48
59
  time_passed =
49
60
  if seconds_ago < 60
50
- "#{seconds_ago} second#{seconds_ago == 1 ? '' : 's'}"
61
+ pluralize seconds_ago, 'second'
51
62
  else
52
63
  [
53
- days_ago > 0 ? "#{days_ago} day#{days_ago == 1 ? '' : 's'}" : nil,
54
- hours_ago > 0 ? "#{hours_ago} hour#{hours_ago == 1 ? '' : 's'}" : nil,
55
- minutes_ago > 0 ? "#{minutes_ago} minute#{minutes_ago == 1 ? '' : 's'}" : nil
56
- ].compact.join(" ")
64
+ days_ago > 0 ? pluralize(days_ago, 'day') : nil,
65
+ hours_ago > 0 ? pluralize(hours_ago, 'hour') : nil,
66
+ minutes_ago > 0 ? pluralize(minutes_ago, 'minute') : nil
67
+ ].compact.join ' '
57
68
  end
58
69
 
59
70
  puts "#{config.last_played_at} (#{time_passed} ago)"
@@ -62,41 +73,43 @@ class Episode
62
73
  alias s status
63
74
 
64
75
  def last
65
- play
76
+ play_last
66
77
  end
67
78
 
68
79
  alias l last
69
80
 
70
81
  def next
71
- config.last = config.last ? episode_by_id(last_id + 1) : episodes.first
72
- play
82
+ config.last =
83
+ if config.last
84
+ episode_by_id(last_id + 1)
85
+ else
86
+ episodes.first
87
+ end
88
+ play_last
73
89
  end
74
90
 
75
91
  alias n next
76
92
 
77
93
  def prev
78
94
  config.last = episode_by_id(last_id - 1)
79
- play
95
+ play_last
80
96
  end
81
97
 
82
98
  alias p prev
83
99
 
84
- def no(n_str)
85
- n =
86
- begin
87
- Integer(n_str.gsub /^0*(\d)/, '\\1')
88
- rescue ArgumentError
89
- raise CommandError, <<~EOS
90
- Invalid value '#{n_str}'.
91
- `no` command expects natural number.
92
- EOS
93
- end
94
- config.last = episode_by_id(n - 1)
95
- play
100
+ def play(ref)
101
+ config.last = parse_episode_ref ref
102
+ play_last
96
103
  end
97
104
 
98
105
  def cfg
99
- cfg_h = global? ? config.global.to_h : config.to_h
106
+ cfg_h =
107
+ if global?
108
+ config.global.to_h
109
+ else
110
+ config.to_h
111
+ end
112
+
100
113
  cfg_h.each { |param, val| puts "#{param}: #{val}" }
101
114
  end
102
115
 
@@ -118,7 +131,12 @@ class Episode
118
131
  end
119
132
 
120
133
  cfg.send "#{param}=", parse_config_value(param, value)
121
- config_save_safe(cfg_path, cfg)
134
+
135
+ if param == 'last'
136
+ cfg.last_played_at = nil
137
+ end
138
+
139
+ config_save cfg_path, cfg
122
140
  rescue NotLocal
123
141
  raise CommandError, "Parameter '#{param}' is not global"
124
142
  end
@@ -127,19 +145,25 @@ class Episode
127
145
  cfg_path = global? ? CFG_GLOBAL_PATH : CFG_FILENAME
128
146
 
129
147
  if param.nil?
130
- puts "Reset all config parameters (delete #{cfg_path})? (y|N)"
131
- FileUtils.rm_f(cfg_path) if 'y' == $stdin.getch
148
+ $stderr.puts "Reset all config parameters (delete #{cfg_path})? (y|N)"
149
+ if 'y' == $stdin.getch
150
+ safe_rm cfg_path
151
+ end
132
152
  else
133
- set(param, nil)
153
+ set param, nil
134
154
  end
135
155
  end
136
-
156
+
137
157
  alias r reset
138
158
 
139
159
  private
140
160
 
141
161
  def method_missing(*args)
142
- raise NoCommandError, args.first
162
+ if args.size == 1
163
+ play args.first.to_s
164
+ else
165
+ raise NoCommandError, args.first
166
+ end
143
167
  end
144
168
 
145
169
  def name?
@@ -154,90 +178,85 @@ class Episode
154
178
  @is_global
155
179
  end
156
180
 
181
+ def viewer
182
+ @viewer || config.viewer
183
+ end
184
+
157
185
  def config
158
- return @config if @config
186
+ @config ||=
187
+ if File.exists? CFG_FILENAME
188
+ File.open(CFG_FILENAME, 'r') { |io| Config.load io, global: config_global }
189
+ else
190
+ Config.new(global: config_global)
191
+ end
192
+ end
159
193
 
160
- global_cfg =
194
+ def config_global
195
+ @config_global ||=
161
196
  if File.exists? CFG_GLOBAL_PATH
162
197
  File.open(CFG_GLOBAL_PATH, 'r') { |io| Config.load io }
163
198
  else
164
199
  Config.new
165
200
  end
166
-
167
- @config =
168
- if File.exists? CFG_FILENAME
169
- File.open(CFG_FILENAME, 'r') { |io| Config.load io, global: global_cfg }
170
- else
171
- Config.new(global: global_cfg)
172
- end
173
- end
174
-
175
- def viewer
176
- @viewer || config.viewer
177
201
  end
178
202
 
179
203
  def episodes
180
204
  @episodes ||=
181
- Dir["./*{#{config.formats.join(',')}}"]
182
- .select { |path| File.file? path }
183
- .map { |path| File.basename(path) }.sort
205
+ Dir.entries(config.dir)
206
+ .select { |path| path =~ /(#{config.formats.join '|'})$/ }
207
+ .sort
184
208
  end
185
209
 
186
210
  def episode_by_id(id)
187
- ep = episodes[id] if id >= 0
188
- ep || raise(CommandError, 'Episode not found')
211
+ id_fixed = id - config.index_from
212
+ ep = episodes[id_fixed] if id_fixed >= 0
213
+ ep || raise(CommandError, "Episode ##{id} not found")
189
214
  end
190
215
 
191
- def parse_episode_ref(ref)
192
- case
193
- when File.file?(ref)
194
- ref
195
- when ref =~ /^\d+$/
196
- ref_i = ref.to_i
197
- id = config.index_from_zero ? ref_i : ref_i - 1
198
- episode_by_id(id)
199
- else
200
- raise CommandError, "Can't parse episode reference '#{ref}'"
201
- end
216
+ def last_id
217
+ config.index_from + episodes.find_index(last_name)
218
+ end
219
+
220
+ def last_name
221
+ File.basename last_path
202
222
  end
203
223
 
204
- def last_safe
224
+ def last_path
205
225
  unless config.last
206
226
  raise CommandError, <<~EOS
207
227
  Last episode is undefined.
208
228
  Please run:
209
- `#{PROGRAM_NAME} #{config.index_from_zero ? 0 : 1}` or `ep next` -- to watch first episode
210
- `#{PROGRAM_NAME} set last <episode-number>` or `#{PROGRAM_NAME} set last <file-name>` -- to define where to start from
229
+ `#{PROGRAM_NAME} #{config.index_from}` or `ep next` -- to watch first episode
230
+ `#{PROGRAM_NAME} set last <number-or-file>` -- to define where to start from
211
231
  EOS
212
232
  end
213
233
 
214
- unless File.file? config.last
234
+ path = File.join config.dir, config.last
235
+
236
+ if File.file? path
237
+ path
238
+ else
215
239
  raise CommandError, <<~EOS
216
240
  '#{config.last}' doesn't exist.
217
- To fix it run:
218
- `#{PROGRAM_NAME} set last <file-name>` or `#{PROGRAM_NAME} set last <episode-number>` -- to define last file
219
- `#{PROGRAM_NAME} reset last` -- to erase erroneous value
241
+ It looks like '.episode' file is damaged. To fix it run:
242
+ `#{PROGRAM_NAME} set last <numer-or-file>` -- to define last file
243
+ `#{PROGRAM_NAME} reset last` -- to reset erroneous value
220
244
  EOS
221
245
  end
222
-
223
- config.last
224
246
  end
225
247
 
226
- def last_id
227
- @last_id ||= episodes.find_index(last_safe)
228
- end
229
-
230
- def play
248
+ def play_last
231
249
  if name?
232
- puts last_safe
250
+ puts last_name
233
251
  else
234
- $stderr.puts "Viewing #{last_safe}"
235
- system(*viewer.split(' '), File.join(config.dir, last_safe))
252
+ $stderr.puts "Viewing ##{last_id} #{config.pointer} #{last_name}"
253
+ viewer_with_options = viewer.split ' '
254
+ system *viewer_with_options, last_path
236
255
  end
237
256
 
238
257
  if update?
239
258
  config.last_played_at = Time.now
240
- config_save_safe(CFG_FILENAME, config)
259
+ config_save CFG_FILENAME, config
241
260
  end
242
261
  end
243
262
 
@@ -246,20 +265,18 @@ class Episode
246
265
 
247
266
  case param
248
267
  when 'last'
249
- parse_episode_ref(value)
250
- when 'index_from_zero'
251
- unless %w[true false].include? value
252
- raise CommandError, <<~EOS
253
- Invalid value '#{value}' for 'index_from_zero'.
254
- Should be true or false.
268
+ parse_episode_ref value
269
+ when 'index_from'
270
+ parse_natural_number value,
271
+ err_msg: <<~EOS
272
+ Invalid value '#{value}' for 'index_from'.
273
+ Should be a natural number.
255
274
  EOS
256
- end
257
- value == "true"
258
275
  when 'last_played_at'
259
276
  begin
260
277
  Time.parse value
261
278
  rescue ArgumentError
262
- raise CommandError, "Can't parse time"
279
+ raise CommandError, "Failed to parse time."
263
280
  end
264
281
  when 'pointer'
265
282
  "\"#{value}\"".undump
@@ -270,11 +287,59 @@ class Episode
270
287
  end
271
288
  end
272
289
 
273
- def config_save_safe(path, cfg)
274
- File.open(path, 'w') { |io| cfg.save(io) }
275
- rescue Errno::EACCES
290
+ def parse_episode_ref(ref)
291
+ case
292
+ when File.file?(ref)
293
+ ref
294
+ when ref =~ /^\d+$/
295
+ id = parse_natural_number ref,
296
+ err_msg: <<~EOS
297
+ Can't parse #{ref}.
298
+ Should be a file or a natural number.
299
+ EOS
300
+ episode_by_id id
301
+ else
302
+ raise CommandError, "File '#{ref}' not found"
303
+ end
304
+ end
305
+
306
+ def parse_natural_number(str, err_msg: nil)
307
+ Integer(str.gsub /^0*(\d)/, '\\1')
308
+ rescue ArgumentError
309
+ if err_msg
310
+ raise CommandError, err_msg
311
+ else
312
+ raise CommandError, <<~EOS
313
+ Invalid value '#{str}'.
314
+ Should be a natural number.
315
+ EOS
316
+ end
317
+ end
318
+
319
+ def config_save(path, cfg)
320
+ File.open(path, 'w') { |io| cfg.save io }
321
+ rescue Errno::EACCES => e
322
+ raise CommandError, <<~EOS
323
+ Failed to save episode data.
324
+ #{e.message}
325
+ EOS
326
+ end
327
+
328
+ def safe_rm(path)
329
+ $stderr.puts "rm #{path}"
330
+ FileUtils.rm path
331
+ rescue Errno::ENOENT => e
276
332
  raise CommandError, <<~EOS
277
- Failed to save the episode data: can't write to this directory.
333
+ File #{path} doesn't exist.
278
334
  EOS
335
+ rescue Errno::EACCES => e
336
+ raise CommandError, <<~EOS
337
+ Failed to remove file #{path}
338
+ #{e.message}
339
+ EOS
340
+ end
341
+
342
+ def pluralize(n, word)
343
+ "#{n} #{word}#{n == 1 ? '' : 's'}"
279
344
  end
280
345
  end
@@ -3,8 +3,9 @@ require 'time'
3
3
 
4
4
  class Episode
5
5
  DEFAULT_CFG = {
6
- viewer: 'mpv',
7
- index_from_zero: false,
6
+ dir: '.',
7
+ viewer: 'xdg-open',
8
+ index_from: 1,
8
9
  pointer: '*',
9
10
  formats: %w[mkv mp4 avi]
10
11
  }
@@ -13,7 +14,7 @@ class Episode
13
14
 
14
15
  class Config
15
16
  attr_writer :viewer
16
- attr_writer :index_from_zero
17
+ attr_writer :index_from
17
18
  attr_writer :pointer
18
19
  attr_writer :formats
19
20
 
@@ -34,7 +35,7 @@ class Episode
34
35
  end
35
36
 
36
37
  @viewer = opts[:viewer]
37
- @index_from_zero = opts[:index_from_zero]
38
+ @index_from = opts[:index_from]
38
39
  @pointer = opts[:pointer]
39
40
  @formats = opts[:formats]
40
41
  end
@@ -47,7 +48,7 @@ class Episode
47
48
  if remove_defaults
48
49
  (@local || {}).merge({
49
50
  viewer: @viewer,
50
- index_from_zero: @index_from_zero,
51
+ index_from: @index_from,
51
52
  pointer: @pointer,
52
53
  formats: @formats
53
54
  }).compact
@@ -60,7 +61,7 @@ class Episode
60
61
 
61
62
  (local_h || {}).merge({
62
63
  viewer: viewer,
63
- index_from_zero: index_from_zero,
64
+ index_from: index_from,
64
65
  pointer: pointer,
65
66
  formats: formats
66
67
  })
@@ -76,7 +77,7 @@ class Episode
76
77
  end
77
78
 
78
79
  def dir
79
- get_local(:dir) { Dir.pwd }
80
+ get_local :dir
80
81
  end
81
82
 
82
83
  def dir=(new_val)
@@ -103,8 +104,8 @@ class Episode
103
104
  @viewer || default(:viewer)
104
105
  end
105
106
 
106
- def index_from_zero
107
- @index_from_zero || default(:index_from_zero)
107
+ def index_from
108
+ @index_from || default(:index_from)
108
109
  end
109
110
 
110
111
  def pointer
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: episode
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maksim Esterkin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-05 00:00:00.000000000 Z
11
+ date: 2019-12-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Episode (ep) remembers what file in the directory you viewed last time.
14
14
  Enumerates all files in the directory and allows to reference them only by a number.
@@ -40,7 +40,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 2.3.0
43
+ version: 2.5.0
44
44
  required_rubygems_version: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - ">="