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 +4 -4
- data/README.md +51 -29
- data/bin/ep +21 -35
- data/episode.gemspec +1 -1
- data/lib/episode.rb +163 -98
- data/lib/episode/config.rb +10 -9
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 263e396c9794568fd7737300004b2db81ae475e1debd03e5b5d40757748c90e9
|
|
4
|
+
data.tar.gz: b5679b21c41ba6aa3adfb51a298b36645a2e449d3e92a6714b15933e3eee68a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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 `
|
|
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
|
|
30
|
+
Watch next episode
|
|
30
31
|
```
|
|
31
|
-
ep
|
|
32
|
+
ep next
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Watch with mpv
|
|
36
|
+
```
|
|
37
|
+
ep 7 -v mpv
|
|
32
38
|
```
|
|
33
39
|
|
|
34
|
-
Make
|
|
40
|
+
Make mpv default player for this directory (add `-g` to make it global)
|
|
35
41
|
```
|
|
36
|
-
ep set viewer
|
|
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
|
|
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
|
|
55
|
-
ep
|
|
56
|
-
ep 7
|
|
57
|
-
ep next
|
|
58
|
-
ep set viewer
|
|
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
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
(
|
|
65
|
-
(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
(c) cfg
|
|
69
|
-
set <param> <value>
|
|
70
|
-
(r) reset [param]
|
|
71
|
-
|
|
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>
|
|
81
|
+
-v, --viewer <program> Specify what viewer to use (i.e. `ep 7 -v mpv`)
|
|
77
82
|
|
|
78
|
-
|
|
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
|
|
21
|
-
ep
|
|
22
|
-
ep 7
|
|
23
|
-
ep next
|
|
24
|
-
ep set viewer
|
|
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
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
(
|
|
31
|
-
(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
(c) cfg
|
|
35
|
-
set <param> <value>
|
|
36
|
-
(r) reset [param]
|
|
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 `
|
|
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>',
|
|
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
|
|
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
|
-
|
|
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
|
data/episode.gemspec
CHANGED
data/lib/episode.rb
CHANGED
|
@@ -4,13 +4,13 @@ require 'io/console'
|
|
|
4
4
|
require_relative 'episode/config'
|
|
5
5
|
|
|
6
6
|
class Episode
|
|
7
|
-
VERSION =
|
|
8
|
-
VERSION_PATCH =
|
|
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,
|
|
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
|
-
|
|
26
|
-
|
|
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 =
|
|
30
|
-
id_formatted = id_fixed.to_s.rjust
|
|
31
|
-
separator =
|
|
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 "#{
|
|
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
|
-
|
|
61
|
+
pluralize seconds_ago, 'second'
|
|
51
62
|
else
|
|
52
63
|
[
|
|
53
|
-
days_ago > 0 ?
|
|
54
|
-
hours_ago > 0 ?
|
|
55
|
-
minutes_ago > 0 ?
|
|
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
|
-
|
|
76
|
+
play_last
|
|
66
77
|
end
|
|
67
78
|
|
|
68
79
|
alias l last
|
|
69
80
|
|
|
70
81
|
def next
|
|
71
|
-
config.last =
|
|
72
|
-
|
|
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
|
-
|
|
95
|
+
play_last
|
|
80
96
|
end
|
|
81
97
|
|
|
82
98
|
alias p prev
|
|
83
99
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
182
|
-
.select { |path|
|
|
183
|
-
.
|
|
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
|
-
|
|
188
|
-
ep
|
|
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
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
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.
|
|
210
|
-
`#{PROGRAM_NAME} set last <
|
|
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
|
-
|
|
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 <
|
|
219
|
-
`#{PROGRAM_NAME} reset last` -- to
|
|
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
|
|
227
|
-
@last_id ||= episodes.find_index(last_safe)
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def play
|
|
248
|
+
def play_last
|
|
231
249
|
if name?
|
|
232
|
-
puts
|
|
250
|
+
puts last_name
|
|
233
251
|
else
|
|
234
|
-
$stderr.puts "Viewing #{
|
|
235
|
-
|
|
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
|
-
|
|
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
|
|
250
|
-
when '
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
Invalid value '#{value}' for '
|
|
254
|
-
Should be
|
|
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, "
|
|
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
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
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
|
data/lib/episode/config.rb
CHANGED
|
@@ -3,8 +3,9 @@ require 'time'
|
|
|
3
3
|
|
|
4
4
|
class Episode
|
|
5
5
|
DEFAULT_CFG = {
|
|
6
|
-
|
|
7
|
-
|
|
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 :
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
107
|
-
@
|
|
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:
|
|
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-
|
|
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.
|
|
43
|
+
version: 2.5.0
|
|
44
44
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
45
|
requirements:
|
|
46
46
|
- - ">="
|