elch_scan 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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +112 -0
- data/Rakefile +1 -0
- data/VERSION +1 -0
- data/bin/elch_scan +14 -0
- data/doc/config.yml +44 -0
- data/doc/filter.rb +70 -0
- data/elch_scan.gemspec +29 -0
- data/elch_scan.rb +2 -0
- data/lib/active_support/number_helper/number_converter.rb +182 -0
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +21 -0
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +58 -0
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +92 -0
- data/lib/banana/logger.rb +258 -0
- data/lib/elch_scan/application/dispatch.rb +190 -0
- data/lib/elch_scan/application/filter.rb +34 -0
- data/lib/elch_scan/application.rb +125 -0
- data/lib/elch_scan/formatter/base.rb +17 -0
- data/lib/elch_scan/formatter/html.rb +9 -0
- data/lib/elch_scan/formatter/plain.rb +76 -0
- data/lib/elch_scan/movie.rb +92 -0
- data/lib/elch_scan/version.rb +4 -0
- data/lib/elch_scan.rb +31 -0
- metadata +169 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
module ActiveSupport
|
2
|
+
module NumberHelper
|
3
|
+
class NumberToRoundedConverter < NumberConverter # :nodoc:
|
4
|
+
self.namespace = :precision
|
5
|
+
self.validate_float = true
|
6
|
+
|
7
|
+
def convert
|
8
|
+
precision = options.delete :precision
|
9
|
+
significant = options.delete :significant
|
10
|
+
|
11
|
+
case number
|
12
|
+
when Float, String
|
13
|
+
@number = BigDecimal(number.to_s)
|
14
|
+
when Rational
|
15
|
+
if significant
|
16
|
+
@number = BigDecimal(number, digit_count(number.to_i) + precision)
|
17
|
+
else
|
18
|
+
@number = BigDecimal(number, precision)
|
19
|
+
end
|
20
|
+
else
|
21
|
+
@number = number.to_d
|
22
|
+
end
|
23
|
+
|
24
|
+
if significant && precision > 0
|
25
|
+
digits, rounded_number = digits_and_rounded_number(precision)
|
26
|
+
precision -= digits
|
27
|
+
precision = 0 if precision < 0 # don't let it be negative
|
28
|
+
else
|
29
|
+
rounded_number = number.round(precision)
|
30
|
+
rounded_number = rounded_number.to_i if precision == 0
|
31
|
+
rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
|
32
|
+
end
|
33
|
+
|
34
|
+
formatted_string =
|
35
|
+
case rounded_number
|
36
|
+
when BigDecimal
|
37
|
+
s = rounded_number.to_s('F') + '0'*precision
|
38
|
+
a, b = s.split('.', 2)
|
39
|
+
a + '.' + b[0, precision]
|
40
|
+
else
|
41
|
+
"%01.#{precision}f" % rounded_number
|
42
|
+
end
|
43
|
+
|
44
|
+
delimited_number = NumberToDelimitedConverter.convert(formatted_string, options)
|
45
|
+
format_number(delimited_number)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def digits_and_rounded_number(precision)
|
51
|
+
if zero?
|
52
|
+
[1, 0]
|
53
|
+
else
|
54
|
+
digits = digit_count(number)
|
55
|
+
multiplier = 10 ** (digits - precision)
|
56
|
+
rounded_number = calculate_rounded_number(multiplier)
|
57
|
+
digits = digit_count(rounded_number) # After rounding, the number of digits may have changed
|
58
|
+
[digits, rounded_number]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def calculate_rounded_number(multiplier)
|
63
|
+
(number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier
|
64
|
+
end
|
65
|
+
|
66
|
+
def digit_count(number)
|
67
|
+
(Math.log10(absolute_number(number)) + 1).floor
|
68
|
+
end
|
69
|
+
|
70
|
+
def strip_insignificant_zeros
|
71
|
+
options[:strip_insignificant_zeros]
|
72
|
+
end
|
73
|
+
|
74
|
+
def format_number(number)
|
75
|
+
if strip_insignificant_zeros
|
76
|
+
escaped_separator = Regexp.escape(options[:separator])
|
77
|
+
number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
|
78
|
+
else
|
79
|
+
number
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def absolute_number(number)
|
84
|
+
number.respond_to?(:abs) ? number.abs : number.to_d.abs
|
85
|
+
end
|
86
|
+
|
87
|
+
def zero?
|
88
|
+
number.respond_to?(:zero?) ? number.zero? : number.to_d.zero?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
module Banana
|
2
|
+
# This class provides a simple logger which maintains and displays the runtime of the logger instance.
|
3
|
+
# It is intended to be used with the colorize gem but it brings its own colorization to eliminate a
|
4
|
+
# dependency. You should initialize the logger as soon as possible to get an accurate runtime.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# logger = Banana::Logger.new
|
8
|
+
# logger.log "foo"
|
9
|
+
# logger.prefix = "[a] "
|
10
|
+
# logger.debug "bar"
|
11
|
+
# sleep 2
|
12
|
+
# logger.ensure_prefix("[b] ") { logger.warn "baz" }
|
13
|
+
# logger.log("rab!", :abort)
|
14
|
+
#
|
15
|
+
# # Result:
|
16
|
+
# # [00:00:00.000 INFO] foo
|
17
|
+
# # [00:00:00.000 DEBUG] [a] bar
|
18
|
+
# # [00:00:02.001 WARN] [b] baz
|
19
|
+
# # [00:00:02.001 ABORT] [a] rab!
|
20
|
+
#
|
21
|
+
# @!attribute [r] startup
|
22
|
+
# @return [DateTime] Point of time where the logger was initiated.
|
23
|
+
# @!attribute [r] channel
|
24
|
+
# @return [Object] The object where messages are getting send to.
|
25
|
+
# @!attribute [r] method
|
26
|
+
# @return [Object] The method used to send messages to {#channel}.
|
27
|
+
# @!attribute [r] logged
|
28
|
+
# @return [Integer] Amount of messages send to channel.
|
29
|
+
# @note This counter starts from 0 in every {#ensure_method} or {#log_with_print} block but gets
|
30
|
+
# added to the main counter after the call.
|
31
|
+
# @!attribute timestr
|
32
|
+
# @return [Boolean] Set to false if the runtime indicator should not be printed (default: true).
|
33
|
+
# @!attribute colorize
|
34
|
+
# @return [Boolean] Set to false if output should not be colored (default: true).
|
35
|
+
# @!attribute prefix
|
36
|
+
# @return [String] Current prefix string for logged messages.
|
37
|
+
class Logger
|
38
|
+
attr_reader :startup, :channel, :method, :logged
|
39
|
+
attr_accessor :colorize, :prefix
|
40
|
+
|
41
|
+
# Foreground color values
|
42
|
+
COLORMAP = {
|
43
|
+
black: 30,
|
44
|
+
red: 31,
|
45
|
+
green: 32,
|
46
|
+
yellow: 33,
|
47
|
+
blue: 34,
|
48
|
+
magenta: 35,
|
49
|
+
cyan: 36,
|
50
|
+
white: 37,
|
51
|
+
}
|
52
|
+
|
53
|
+
# Initializes a new logger instance. The internal runtime measurement starts here!
|
54
|
+
#
|
55
|
+
# There are 4 default log levels: info (yellow), warn & abort (red) and debug (blue).
|
56
|
+
# All are enabled by default. You propably want to {#disable disable(:debug)}.
|
57
|
+
#
|
58
|
+
# @param scope Only for backward compatibility, not used
|
59
|
+
# @todo add option hash
|
60
|
+
def initialize scope = nil
|
61
|
+
@startup = Time.now.utc
|
62
|
+
@colorize = true
|
63
|
+
@prefix = ""
|
64
|
+
@enabled = true
|
65
|
+
@timestr = true
|
66
|
+
@channel = ::Kernel
|
67
|
+
@method = :puts
|
68
|
+
@logged = 0
|
69
|
+
@levels = {
|
70
|
+
info: { color: "yellow", enabled: true },
|
71
|
+
warn: { color: "red", enabled: true },
|
72
|
+
abort: { color: "red", enabled: true },
|
73
|
+
debug: { color: "blue", enabled: true },
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
# The default channel is `Kernel` which is Ruby's normal `puts`.
|
78
|
+
# Attach it to a open file with write access and it will write into
|
79
|
+
# the file. Ensure to close the file in your code.
|
80
|
+
#
|
81
|
+
# @param channel Object which responds to puts and print
|
82
|
+
def attach channel
|
83
|
+
@channel = channel
|
84
|
+
end
|
85
|
+
|
86
|
+
# Print raw message onto {#channel} using {#method}.
|
87
|
+
#
|
88
|
+
# @param [String] msg Message to send to {#channel}.
|
89
|
+
# @param [Symbol] method Override {#method}.
|
90
|
+
def raw msg, method = @method
|
91
|
+
@channel.send(method, msg)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Add additional log levels which are automatically enabled.
|
95
|
+
# @param [Hash] levels Log levels in the format `{ debug: "red" }`
|
96
|
+
def log_level levels = {}
|
97
|
+
levels.each do |level, color|
|
98
|
+
@levels[level.to_sym] ||= { enabled: true }
|
99
|
+
@levels[level.to_sym][:color] = color
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Calls the block with the given prefix and ensures that the prefix
|
104
|
+
# will be the same as before.
|
105
|
+
#
|
106
|
+
# @param [String] prefix Prefix to use for the block
|
107
|
+
# @param [Proc] block Block to call
|
108
|
+
def ensure_prefix prefix, &block
|
109
|
+
old_prefix, @prefix = @prefix, prefix
|
110
|
+
block.call
|
111
|
+
ensure
|
112
|
+
@prefix = old_prefix
|
113
|
+
end
|
114
|
+
|
115
|
+
# Calls the block after changing the output method.
|
116
|
+
# It also ensures to set back the method to what it was before.
|
117
|
+
#
|
118
|
+
# @param [Symbol] method Method to call on {#channel}
|
119
|
+
def ensure_method method, &block
|
120
|
+
old_method, old_logged = @method, @logged
|
121
|
+
@method, @logged = method, 0
|
122
|
+
block.call
|
123
|
+
ensure
|
124
|
+
@method = old_method
|
125
|
+
@logged += old_logged
|
126
|
+
end
|
127
|
+
|
128
|
+
# Calls the block after changing the output method to `:print`.
|
129
|
+
# It also ensures to set back the method to what it was before.
|
130
|
+
#
|
131
|
+
# @param [Boolean] clear If set to true and any message was printed inside the block
|
132
|
+
# a \n newline character will be printed.
|
133
|
+
def log_with_print clear = true, &block
|
134
|
+
ensure_method :print do
|
135
|
+
begin
|
136
|
+
block.call
|
137
|
+
ensure
|
138
|
+
puts if clear && @logged > 0
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Calls the block after disabling the runtime indicator.
|
144
|
+
# It also ensures to set back the old setting after execution.
|
145
|
+
def log_without_timestr &block
|
146
|
+
timestr, @timestr = @timestr, false
|
147
|
+
block.call
|
148
|
+
ensure
|
149
|
+
@timestr = timestr
|
150
|
+
end
|
151
|
+
|
152
|
+
# @return [Boolean] returns true if the log level format :debug is enabled.
|
153
|
+
def debug?
|
154
|
+
enabled? :debug
|
155
|
+
end
|
156
|
+
|
157
|
+
# If a `level` is provided it will return true if this log level is enabled.
|
158
|
+
# If no `level` is provided it will return true if the logger is enabled generally.
|
159
|
+
#
|
160
|
+
# @return [Boolean] returns true if the given log level is enabled
|
161
|
+
def enabled? level = nil
|
162
|
+
level.nil? ? @enabled : @levels[level.to_sym][:enabled]
|
163
|
+
end
|
164
|
+
|
165
|
+
# Same as {#enabled?} just negated.
|
166
|
+
def disabled? level = nil
|
167
|
+
!enabled?(level)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Same as {#enable} just negated.
|
171
|
+
#
|
172
|
+
# @param [Symbol, String] level Loglevel to disable.
|
173
|
+
def disable level = nil
|
174
|
+
if level.nil?
|
175
|
+
@enabled = false
|
176
|
+
else
|
177
|
+
@levels[level.to_sym][:enabled] = false
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Enables the given `level` or the logger generally if no `level` is given.
|
182
|
+
# If the logger is disabled no messages will be printed. If it is enabled
|
183
|
+
# only messages for enabled log levels will be printed.
|
184
|
+
#
|
185
|
+
# @param [Symbol, String] level Loglevel to enable.
|
186
|
+
def enable level = nil
|
187
|
+
if level.nil?
|
188
|
+
@enabled = true
|
189
|
+
else
|
190
|
+
@levels[level.to_sym][:enabled] = true
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Colorizes the given string with the given color. It uses the build-in
|
195
|
+
# colorization feature. You may want to use the colorize gem.
|
196
|
+
#
|
197
|
+
# @param [String] str The string to colorize
|
198
|
+
# @param [Symbol, String] color The color to use (see {COLORMAP})
|
199
|
+
# @raise [ArgumentError] if color does not exist in {COLORMAP}.
|
200
|
+
def colorize str, color
|
201
|
+
ccode = COLORMAP[color.to_sym] || raise(ArgumentError, "Unknown color #{color}!")
|
202
|
+
"\e[#{ccode}m#{str}\e[0m"
|
203
|
+
end
|
204
|
+
|
205
|
+
def colorize?
|
206
|
+
@colorize
|
207
|
+
end
|
208
|
+
|
209
|
+
# This method is the only method which sends the message `msg` to `@channel` via `@method`.
|
210
|
+
# It also increments the message counter `@logged` by one.
|
211
|
+
#
|
212
|
+
# This method instantly returns nil if the logger or the given log level `type` is disabled.
|
213
|
+
#
|
214
|
+
# @param [String] msg The message to send to the channel
|
215
|
+
# @param [Symbol] type The log level
|
216
|
+
def log msg, type = :info
|
217
|
+
return if !@enabled || !@levels[type][:enabled]
|
218
|
+
if @levels[type.to_sym] || !@levels.key?(type.to_sym)
|
219
|
+
time = Time.at(Time.now.utc - @startup).utc
|
220
|
+
timestr = @timestr ? "[#{time.strftime("%H:%M:%S.%L")} #{type.to_s.upcase}]\t" : ""
|
221
|
+
|
222
|
+
if @colorize
|
223
|
+
msg = "#{colorize(timestr, @levels[type.to_sym][:color])}" <<
|
224
|
+
"#{@prefix}" <<
|
225
|
+
"#{colorize(msg, @levels[type.to_sym][:color])}"
|
226
|
+
else
|
227
|
+
msg = "#{timestr}#{@prefix}#{msg}"
|
228
|
+
end
|
229
|
+
@logged += 1
|
230
|
+
@channel.send(@method, msg)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
alias_method :info, :log
|
234
|
+
|
235
|
+
# Shortcut for {#log #log(msg, :debug)}
|
236
|
+
#
|
237
|
+
# @param [String] msg The message to send to {#log}.
|
238
|
+
def debug msg
|
239
|
+
log(msg, :debug)
|
240
|
+
end
|
241
|
+
|
242
|
+
# Shortcut for {#log #log(msg, :warn)} but sets channel method to "warn".
|
243
|
+
#
|
244
|
+
# @param [String] msg The message to send to {#log}.
|
245
|
+
def warn msg
|
246
|
+
ensure_method(:warn) { log(msg, :warn) }
|
247
|
+
end
|
248
|
+
|
249
|
+
# Shortcut for {#log #log(msg, :abort)} but sets channel method to "warn".
|
250
|
+
#
|
251
|
+
# @param [String] msg The message to send to {#log}.
|
252
|
+
# @param [Integer] exit_code Exits with given code or does nothing when falsy.
|
253
|
+
def abort msg, exit_code = false
|
254
|
+
ensure_method(:warn) { log(msg, :abort) }
|
255
|
+
exit(exit_code) if exit_code
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module ElchScan
|
2
|
+
class Application
|
3
|
+
module Dispatch
|
4
|
+
def dispatch action = (@opts[:dispatch] || :help)
|
5
|
+
case action
|
6
|
+
when :help then @optparse.to_s.split("\n").each(&method(:log))
|
7
|
+
when :version, :info then dispatch_info
|
8
|
+
else
|
9
|
+
if respond_to?("dispatch_#{action}")
|
10
|
+
send("dispatch_#{action}")
|
11
|
+
else
|
12
|
+
abort("unknown action #{action}", 1)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def dispatch_generate_config
|
18
|
+
if File.exist?(@config_src)
|
19
|
+
log "Configuration already exists, please delete it do generate another one."
|
20
|
+
else
|
21
|
+
FileUtils.cp("#{ROOT}/doc/config.yml", @config_src)
|
22
|
+
log "Sample configuration created!"
|
23
|
+
log "You will need to add at least 1 one movie directory."
|
24
|
+
answer = ask("Do you want to open the configuration file now? [Yes/no]")
|
25
|
+
if ["", "y", "yes"].include?(answer.downcase)
|
26
|
+
if RUBY_PLATFORM.include?("darwin")
|
27
|
+
exec("open #{@config_src}")
|
28
|
+
else
|
29
|
+
system "#{cfg :application, :editor} #{@config_src}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def dispatch_info
|
36
|
+
logger.log_without_timestr do
|
37
|
+
log ""
|
38
|
+
log " Your version: #{your_version = Gem::Version.new(ElchScan::VERSION)}"
|
39
|
+
|
40
|
+
# get current version
|
41
|
+
logger.log_with_print do
|
42
|
+
log " Current version: "
|
43
|
+
if cfg("application.check_version")
|
44
|
+
require "net/http"
|
45
|
+
log c("checking...", :blue)
|
46
|
+
|
47
|
+
begin
|
48
|
+
current_version = Gem::Version.new Net::HTTP.get_response(URI.parse(ElchScan::UPDATE_URL)).body.strip
|
49
|
+
|
50
|
+
if current_version > your_version
|
51
|
+
status = c("#{current_version} (consider update)", :red)
|
52
|
+
elsif current_version < your_version
|
53
|
+
status = c("#{current_version} (ahead, beta)", :green)
|
54
|
+
else
|
55
|
+
status = c("#{current_version} (up2date)", :green)
|
56
|
+
end
|
57
|
+
rescue
|
58
|
+
status = c("failed (#{$!.message})", :red)
|
59
|
+
end
|
60
|
+
|
61
|
+
logger.raw "#{"\b" * 11}#{" " * 11}#{"\b" * 11}", :print # reset cursor
|
62
|
+
log status
|
63
|
+
else
|
64
|
+
log c("check disabled", :red)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# more info
|
69
|
+
log ""
|
70
|
+
log " ElchScan is brought to you by #{c "bmonkeys.net", :green}"
|
71
|
+
log " Contribute @ #{c "github.com/2called-chaos/elch_scan", :cyan}"
|
72
|
+
log " Eat bananas every day!"
|
73
|
+
log ""
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def dispatch_edit_script
|
78
|
+
record_filter(filter_script(@opts[:select_script]))
|
79
|
+
end
|
80
|
+
|
81
|
+
def dispatch_index
|
82
|
+
if cfg(:movies).empty?
|
83
|
+
log "You will need at least 1 one movie directory defined in your configuration."
|
84
|
+
answer = ask("Do you want to open the file now? [Yes/no]")
|
85
|
+
if ["", "y", "yes"].include?(answer.downcase)
|
86
|
+
if RUBY_PLATFORM.include?("darwin")
|
87
|
+
exec("open #{@config_src}")
|
88
|
+
else
|
89
|
+
system "#{cfg :application, :editor} #{@config_src}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
else
|
93
|
+
movies = _index_movies(cfg(:movies))
|
94
|
+
old_count = movies.count
|
95
|
+
return log("No movies found :(") if old_count.zero?
|
96
|
+
log(
|
97
|
+
"We have found " << c("#{movies.count}", :magenta) <<
|
98
|
+
c(" movies in ") << c("#{cfg(:movies).count}", :magenta) << c(" directories")
|
99
|
+
)
|
100
|
+
|
101
|
+
if @opts[:console]
|
102
|
+
log "You have access to the collection with " << c("movies", :magenta)
|
103
|
+
log "Apply existent select script with " << c("apply_filter(movies, 'filter_name')", :magenta)
|
104
|
+
log "Type " << c("exit", :magenta) << c(" to leave the console.")
|
105
|
+
binding.pry(quiet: true)
|
106
|
+
else
|
107
|
+
# ask to filter
|
108
|
+
if !@opts[:quiet] && @opts[:select_scripts].empty?
|
109
|
+
answer = ask("Do you want to filter the results? [yes/No]")
|
110
|
+
if ["y", "yes"].include?(answer.downcase)
|
111
|
+
movies = apply_filter(movies, record_filter)
|
112
|
+
old_count = movies.count
|
113
|
+
collection_size_changed old_count, movies.count, "custom filter"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# filter
|
118
|
+
@opts[:select_scripts].each do |filter|
|
119
|
+
movies = apply_filter(movies, filter_script(filter))
|
120
|
+
collection_size_changed old_count, movies.count, "filter: #{filter}"
|
121
|
+
old_count = movies.count
|
122
|
+
end
|
123
|
+
|
124
|
+
# permute
|
125
|
+
permute_script(movies) if @opts[:permute]
|
126
|
+
|
127
|
+
# ask to save
|
128
|
+
if !@opts[:quiet] && !@opts[:output_file]
|
129
|
+
answer = ask("Enter filename to save output or leave blank to print to STDOUT:")
|
130
|
+
if !answer.empty?
|
131
|
+
@opts[:output_file] = answer
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# format results
|
136
|
+
begin
|
137
|
+
formatter = "ElchScan::Formatter::#{@opts[:formatter]}".constantize.new(self)
|
138
|
+
rescue LoadError
|
139
|
+
warn "Unknown formatter " << c("#{@opts[:formatter]}", :magenta) << c(", using Plain...", :red)
|
140
|
+
formatter = "ElchScan::Formatter::Plain".constantize.new(self)
|
141
|
+
end
|
142
|
+
results = formatter.format(movies)
|
143
|
+
|
144
|
+
# save
|
145
|
+
if @opts[:output_file]
|
146
|
+
File.open(@opts[:output_file], "w+") {|f| f.write(results.join("\n")) }
|
147
|
+
else
|
148
|
+
logger.log_without_timestr do
|
149
|
+
results.each {|line| log line }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def collection_size_changed cold, cnew, reason = nil
|
157
|
+
if cold != cnew
|
158
|
+
log(
|
159
|
+
"We have filtered " << c("#{cnew}", :magenta) <<
|
160
|
+
c(" movies") << c(" (#{cnew - cold})", :red) <<
|
161
|
+
c(" from originally ") << c("#{cold}", :magenta) <<
|
162
|
+
(reason ? c(" (#{reason})", :blue) : "")
|
163
|
+
)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def _index_movies directories
|
168
|
+
directories.each_with_object({}) do |dir, result|
|
169
|
+
if FileTest.directory?(dir)
|
170
|
+
Find.find(dir) do |path|
|
171
|
+
# calculate depth
|
172
|
+
depth = path.scan(?/).count - dir.scan(?/).count
|
173
|
+
depth -= 1 unless File.directory?(path)
|
174
|
+
|
175
|
+
if depth == 1 && File.directory?(path)
|
176
|
+
result[File.basename(path)] = Movie.new(
|
177
|
+
self,
|
178
|
+
dir: dir,
|
179
|
+
path: path,
|
180
|
+
)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
else
|
184
|
+
log(c("Directory unreadable, skipping ", :red) << c("#{dir}", :magenta))
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ElchScan
|
2
|
+
module Filter
|
3
|
+
def filter_script name
|
4
|
+
"#{ROOT}/tmp/#{name}.esss"
|
5
|
+
end
|
6
|
+
|
7
|
+
def apply_filter collection, file
|
8
|
+
app = @app
|
9
|
+
eval File.read(file), binding, file
|
10
|
+
collection
|
11
|
+
end
|
12
|
+
|
13
|
+
def permute_script collection, file = nil
|
14
|
+
file ||= "#{Dir.tmpdir}/#{SecureRandom.urlsafe_base64}"
|
15
|
+
FileUtils.mkdir(File.dirname(file)) if !File.exist?(File.dirname(file))
|
16
|
+
if !File.exist?(file) || File.read(file).strip.empty?
|
17
|
+
File.open(file, "w") {|f| f.puts("# Permute your collection, same as with the selector script filters.") }
|
18
|
+
end
|
19
|
+
system "#{cfg :application, :editor} #{file}"
|
20
|
+
eval File.read(file), binding, file
|
21
|
+
collection
|
22
|
+
end
|
23
|
+
|
24
|
+
def record_filter file = nil
|
25
|
+
file ||= "#{Dir.tmpdir}/#{SecureRandom.urlsafe_base64}"
|
26
|
+
FileUtils.mkdir(File.dirname(file)) if !File.exist?(File.dirname(file))
|
27
|
+
if !File.exist?(file) || File.read(file).strip.empty?
|
28
|
+
FileUtils.cp("#{ROOT}/doc/filter.rb", file)
|
29
|
+
end
|
30
|
+
system "#{cfg :application, :editor} #{file}"
|
31
|
+
file
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module ElchScan
|
2
|
+
# Logger Singleton
|
3
|
+
MAIN_THREAD = ::Thread.main
|
4
|
+
def MAIN_THREAD.app_logger
|
5
|
+
MAIN_THREAD[:app_logger] ||= Banana::Logger.new
|
6
|
+
end
|
7
|
+
|
8
|
+
class Application
|
9
|
+
include Dispatch
|
10
|
+
include Filter
|
11
|
+
|
12
|
+
# =========
|
13
|
+
# = Setup =
|
14
|
+
# =========
|
15
|
+
def self.dispatch *a
|
16
|
+
new(*a) do |app|
|
17
|
+
app.load_config "~/.elch_scan.yml"
|
18
|
+
app.apply_config
|
19
|
+
app.parse_params
|
20
|
+
app.dispatch
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize env, argv
|
25
|
+
@env, @argv = env, argv
|
26
|
+
@opts = {
|
27
|
+
dispatch: :index,
|
28
|
+
quiet: false,
|
29
|
+
output_file: nil,
|
30
|
+
formatter: "Plain",
|
31
|
+
select_scripts: [],
|
32
|
+
}
|
33
|
+
yield(self)
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_params
|
37
|
+
@optparse = OptionParser.new do |opts|
|
38
|
+
opts.banner = "Usage: elch_scan [options]"
|
39
|
+
|
40
|
+
opts.on("--generate-config", "Generate sample configuration file in ~/.elch_scan.yml") { @opts[:dispatch] = :generate_config }
|
41
|
+
opts.on("-h", "--help", "Shows this help") { @opts[:dispatch] = :help }
|
42
|
+
opts.on("-v", "--version", "Shows version and other info") { @opts[:dispatch] = :info }
|
43
|
+
opts.on("-f", "--formatter HTML", "Use formatter") {|f| @opts[:formatter] = f }
|
44
|
+
opts.on("-o", "--output FILENAME", "Write formatted results to file") {|f| @opts[:output_file] = f }
|
45
|
+
opts.on("-e", "--edit SELECT_SCRIPT", "Edit selector script") {|s| @opts[:dispatch] = :edit_script; @opts[:select_script] = s }
|
46
|
+
opts.on("-s", "--select [WITH_SUBS,NO_NFO]", Array, "Filter movies with saved selector scripts") {|s| @opts[:select_scripts] = s }
|
47
|
+
opts.on("-p", "--permute", "Open editor to write permutation code for collection") {|s| @opts[:permute] = true }
|
48
|
+
opts.on("-q", "--quiet", "Don't ask to filter or save results") { @opts[:quiet] = true }
|
49
|
+
opts.on("-c", "--console", "Start console to play around with the collection") {|f| @opts[:console] = true }
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
@optparse.parse!(@argv)
|
54
|
+
rescue OptionParser::ParseError => e
|
55
|
+
abort(e.message)
|
56
|
+
dispatch(:help)
|
57
|
+
exit 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def load_config file
|
62
|
+
@config_src = File.expand_path(file)
|
63
|
+
@config = YAML.load_file(@config_src)
|
64
|
+
raise "empty config" if !@config || @config.empty? || !@config.is_a?(Hash)
|
65
|
+
@config = @config.with_indifferent_access
|
66
|
+
rescue Exception => e
|
67
|
+
if e.message =~ /no such file or directory/i
|
68
|
+
if @argv.include?("--generate-config")
|
69
|
+
@config = { application: { logger: { colorize: true } } }.with_indifferent_access
|
70
|
+
else
|
71
|
+
log "Please create or generate a configuration file."
|
72
|
+
log(
|
73
|
+
c("Use ") << c("elch_scan --generate-config", :magenta) <<
|
74
|
+
c(" or create ") << c("~/.elch_scan.yml", :magenta) << c(" manually.")
|
75
|
+
)
|
76
|
+
abort "No configuration file found.", 1
|
77
|
+
end
|
78
|
+
elsif e.message =~ //i
|
79
|
+
abort "Configuration file is invalid.", 1
|
80
|
+
else
|
81
|
+
raise
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def apply_config
|
86
|
+
logger.colorize = cfg(:application, :logger, :colorize)
|
87
|
+
(cfg(:formatters) || []).each do |f|
|
88
|
+
begin
|
89
|
+
require File.expand_path(f)
|
90
|
+
rescue LoadError
|
91
|
+
abort "The custom formatter file wasn't found: " << c("#{f}", :magenta)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def cfg *keys
|
97
|
+
keys = keys.flatten.join(".").split(".")
|
98
|
+
keys.inject(@config) {|cfg, skey| cfg.try(:[], skey) }
|
99
|
+
end
|
100
|
+
|
101
|
+
# ==========
|
102
|
+
# = Logger =
|
103
|
+
# ==========
|
104
|
+
[:log, :warn, :abort, :debug].each do |meth|
|
105
|
+
define_method meth, ->(*a, &b) { Thread.main.app_logger.send(meth, *a, &b) }
|
106
|
+
end
|
107
|
+
|
108
|
+
def logger
|
109
|
+
Thread.main.app_logger
|
110
|
+
end
|
111
|
+
|
112
|
+
# Shortcut for logger.colorize
|
113
|
+
def c str, color = :yellow
|
114
|
+
logger.colorize? ? logger.colorize(str, color) : str
|
115
|
+
end
|
116
|
+
|
117
|
+
def ask question
|
118
|
+
logger.log_with_print(false) do
|
119
|
+
log c("#{question} ", :blue)
|
120
|
+
STDOUT.flush
|
121
|
+
gets.chomp
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|