-
Notifications
You must be signed in to change notification settings - Fork 390
/
flags.rb
330 lines (271 loc) · 10.4 KB
/
flags.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# frozen_string_literal: true
require 'optparse'
require 'colorls/version'
module ColorLS
class Flags
def initialize(*args)
@args = args
@light_colors = false
@opts = default_opts
@report_mode = false
@exit_status_code = 0
parse_options
return unless @opts[:mode] == :tree
# FIXME: `--all` and `--tree` do not work together, use `--almost-all` instead
@opts[:almost_all] = true if @opts[:all]
@opts[:all] = false
end
def process
init_locale
@args = ['.'] if @args.empty?
process_args
end
Option = Struct.new(:flags, :desc)
def options
list = @parser.top.list + @parser.base.list
result = list.collect do |o|
next unless o.respond_to? :desc
flags = o.short + o.long
next if flags.empty?
Option.new(flags, o.desc)
end
result.compact
end
private
def init_locale
# initialize locale from environment
CLocale.setlocale(CLocale::LC_COLLATE, '')
rescue RuntimeError => e
warn "WARN: #{e}, check your locale settings"
end
def group_files_and_directories
infos = @args.flat_map do |arg|
FileInfo.info(arg, show_filepath: true)
rescue Errno::ENOENT
$stderr.puts "colorls: Specified path '#{arg}' doesn't exist.".colorize(:red)
@exit_status_code = 2
[]
rescue SystemCallError => e
$stderr.puts "#{path}: #{e}".colorize(:red)
@exit_status_code = 2
[]
end
infos.group_by(&:directory?).values_at(true, false)
end
def process_args
core = Core.new(**@opts)
directories, files = group_files_and_directories
core.ls_files(files) unless files.nil?
directories&.sort_by! do |a|
CLocale.strxfrm(a.name)
end&.each do |dir|
puts "\n#{dir.show}:" if @args.size > 1
core.ls_dir(dir)
rescue SystemCallError => e
$stderr.puts "#{dir}: #{e}".colorize(:red)
end
core.display_report(@report_mode) if @report_mode
@exit_status_code
end
def default_opts
{
show: false,
sort: true,
reverse: false,
group: nil,
mode: STDOUT.tty? ? :vertical : :one_per_line, # rubocop:disable Style/GlobalStdStream
all: false,
almost_all: false,
show_git: false,
colors: [],
tree_depth: 3,
show_inode: false,
indicator_style: 'slash',
long_style_options: {}
}
end
def add_sort_options(options)
options.separator ''
options.separator 'sorting options:'
options.separator ''
options.on('--sd', '--sort-dirs', '--group-directories-first', 'sort directories first') { @opts[:group] = :dirs }
options.on('--sf', '--sort-files', 'sort files first') { @opts[:group] = :files }
options.on('-t', 'sort by modification time, newest first') { @opts[:sort] = :time }
options.on('-U', 'do not sort; list entries in directory order') { @opts[:sort] = false }
options.on('-S', 'sort by file size, largest first') { @opts[:sort] = :size }
options.on('-X', 'sort by file extension') { @opts[:sort] = :extension }
options.on(
'--sort=WORD',
%w[none time size extension],
'sort by WORD instead of name: none, size (-S), time (-t), extension (-X)'
) do |word|
@opts[:sort] = case word
when 'none' then false
else word.to_sym
end
end
options.on('-r', '--reverse', 'reverse order while sorting') { @opts[:reverse] = true }
end
def add_common_options(options)
options.on('-a', '--all', 'do not ignore entries starting with .') { @opts[:all] = true }
options.on('-A', '--almost-all', 'do not list . and ..') { @opts[:almost_all] = true }
options.on('-d', '--dirs', 'show only directories') { @opts[:show] = :dirs }
options.on('-f', '--files', 'show only files') { @opts[:show] = :files }
options.on('--gs', '--git-status', 'show git status for each file') { @opts[:show_git] = true }
options.on('-p', 'append / indicator to directories') { @opts[:indicator_style] = 'slash' }
options.on('-i', '--inode', 'show inode number') { @opts[:show_inode] = true }
options.on('--report=[WORD]', %w[short long], 'show report: short, long (default if omitted)') do |word|
word ||= :long
@report_mode = word.to_sym
end
options.on(
'--indicator-style=[STYLE]',
%w[none slash], 'append indicator with style STYLE to entry names: none, slash (-p) (default)'
) do |style|
@opts[:indicator_style] = style
end
end
def add_format_options(options)
options.on(
'--format=WORD', %w[across horizontal long single-column],
'use format: across (-x), horizontal (-x), long (-l), single-column (-1), vertical (-C)'
) do |word|
case word
when 'across', 'horizontal' then @opts[:mode] = :horizontal
when 'vertical' then @opts[:mode] = :vertical
when 'long' then @opts[:mode] = :long
when 'single-column' then @opts[:mode] = :one_per_line
end
end
options.on('-1', 'list one file per line') { @opts[:mode] = :one_per_line }
options.on('--tree=[DEPTH]', Integer, 'shows tree view of the directory') do |depth|
@opts[:tree_depth] = depth
@opts[:mode] = :tree
end
options.on('-x', 'list entries by lines instead of by columns') { @opts[:mode] = :horizontal }
options.on('-C', 'list entries by columns instead of by lines') { @opts[:mode] = :vertical }
options.on('--without-icons', 'list entries without icons') { @opts[:icons] = false }
end
def default_long_style_options
{
show_group: true,
show_user: true,
time_style: '',
hard_links_count: true,
show_symbol_dest: false,
human_readable_size: true
}
end
def add_long_style_options(options)
long_style_options = default_long_style_options
options.on('-l', '--long', 'use a long listing format') { @opts[:mode] = :long }
long_style_options = set_long_style_user_and_group_options(options, long_style_options)
options.on('--time-style=FORMAT', String, 'use time display format') do |time_style|
long_style_options[:time_style] = time_style
end
options.on('--no-hardlinks', 'show no hard links count in a long listing') do
long_style_options[:hard_links_count] = false
end
long_style_options = get_long_style_symlink_options(options, long_style_options)
options.on('--non-human-readable', 'show file sizes in bytes only') do
long_style_options[:human_readable_size] = false
end
@opts[:long_style_options] = long_style_options
end
def set_long_style_user_and_group_options(options, long_style_options)
options.on('-o', 'use a long listing format without group information') do
@opts[:mode] = :long
long_style_options[:show_group] = false
end
options.on('-g', 'use a long listing format without owner information') do
@opts[:mode] = :long
long_style_options[:show_user] = false
end
options.on('-G', '--no-group', 'show no group information in a long listing') do
long_style_options[:show_group] = false
end
long_style_options
end
def get_long_style_symlink_options(options, long_style_options)
options.on('-L', 'show information on the destination of symbolic links') do
long_style_options[:show_symbol_dest] = true
end
long_style_options
end
def add_general_options(options)
options.separator ''
options.separator 'general options:'
options.separator ''
options.on(
'--color=[WHEN]', %w[always auto never],
'colorize the output: auto, always (default if omitted), never'
) do |word|
# let Rainbow decide in "auto" mode
Rainbow.enabled = (word != 'never') unless word == 'auto'
end
options.on('--light', 'use light color scheme') { @light_colors = true }
options.on('--dark', 'use dark color scheme') { @light_colors = false }
options.on('--hyperlink') { @opts[:hyperlink] = true }
end
def add_compatiblity_options(options)
options.separator ''
options.separator 'options for compatiblity with ls (ignored):'
options.separator ''
options.on('-h', '--human-readable') {} # always active
end
def show_help
puts @parser
show_examples
exit
end
def add_help_option(opts)
opts.separator ''
opts.on_tail('--help', 'prints this help') { show_help }
end
def show_examples
puts <<EXAMPLES.gsub(/^ /, '')
examples:
* show the given file:
#{'colorls README.md'.colorize(:green)}
* show matching files and list matching directories:
#{'colorls *'.colorize(:green)}
* filter output by a regular expression:
#{'colorls | grep PATTERN'.colorize(:green)}
* several short options can be combined:
#{'colorls -d -l -a'.colorize(:green)}
#{'colorls -dla'.colorize(:green)}
EXAMPLES
end
def assign_each_options(opts)
add_common_options(opts)
add_format_options(opts)
add_long_style_options(opts)
add_sort_options(opts)
add_compatiblity_options(opts)
add_general_options(opts)
add_help_option(opts)
end
def parse_options
@parser = OptionParser.new do |opts|
opts.banner = 'Usage: colorls [OPTION]... [FILE]...'
opts.separator ''
assign_each_options(opts)
opts.on_tail('--version', 'show version') do
puts ColorLS::VERSION
exit
end
end
# show help and exit if the only argument is -h
show_help if !@args.empty? && @args.all?('-h')
@parser.parse!(@args)
set_color_opts
rescue OptionParser::ParseError => e
warn "colorls: #{e}\nSee 'colorls --help'."
exit 2
end
def set_color_opts
color_scheme_file = @light_colors ? 'light_colors.yaml' : 'dark_colors.yaml'
@opts[:colors] = ColorLS::Yaml.new(color_scheme_file).load(aliase: true)
end
end
end