Skip to content

Commit 5b2dbfd

Browse files
committed
Add --color and --no-color options
We want to allow people to force the use or non-use of color when running scss-lint. This commit does this by extracting the logging into a Logger class and adding `--color` and `--no-color` options. For this code, I essentially brought over the same stuff that was done in HamlLint, and split out coloring and logging into separate methods on the Logger so that we could color strings without outputting them in our reporters. This removes the dependency on the Rainbow gem, which is nice. There was a note in the readme about Windows compatibility that I'm not really sure if it still applies or not, but since the wording and links to more information was Rainbow gem specific I decided to just remove it--we can add Windows information back in again if it comes up. Addresses sds#364
1 parent a7d757a commit 5b2dbfd

19 files changed

+343
-65
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# SCSS-Lint Changelog
22

3+
## master (unreleased)
4+
5+
* Add `--color` and `--no-color` options
6+
37
## 0.43.2
48

59
* Fix passing a file via STDIN

README.md

+1-8
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ Command Line Flag | Description
8888
`-i`/`--include-linter` | Specify which linters you specifically want to run
8989
`-x`/`--exclude-linter` | Specify which linters you _don't_ want to run
9090
`--stdin-file-path` | When linting a file passed via standard input, treat it as having the specified path to apply the appropriate configuration
91+
`--[no-]color` | Whether to output in color
9192
`-h`/`--help` | Show command line flag documentation
9293
`--show-formatters` | Show all available formatters
9394
`--show-linters` | Show all available linters
@@ -239,14 +240,6 @@ test.scss:5 [W] StringQuotes: Prefer single quoted strings
239240
test.scss:6 [W] UrlQuotes: URLs should be enclosed in quotes
240241
```
241242
242-
The default formatter tries to colorize the output using
243-
[Rainbow](https://github.com/sickill/rainbow#windows-support), which will
244-
silently fail on Windows systems if the gems
245-
[windows-pr](https://rubygems.org/gems/windows-pr) and
246-
[win32console](https://rubygems.org/gems/win32console)
247-
are not installed.
248-
[Read more about adding Windows support](https://github.com/sickill/rainbow#windows-support).
249-
250243
### CleanFiles
251244
252245
Displays a list of all files that were free of lints.

bin/scss-lint

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
require 'scss_lint'
44
require 'scss_lint/cli'
55

6-
exit SCSSLint::CLI.new.run(ARGV)
6+
logger = SCSSLint::Logger.new(STDOUT)
7+
exit SCSSLint::CLI.new(logger).run(ARGV)

lib/scss_lint.rb

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require 'scss_lint/location'
66
require 'scss_lint/lint'
77
require 'scss_lint/linter_registry'
8+
require 'scss_lint/logger'
89
require 'scss_lint/file_finder'
910
require 'scss_lint/runner'
1011
require 'scss_lint/selector_visitor'

lib/scss_lint/cli.rb

+49-29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
require 'rainbow'
2-
require 'rainbow/ext/string'
31
require 'scss_lint/options'
42

53
module SCSSLint
@@ -22,6 +20,13 @@ class CLI
2220
plugin: 82, # Plugin loading error
2321
}
2422

23+
# Create a CLI that outputs to the specified logger.
24+
#
25+
# @param logger [SCSSLint::Logger]
26+
def initialize(logger)
27+
@log = logger
28+
end
29+
2530
def run(args)
2631
options = SCSSLint::Options.new.parse(args)
2732
act_on_options(options)
@@ -31,7 +36,10 @@ def run(args)
3136

3237
private
3338

39+
attr_reader :log
40+
3441
def act_on_options(options)
42+
log.color_enabled = options.fetch(:color, log.tty?)
3543
load_required_paths(options)
3644
load_reporters(options)
3745

@@ -77,40 +85,47 @@ def scan_for_lints(options, config)
7785
def handle_runtime_exception(exception, options) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/LineLength, Metrics/MethodLength
7886
case exception
7987
when SCSSLint::Exceptions::InvalidCLIOption
80-
puts exception.message
81-
puts 'Run `scss-lint --help` for usage documentation'
88+
log.error exception.message
89+
log.info 'Run `scss-lint --help` for usage documentation'
8290
halt :usage
8391
when SCSSLint::Exceptions::InvalidConfiguration
84-
puts exception.message
92+
log.error exception.message
8593
halt :config
8694
when SCSSLint::Exceptions::RequiredLibraryMissingError
87-
puts exception.message
95+
log.error exception.message
8896
halt :unavailable
8997
when SCSSLint::Exceptions::NoFilesError
90-
puts exception.message
98+
log.error exception.message
9199
halt :no_files
92100
when SCSSLint::Exceptions::PluginGemLoadError
93-
puts exception.message
101+
log.error exception.message
94102
halt :plugin
95103
when Errno::ENOENT
96-
puts exception.message
104+
log.error exception.message
97105
halt :no_input
98106
when NoSuchLinter
99-
puts exception.message
107+
log.error exception.message
100108
halt :usage
101109
else
102110
config_file = relevant_configuration_file(options) if options
103111

104-
puts exception.message
105-
puts exception.backtrace
106-
puts 'Report this bug at '.color(:yellow) + BUG_REPORT_URL.color(:cyan)
107-
puts
108-
puts 'To help fix this issue, please include:'.color(:green)
109-
puts '- The above stack trace'
110-
puts "- SCSS-Lint version: #{SCSSLint::VERSION.color(:cyan)}"
111-
puts "- Sass version: #{Gem.loaded_specs['sass'].version.to_s.color(:cyan)}"
112-
puts "- Ruby version: #{RUBY_VERSION.color(:cyan)}"
113-
puts "- Contents of #{File.expand_path(config_file).color(:cyan)}" if config_file
112+
log.bold_error exception.message
113+
log.error exception.backtrace.join("\n")
114+
log.warning 'Report this bug at ', false
115+
log.info BUG_REPORT_URL
116+
log.newline
117+
log.success 'To help fix this issue, please include:'
118+
log.log '- The above stack trace'
119+
log.log '- SCSS-Lint version: ', false
120+
log.info SCSSLint::VERSION
121+
log.log '- Sass version: ', false
122+
log.info Gem.loaded_specs['sass'].version.to_s
123+
log.log '- Ruby version: ', false
124+
log.info RUBY_VERSION
125+
if config_file
126+
log.log '- Contents of ', false
127+
log.info File.expand_path(config_file)
128+
end
114129
halt :software
115130
end
116131
end
@@ -167,9 +182,14 @@ def merge_options_with_config(options, config)
167182
def report_lints(options, lints, files)
168183
sorted_lints = lints.sort_by { |l| [l.filename, l.location] }
169184
options.fetch(:reporters).each do |reporter, output|
170-
results = reporter.new(sorted_lints, files).report_lints
171-
io = (output == :stdout ? $stdout : File.new(output, 'w+'))
172-
io.print results if results
185+
results = reporter.new(sorted_lints, files, log).report_lints
186+
next unless results
187+
188+
if output == :stdout
189+
log.log results
190+
else
191+
File.new(output, 'w+').print results
192+
end
173193
end
174194
end
175195

@@ -195,40 +215,40 @@ def load_reporters(options)
195215
end
196216

197217
def print_formatters
198-
puts 'Installed formatters:'
218+
log.log 'Installed formatters:'
199219

200220
reporter_names = SCSSLint::Reporter.descendants.map do |reporter|
201221
reporter.name.split('::').last.split('Reporter').first
202222
end
203223

204224
reporter_names.sort.each do |reporter_name|
205-
puts " - #{reporter_name}"
225+
log.log " - #{reporter_name}"
206226
end
207227

208228
halt
209229
end
210230

211231
def print_linters
212-
puts 'Installed linters:'
232+
log.log 'Installed linters:'
213233

214234
linter_names = LinterRegistry.linters.map(&:simple_name)
215235

216236
linter_names.sort.each do |linter_name|
217-
puts " - #{linter_name}"
237+
log.log " - #{linter_name}"
218238
end
219239

220240
halt
221241
end
222242

223243
# @param options [Hash]
224244
def print_help(options)
225-
puts options[:help]
245+
log.log options[:help]
226246
halt :ok
227247
end
228248

229249
# @param options [Hash]
230250
def print_version
231-
puts "scss-lint #{SCSSLint::VERSION}"
251+
log.log "scss-lint #{SCSSLint::VERSION}"
232252
halt :ok
233253
end
234254

lib/scss_lint/logger.rb

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
module SCSSLint
2+
# Encapsulates all communication to an output source.
3+
class Logger
4+
# Whether colored output via ANSI escape sequences is enabled.
5+
# @return [true,false]
6+
attr_accessor :color_enabled
7+
8+
# Creates a logger which outputs nothing.
9+
# @return [SCSSLint::Logger]
10+
def self.silent
11+
new(File.open('/dev/null', 'w'))
12+
end
13+
14+
# Creates a new {SCSSLint::Logger} instance.
15+
#
16+
# @param out [IO] the output destination.
17+
def initialize(out)
18+
@out = out
19+
end
20+
21+
# Print the specified output.
22+
#
23+
# @param output [String] the output to send
24+
# @param newline [true,false] whether to append a newline
25+
def log(output, newline = true)
26+
@out.print(output)
27+
@out.print("\n") if newline
28+
end
29+
30+
# Print the specified output in a color indicative of error.
31+
# If output destination is not a TTY, behaves the same as {#log}.
32+
#
33+
# @param output [String] the output to send
34+
# @param newline [true,false] whether to append a newline
35+
def error(output, newline = true)
36+
log(red(output), newline)
37+
end
38+
39+
# Print the specified output in a bold face and color indicative of error.
40+
# If output destination is not a TTY, behaves the same as {#log}.
41+
#
42+
# @param output [String] the output to send
43+
# @param newline [true,false] whether to append a newline
44+
def bold_error(output, newline = true)
45+
log(bold_red(output), newline)
46+
end
47+
48+
# Print the specified output in a color indicative of success.
49+
# If output destination is not a TTY, behaves the same as {#log}.
50+
#
51+
# @param output [String] the output to send
52+
# @param newline [true,false] whether to append a newline
53+
def success(output, newline = true)
54+
log(green(output), newline)
55+
end
56+
57+
# Print the specified output in a color indicative of a warning.
58+
# If output destination is not a TTY, behaves the same as {#log}.
59+
#
60+
# @param output [String] the output to send
61+
# @param newline [true,false] whether to append a newline
62+
def warning(output, newline = true)
63+
log(yellow(output), newline)
64+
end
65+
66+
# Print the specified output in a color indicating information.
67+
# If output destination is not a TTY, behaves the same as {#log}.
68+
#
69+
# @param output [String] the output to send
70+
# @param newline [true,false] whether to append a newline
71+
def info(output, newline = true)
72+
log(cyan(output), newline)
73+
end
74+
75+
# Print a blank line.
76+
def newline
77+
log('')
78+
end
79+
80+
# Mark the specified output in bold face.
81+
# If output destination is not a TTY, this is a noop.
82+
#
83+
# @param output [String] the output to format
84+
def bold(output)
85+
color('1', output)
86+
end
87+
88+
# Mark the specified output in bold red.
89+
# If output destination is not a TTY, this is a noop.
90+
#
91+
# @param output [String] the output to format
92+
def bold_red(output)
93+
color('1;31', output)
94+
end
95+
96+
# Mark the specified output in red.
97+
# If output destination is not a TTY, this is a noop.
98+
#
99+
# @param output [String] the output to format
100+
def red(output)
101+
color(31, output)
102+
end
103+
104+
# Mark the specified output in green.
105+
# If output destination is not a TTY, this is a noop.
106+
#
107+
# @param output [String] the output to format
108+
def green(output)
109+
color(32, output)
110+
end
111+
112+
# Mark the specified output in yellow.
113+
# If output destination is not a TTY, this is a noop.
114+
#
115+
# @param output [String] the output to format
116+
def yellow(output)
117+
color(33, output)
118+
end
119+
120+
# Mark the specified output in magenta.
121+
# If output destination is not a TTY, this is a noop.
122+
#
123+
# @param output [String] the output to format
124+
def magenta(output)
125+
color(35, output)
126+
end
127+
128+
# Mark the specified output in cyan.
129+
# If output destination is not a TTY, this is a noop.
130+
#
131+
# @param output [String] the output to format
132+
def cyan(output)
133+
color(36, output)
134+
end
135+
136+
# Whether this logger is outputting to a TTY.
137+
#
138+
# @return [true,false]
139+
def tty?
140+
@out.respond_to?(:tty?) && @out.tty?
141+
end
142+
143+
private
144+
145+
def color(code, output)
146+
color_enabled ? "\033[#{code}m#{output}\033[0m" : output
147+
end
148+
end
149+
end

lib/scss_lint/options.rb

+4
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ def add_info_options(parser)
102102
@options[:show_linters] = true
103103
end
104104

105+
parser.on('--[no-]color', 'Force output to be colorized') do |color|
106+
@options[:color] = color
107+
end
108+
105109
parser.on_tail('-h', '--help', 'Display help documentation') do
106110
@options[:help] = parser.help
107111
end

0 commit comments

Comments
 (0)