elch_scan 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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