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