Group and process arguments in groups of files and directories

Fixes #233
This commit is contained in:
Claudio Bley 2020-12-23 22:30:41 +01:00
parent 4612549bc2
commit 0048e3883a
7 changed files with 179 additions and 108 deletions

View file

@ -31,34 +31,38 @@ module ColorLS
init_long_format(mode,show_group,show_user)
@tree = {mode: mode == :tree, depth: tree_depth}
@horizontal = mode == :horizontal
process_git_status_details(git_status)
@git_status = init_git_status(git_status)
init_colors colors
init_icons
end
def ls(input)
@input = (+input).force_encoding(ColorLS.file_encoding)
@contents = init_contents(@input)
def ls_dir(info)
if @tree[:mode]
print "\n"
return tree_traverse(info.path, 0, 1, 2)
end
@contents = Dir.entries(info.path, encoding: ColorLS.file_encoding)
filter_hidden_contents
@contents.map! { |e| FileInfo.dir_entry(info.path, e, link_info: @long) }
filter_contents if @show
sort_contents if @sort
group_contents if @group
return print "\n Nothing to show here\n".colorize(@colors[:empty]) if @contents.empty?
layout = case
when @tree[:mode]
print "\n"
return tree_traverse(@input, 0, 1, 2)
when @horizontal
HorizontalLayout.new(@contents, item_widths, ColorLS.screen_width)
when @one_per_line || @long
SingleColumnLayout.new(@contents)
else
VerticalLayout.new(@contents, item_widths, ColorLS.screen_width)
end
ls
end
layout.each_line do |line, widths|
ls_line(line, widths)
end
def ls_files(files)
@contents = files
ls
end
def display_report
@ -72,6 +76,23 @@ module ColorLS
private
def ls
init_column_lengths
layout = case
when @horizontal
HorizontalLayout.new(@contents, item_widths, ColorLS.screen_width)
when @one_per_line || @long
SingleColumnLayout.new(@contents)
else
VerticalLayout.new(@contents, item_widths, ColorLS.screen_width)
end
layout.each_line do |line, widths|
ls_line(line, widths)
end
end
def init_colors(colors)
@colors = colors
@modes = Hash.new do |hash, key|
@ -85,12 +106,26 @@ module ColorLS
end
end
def init_long_format(mode,show_group,show_user)
def init_long_format(mode, show_group, show_user)
@long = mode == :long
@show_group = show_group
@show_user = show_user
end
def init_git_status(show_git)
return {}.freeze unless show_git
# stores git status information per directory
Hash.new do |hash, key|
path = File.absolute_path key.parent
if hash.key? path
hash[path]
else
hash[path] = Git.status(path)
end
end
end
# how much characters an item occupies besides its name
CHARS_PER_ITEM = 12
@ -98,28 +133,6 @@ module ColorLS
@contents.map { |item| Unicode::DisplayWidth.of(item.show) + CHARS_PER_ITEM }
end
def init_contents(path)
info = FileInfo.new(path, link_info: @long)
if info.directory?
@contents = Dir.entries(path, encoding: ColorLS.file_encoding)
filter_hidden_contents
@contents.map! { |e| FileInfo.new(File.join(path, e), link_info: @long) }
filter_contents if @show
sort_contents if @sort
group_contents if @group
else
@contents = [info]
end
init_column_lengths
@contents
end
def filter_hidden_contents
@contents -= %w[. ..] unless @all
@contents.keep_if { |x| !x.start_with? '.' } unless @all || @almost_all
@ -230,42 +243,32 @@ module ColorLS
mtime.colorize(@colors[:no_modifier])
end
def process_git_status_details(git_status)
@git_status = case
when !git_status then nil
when File.directory?(@input) then Git.status(@input)
else Git.status(File.dirname(@input))
end
end
def git_info(content)
return '' unless @git_status
return '' unless (status = @git_status[content])
if content.directory?
git_dir_info(content.name)
git_dir_info(content, status)
else
git_file_info(content.name)
git_file_info(status[content.name])
end
end
def git_file_info(path)
unless @git_status[path]
return ' ✓ '
.encode(Encoding.default_external, undef: :replace, replace: '=')
.colorize(@colors[:unchanged])
end
def git_file_info(status)
return Git.colored_status_symbols(status, @colors) if status
Git.colored_status_symbols(@git_status[path], @colors)
' ✓ '
.encode(Encoding.default_external, undef: :replace, replace: '=')
.colorize(@colors[:unchanged])
end
def git_dir_info(path)
modes = if path == '.'
Set.new(@git_status.values).flatten
def git_dir_info(content, status)
modes = if content.path == '.'
Set.new(status.values).flatten
else
@git_status[path]
status[content.name]
end
if modes.empty? && Dir.empty?(File.join(@input, path))
if modes.empty? && Dir.empty?(content.path)
' '
else
Git.colored_status_symbols(modes, @colors)
@ -300,12 +303,12 @@ module ColorLS
str.encode(Encoding.default_external, undef: :replace, replace: '')
end
def fetch_string(path, content, key, color, increment)
def fetch_string(content, key, color, increment)
@count[increment] += 1
value = increment == :folders ? @folders[key] : @files[key]
logo = value.gsub(/\\u[\da-f]{4}/i) { |m| [m[-4..-1].to_i(16)].pack('U') }
name = content.show
name = make_link(path, name) if @hyperlink
name = make_link(content) if @hyperlink
name += content.directory? ? '/' : ' '
entry = "#{out_encode(logo)} #{out_encode(name)}"
entry = entry.bright if !content.directory? && content.executable?
@ -317,7 +320,7 @@ module ColorLS
padding = 0
line = +''
chunk.each_with_index do |content, i|
entry = fetch_string(@input, content, *options(content))
entry = fetch_string(content, *options(content))
line << ' ' * padding
line << ' ' << entry.encode(Encoding.default_external, undef: :replace)
padding = widths[i] - Unicode::DisplayWidth.of(content.show) - CHARS_PER_ITEM
@ -354,12 +357,26 @@ module ColorLS
[key, color, group]
end
def tree_contents(path)
@contents = Dir.entries(path, encoding: ColorLS.file_encoding)
filter_hidden_contents
@contents.map! { |e| FileInfo.dir_entry(path, e, link_info: @long) }
filter_contents if @show
sort_contents if @sort
group_contents if @group
@contents
end
def tree_traverse(path, prespace, depth, indent)
contents = init_contents(path)
contents = tree_contents(path)
contents.each do |content|
icon = content == contents.last || content.directory? ? ' └──' : ' ├──'
print tree_branch_preprint(prespace, indent, icon).colorize(@colors[:tree])
print " #{fetch_string(path, content, *options(content))} \n"
print " #{fetch_string(content, *options(content))} \n"
next unless content.directory?
tree_traverse("#{path}/#{content}", prespace + indent, depth + 1, indent) if keep_going(depth)
@ -376,9 +393,9 @@ module ColorLS
' │ ' * (prespace/indent) + prespace_icon + '─' * indent
end
def make_link(path, name)
uri = Addressable::URI.convert_path(File.absolute_path(File.join(path, name)))
"\033]8;;#{uri}\007#{name}\033]8;;\007"
def make_link(content)
uri = Addressable::URI.convert_path(File.absolute_path(content.path))
"\033]8;;#{uri}\007#{content.name}\033]8;;\007"
end
end
end

View file

@ -9,19 +9,24 @@ module ColorLS
@@users = {} # rubocop:disable Style/ClassVars
@@groups = {} # rubocop:disable Style/ClassVars
attr_reader :stats, :name
def initialize(path, link_info: true)
@name = File.basename(path)
@stats = File.lstat(path)
attr_reader :stats, :name, :path, :parent
def initialize(name:, parent:, path: nil, link_info: true)
@name = name
@parent = parent
@path = path.nil? ? File.join(parent, name) : path
@stats = File.lstat(@path)
@show_name = nil
handle_symlink(path) if link_info && @stats.symlink?
handle_symlink(@path) if link_info && @stats.symlink?
end
def self.info(path)
FileInfo.new(path)
def self.info(path, link_info: true)
FileInfo.new(name: File.basename(path), parent: File.dirname(path), path: path, link_info: link_info)
end
def self.dir_entry(dir, child, link_info: true)
FileInfo.new(name: child, parent: dir, link_info: link_info)
end
def show

View file

@ -12,6 +12,7 @@ module ColorLS
@opts = default_opts
@show_report = false
@exit_status_code = 0
parse_options
@ -54,29 +55,42 @@ module ColorLS
warn "WARN: #{e}, check your locale settings"
end
def group_files_and_directories
infos = @args.flat_map do |arg|
FileInfo.info(arg)
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)
exit_status_code = 0
directories, files = group_files_and_directories
@args.sort!.each_with_index do |path, i|
unless File.exist?(path)
$stderr.puts "\n Specified path '#{path}' doesn't exist.".colorize(:red)
exit_status_code = 2
next
end
core.ls_files(files) unless files.nil?
puts '' if i.positive?
puts "\n#{path}:" if Dir.exist?(path) && @args.size > 1
directories&.sort_by! do |a|
CLocale.strxfrm(a.name)
end&.each do |dir|
puts "\n#{dir.show}:" if @args.size > 1
core.ls(path)
core.ls_dir(dir)
rescue SystemCallError => e
$stderr.puts "#{path}: #{e}".colorize(:red)
$stderr.puts "#{dir}: #{e}".colorize(:red)
end
core.display_report if @show_report
exit_status_code
@exit_status_code
end
def default_opts

View file

@ -10,18 +10,23 @@ module ColorLS
return unless success
prefix = Pathname.new(prefix)
prefix_path = Pathname.new(prefix)
git_status = Hash.new { |hash, key| hash[key] = Set.new }
git_subdir_status(repo_path) do |mode, file|
path = Pathname.new(file).relative_path_from(prefix)
git_status[path.descend.first.cleanpath.to_s].add(mode)
if file == prefix
git_status.default = Set[mode].freeze
else
path = Pathname.new(file).relative_path_from(prefix_path)
git_status[path.descend.first.cleanpath.to_s].add(mode)
end
end
warn "git status failed in #{repo_path}" unless $CHILD_STATUS.success?
git_status
git_status.default = Set.new.freeze if git_status.default.nil?
git_status.freeze
end
def self.colored_status_symbols(modes, colors)

View file

@ -17,6 +17,7 @@ RSpec.describe ColorLS::Core do
directory?: true,
owner: 'user',
name: imagenes,
path: '.',
show: imagenes,
nlink: 1,
size: 128,
@ -58,10 +59,9 @@ RSpec.describe ColorLS::Core do
allow(::Dir).to receive(:entries).and_return([camera])
allow(ColorLS::FileInfo).to receive(:new).and_return(dir_info)
allow(ColorLS::FileInfo).to receive(:new).with(File.join(imagenes, camera), link_info: false) { file_info }
allow(ColorLS::FileInfo).to receive(:new).and_return(file_info)
expect { subject.ls('Imágenes') }.to output(/mara/).to_stdout
expect { subject.ls_dir(dir_info) }.to output(/mara/).to_stdout
end
end
end

View file

@ -94,7 +94,12 @@ RSpec.describe ColorLS::Flags do
executable?: false
)
allow(ColorLS::FileInfo).to receive(:new).with("#{FIXTURES}/a.txt", link_info: true) { file_info }
allow(ColorLS::FileInfo).to receive(:new).with(
path: File.join(FIXTURES, 'a.txt'),
parent: FIXTURES,
name: 'a.txt',
link_info: true
) { file_info }
expect { subject }.to output(/r-Sr-Sr-T .* a.txt/mx).to_stdout
end
@ -123,7 +128,12 @@ RSpec.describe ColorLS::Flags do
executable?: false
)
allow(ColorLS::FileInfo).to receive(:new).with("#{FIXTURES}/a.txt", link_info: true) { file_info }
allow(ColorLS::FileInfo).to receive(:new).with(
path: File.join(FIXTURES, 'a.txt'),
parent: FIXTURES,
name: 'a.txt',
link_info: true
) { file_info }
expect { subject }.to output(/\S+\s+ 5 .* a.txt/mx).to_stdout
end
@ -354,7 +364,7 @@ RSpec.describe ColorLS::Flags do
let(:args) { ['not_exist_file'] }
it 'exits with status code 2' do # rubocop:todo RSpec/MultipleExpectations
expect { subject }.to output(/ Specified path 'not_exist_file' doesn't exist./).to_stderr
expect { subject }.to output(/colorls: Specified path 'not_exist_file' doesn't exist./).to_stderr
expect(subject).to eq 2
end
end
@ -386,7 +396,12 @@ RSpec.describe ColorLS::Flags do
executable?: false
)
allow(ColorLS::FileInfo).to receive(:new).with("#{FIXTURES}/a.txt", link_info: true) { file_info }
allow(ColorLS::FileInfo).to receive(:new).with(
path: File.join(FIXTURES, 'a.txt'),
parent: FIXTURES,
name: 'a.txt',
link_info: true
) { file_info }
end
it 'lists without group info' do
@ -425,7 +440,12 @@ RSpec.describe ColorLS::Flags do
executable?: false
)
allow(ColorLS::FileInfo).to receive(:new).with("#{FIXTURES}/a.txt", link_info: true) { file_info }
allow(ColorLS::FileInfo).to receive(:new).with(
path: File.join(FIXTURES, 'a.txt'),
parent: FIXTURES,
name: 'a.txt',
link_info: true
) { file_info }
end
it 'lists with group info' do
@ -464,7 +484,12 @@ RSpec.describe ColorLS::Flags do
executable?: false
)
allow(ColorLS::FileInfo).to receive(:new).with("#{FIXTURES}/a.txt", link_info: true) { file_info }
allow(ColorLS::FileInfo).to receive(:new).with(
path: File.join(FIXTURES, 'a.txt'),
parent: FIXTURES,
name: 'a.txt',
link_info: true
) { file_info }
end
it 'lists without group info' do
@ -503,7 +528,12 @@ RSpec.describe ColorLS::Flags do
executable?: false
)
allow(ColorLS::FileInfo).to receive(:new).with("#{FIXTURES}/a.txt", link_info: true) { file_info }
allow(ColorLS::FileInfo).to receive(:new).with(
path: File.join(FIXTURES, 'a.txt'),
parent: FIXTURES,
name: 'a.txt',
link_info: true
) { file_info }
end
it 'lists without group info' do

View file

@ -1,4 +1,4 @@
# frozen_string_literal: true
# frozen_string_literal: false
require 'spec_helper'