diff --git a/07.ls_object/file.rb b/07.ls_object/file.rb new file mode 100644 index 0000000000..0bb292fb8f --- /dev/null +++ b/07.ls_object/file.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module LS + class File + FILE_TYPE_ABBRV = { + 'file' => '-', + 'directory' => 'd', + 'characterSpecial' => 'c', + 'blockSpecial' => 'b', + 'fifo' => 'p', + 'link' => 'l', + 'socket' => 's' + }.freeze + + FILE_PERMISSION_TEXT = { + '0' => '---', + '1' => '--x', + '2' => '-w-', + '3' => '-wx', + '4' => 'r--', + '5' => 'r-x', + '6' => 'rw-', + '7' => 'rwx' + }.freeze + + attr_reader :file_name, :file_mode, :nlink, :user, :group, :size, :mod_month, :mod_day, :mod_time + + def initialize(file_name) + raw_stat = ::File.lstat(file_name) + @file_name = file_name + @file_mode = file_mode_text(raw_stat) + @nlink = raw_stat.nlink.to_s + @user = Etc.getpwuid(raw_stat.uid).name + @group = Etc.getgrgid(raw_stat.gid).name + @size = raw_stat.size.to_s + @mod_month = raw_stat.mtime.month.to_s + @mod_day = raw_stat.mtime.day.to_s + @mod_time = raw_stat.mtime.strftime('%H:%M') + end + + def stat + [file_mode, nlink, user, group, size, mod_month, mod_day, mod_time] + end + + private + + def convert_file_mode_to_octal_string(mode) + mode.to_s(8) + end + + def file_mode_text(stat) + type = FILE_TYPE_ABBRV[stat.ftype] + octalized_file_mode = convert_file_mode_to_octal_string(stat.mode) + file_mode = octalized_file_mode.length == 5 ? "0#{octalized_file_mode}" : octalized_file_mode + "#{type}#{file_mode[3..5].chars.map { |c| FILE_PERMISSION_TEXT[c] }.join}" + end + end +end diff --git a/07.ls_object/file_list.rb b/07.ls_object/file_list.rb new file mode 100644 index 0000000000..17194bb04e --- /dev/null +++ b/07.ls_object/file_list.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require_relative 'file' + +module LS + class FileList + attr_reader :files + attr_accessor :reverse_order, :long_format, :max_column + + def initialize(options) + file_names = options.include?(:a) ? Dir.glob('*', ::File::FNM_DOTMATCH) : Dir.glob('*') + file_names.reverse! if options.include?(:r) + @files = file_names.map { |file_name| LS::File.new(file_name) } + @reverse_order = options.include?(:r) + @long_format = options.include?(:l) + @max_column = long_format ? 1 : 3 + end + + def current_directory_stats + file_stats_array = files.map { |file| file.stat } + file_stats_array_per_column = file_stats_array.transpose + + max_length_per_column = {} + file_stats_array_per_column.each_with_index do |file_stats, idx| + max_length_per_column[idx] = file_stats.max_by(&:length).length + end + + file_stats_array.map.with_index do |file_stats, idx| + justify_file_stats = file_stats.map.with_index do + _1.rjust(max_length_per_column[_2]) + end + + file_names = [] + file_names[idx] = (justify_file_stats << file_names[idx]).join(' ') + end + end + + def show + matrix.map { |m| m.join.rstrip!.concat("\n") }.join + end + + private + + def matrix + num_of_files = files.size + + return [] if num_of_files == 0 + + formatted_files = long_format ? current_directory_stats : files.map { |file| file.file_name } + + num_of_display_rows = (num_of_files % max_column).zero? ? num_of_files.div(max_column) : num_of_files.div(max_column) + 1 + + formatted_files.each_slice(num_of_display_rows).map do |matrix_col| + matrix_col.fill('', matrix_col.size..(num_of_display_rows - 1)) unless matrix_col.size == num_of_display_rows + longest_file_name = matrix_col.max_by(&:length).length + + matrix_col.map do |file_name| + diff = longest_file_name - file_name.length + "#{file_name}#{' ' * diff if diff.positive?}#{' ' * 4}" + end + end.transpose + end + end +end diff --git a/07.ls_object/ls.rb b/07.ls_object/ls.rb new file mode 100755 index 0000000000..2454319216 --- /dev/null +++ b/07.ls_object/ls.rb @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +# frozen_string_literal: true + +require 'etc' + +require_relative 'option' +require_relative 'file_list' + +options = LS::OptionParser.new(ARGV).options + +files = LS::FileList.new(options) + +puts files.show diff --git a/07.ls_object/option.rb b/07.ls_object/option.rb new file mode 100644 index 0000000000..564d4049e8 --- /dev/null +++ b/07.ls_object/option.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'optparse' + +module LS + class OptionParser + BANNER = 'Usage: ./ls.rb [options]' + OPTION_CHOICES = { + '-a': 'Include directory entries whose names begin with a dot (‘.’).', + '-r': 'Display files in reverse order.', + '-l': 'List files in the long format.' + } + + attr_reader :options + + def initialize(argv) + @options = parse(argv) + end + + def parse(argv) + parser = ::OptionParser.new do |opts| + opts.banner = BANNER + OPTION_CHOICES.each { |name, description| opts.on(name, description) } + end + + begin + opts = {} + parser.parse!(ARGV, into: opts) + opts + rescue ::OptionParser::ParseError => e + puts e.message + puts opts.help + exit + end + end + end +end