diff --git a/07.ls_object/command_line_options.rb b/07.ls_object/command_line_options.rb new file mode 100644 index 0000000000..c58b88d535 --- /dev/null +++ b/07.ls_object/command_line_options.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'optparse' + +class CommandLineOptions + def initialize + opt = OptionParser.new + opt.on('-a') { |v| @all = v } + opt.on('-r') { |v| @reverse = v } + opt.on('-l') { |v| @long_format = v } + opt.parse(ARGV) + end + + def show_all? + @all.nil? ? false : @all + end + + def show_long_format? + @long_format.nil? ? false : @long_format + end + + def show_reverse? + @reverse.nil? ? false : @reverse + end +end diff --git a/07.ls_object/file_list_printer.rb b/07.ls_object/file_list_printer.rb new file mode 100644 index 0000000000..aa27e58465 --- /dev/null +++ b/07.ls_object/file_list_printer.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require_relative 'multi_column_format' +require_relative 'long_format' + +class FileListPrinter + def self.run(files, long_format) + file_list_printer = new(files, long_format) + puts file_list_printer.generate + end + + def initialize(files, long_format) + @files = files + @long_format = long_format + end + + def generate + @long_format ? LongFormat.generate(@files) : MultiColumnFormat.generate(@files) + end +end diff --git a/07.ls_object/file_metadata.rb b/07.ls_object/file_metadata.rb new file mode 100644 index 0000000000..57830be588 --- /dev/null +++ b/07.ls_object/file_metadata.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'etc' + +class FileMetadata + FILE_TYPES = { + '04' => 'd', + '10' => '-', + '12' => 'l' + }.freeze + PERMISSIONS = { + '7' => 'rwx', + '6' => 'rw-', + '5' => 'r-x', + '4' => 'r--', + '3' => '-wx', + '2' => '-w-', + '1' => '--x', + '0' => '---' + }.freeze + + private_constant :FILE_TYPES, :PERMISSIONS + attr_reader :name + + def self.get_files(all, reverse) + flags = all ? File::FNM_DOTMATCH : 0 + files = Dir.glob('*', flags).map { |file_name| new(file_name) } + asc_order_files = files.sort_by(&:name) + reverse ? asc_order_files.reverse : asc_order_files + end + + def initialize(name) + @name = name + @stat = ::File.stat(name) + end + + def str_mode + int_mode = @stat.mode.to_s(8).rjust(6, '0') + FILE_TYPES[int_mode[0..1]] + int_mode[3..5].chars.map { |mode| PERMISSIONS[mode] }.join + end + + def nlink + @stat.nlink + end + + def owner + uid = @stat.uid + Etc.getpwuid(uid) + end + + def group + gid = @stat.gid + Etc.getgrgid(gid) + end + + def size + @stat.size.to_s + end + + def blocks + @stat.blocks + end + + def mtime + @stat.mtime.strftime('%b %d %H:%M').to_s + end +end diff --git a/07.ls_object/long_format.rb b/07.ls_object/long_format.rb new file mode 100644 index 0000000000..8087f4a764 --- /dev/null +++ b/07.ls_object/long_format.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +class LongFormat + def self.generate(files) + long_format = new(files) + column = files.map do |file| + long_format.generate_content(file) + end.join("\n") + + [ + "total #{long_format.total_block_size}", + column + ].join("\n") + end + + def initialize(files) + @files = files + end + + def total_block_size + # rubyは1ブロックを512バイト、Linuxは1ブロックを1024で計算しているため、2で割っている + @files.map { |file| file.blocks.div(2) }.sum + end + + def generate_content(file) + content_widths = calc_widths + [ + file.str_mode, + file.nlink.to_s.rjust(content_widths[:nlink]), + file.owner.name.rjust(content_widths[:owner]), + file.group.name.rjust(content_widths[:group]), + file.size.rjust(content_widths[:size]), + file.mtime.rjust(content_widths[:mtime]), + file.name + ].join(' ') + end + + private + + def calc_widths + widths = Hash.new { |hash, key| hash[key] = [] } + @files.each do |f| + widths[:owner].push(f.owner.name.length) + widths[:group].push(f.group.name.length) + widths[:nlink].push(f.nlink.to_s.length) + widths[:size].push(f.size.length) + widths[:mtime].push(f.mtime.length) + widths[:name].push(f.name.length) + end + widths.transform_values(&:max) + end +end diff --git a/07.ls_object/ls.rb b/07.ls_object/ls.rb new file mode 100644 index 0000000000..618a8724cf --- /dev/null +++ b/07.ls_object/ls.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require_relative 'command_line_options' +require_relative 'file_metadata' +require_relative 'file_list_printer' + +class Ls + def self.run + options = CommandLineOptions.new + files = FileMetadata.get_files(options.show_all?, options.show_reverse?) + FileListPrinter.run(files, options.show_long_format?) + end +end + +Ls.run diff --git a/07.ls_object/multi_column_format.rb b/07.ls_object/multi_column_format.rb new file mode 100644 index 0000000000..a9c5044dea --- /dev/null +++ b/07.ls_object/multi_column_format.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +class MultiColumnFormat + COLUMN_COUNT = 3 + + def self.generate(files) + multi_column_format = new(files) + matrixed_files = multi_column_format.build_matrix + multi_column_format.generate_rows(matrixed_files) + end + + def initialize(files) + @files = files + end + + def build_matrix + (0...row_count).map do |i| + index_to_row_value = i + (0...COLUMN_COUNT).map do + index_to_target = index_to_row_value + index_to_row_value += row_count + @files[index_to_target] + end.compact + end + end + + def generate_rows(matrixed_files) + matrixed_files.map do |files| + generate_row(files) + end.join("\n") + end + + private + + def name_width + @files.map { |file| file.name.length }.max + end + + def row_count + @files.count.ceildiv(COLUMN_COUNT) + end + + def generate_row(files) + files.map do |file| + generate_content(file) + end.join(' ') + end + + def generate_content(file) + file.name.ljust(name_width) + end +end diff --git a/07.ls_object/test/abc.txt b/07.ls_object/test/abc.txt new file mode 100644 index 0000000000..44d5a7b33b --- /dev/null +++ b/07.ls_object/test/abc.txt @@ -0,0 +1 @@ +この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大きさ、量、字間、行間等を確認するために入れています。この文章はダミーです。文字の大き diff --git a/07.ls_object/test/file_a.txt b/07.ls_object/test/file_a.txt new file mode 100644 index 0000000000..038abb698b --- /dev/null +++ b/07.ls_object/test/file_a.txt @@ -0,0 +1 @@ +ダミーです diff --git a/07.ls_object/test/file_b.txt b/07.ls_object/test/file_b.txt new file mode 100644 index 0000000000..e861f0d264 --- /dev/null +++ b/07.ls_object/test/file_b.txt @@ -0,0 +1,2 @@ +あなたの名前は何ですか +私の名前は diff --git a/07.ls_object/test/file_c.txt b/07.ls_object/test/file_c.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/07.ls_object/test/file_d.txt b/07.ls_object/test/file_d.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/07.ls_object/test/file_e.txt b/07.ls_object/test/file_e.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/07.ls_object/test/file_f.txt b/07.ls_object/test/file_f.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/07.ls_object/test/test_ls.rb b/07.ls_object/test/test_ls.rb new file mode 100644 index 0000000000..11ed71fb07 --- /dev/null +++ b/07.ls_object/test/test_ls.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +require 'minitest/autorun' +require_relative '../file_metadata' +require_relative '../file_list_printer' + +class LsTest < Minitest::Test + def test_default + expected = <<~LIST.chomp + abc.txt file_c.txt file_f.txt + file_a.txt file_d.txt test_ls.rb + file_b.txt file_e.txt + LIST + all = false + reverse = false + long_format = false + files = FileMetadata.get_files(all, reverse) + output = FileListPrinter.new(files, long_format) + ls_content = output.generate + assert_equal expected, ls_content + end + + def test_all + expected = <<~LIST.chomp + . file_b.txt file_e.txt + abc.txt file_c.txt file_f.txt + file_a.txt file_d.txt test_ls.rb + LIST + all = true + reverse = false + long_format = false + files = FileMetadata.get_files(all, reverse) + output = FileListPrinter.new(files, long_format) + ls_content = output.generate + assert_equal expected, ls_content + end + + def test_reverse + expected = <<~LIST.chomp + test_ls.rb file_d.txt file_a.txt + file_f.txt file_c.txt abc.txt#{' '} + file_e.txt file_b.txt + LIST + all = false + reverse = true + long_format = false + files = FileMetadata.get_files(all, reverse) + output = FileListPrinter.new(files, long_format) + ls_content = output.generate + assert_equal expected, ls_content + end + + def test_long_format + file = FileMetadata.new('test_ls.rb') + expected = <<~LIST.chomp + total 20 + -rw-r--r-- 1 s-tone s-tone 3001 Dec 03 14:54 abc.txt + -rw-r--r-- 1 s-tone s-tone 16 Dec 03 14:57 file_a.txt + -rw-r--r-- 1 s-tone s-tone 50 Dec 03 14:57 file_b.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_c.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_d.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_e.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_f.txt + -rw-r--r-- 1 s-tone s-tone #{file.size} #{file.mtime} test_ls.rb + LIST + all = false + reverse = false + long_format = true + files = FileMetadata.get_files(all, reverse) + output = FileListPrinter.new(files, long_format) + ls_content = output.generate + assert_equal expected, ls_content + end + + def test_all_reverse + expected = <<~LIST.chomp + test_ls.rb file_d.txt file_a.txt + file_f.txt file_c.txt abc.txt#{' '} + file_e.txt file_b.txt .#{' '} + LIST + all = true + reverse = true + long_format = false + files = FileMetadata.get_files(all, reverse) + output = FileListPrinter.new(files, long_format) + ls_content = output.generate + assert_equal expected, ls_content + end + + def test_all_long_format + file = FileMetadata.new('test_ls.rb') + current_directory = FileMetadata.new('.') + expected = <<~LIST.chomp + total 24 + drwxr-xr-x 2 s-tone s-tone #{current_directory.size} #{current_directory.mtime} . + -rw-r--r-- 1 s-tone s-tone 3001 Dec 03 14:54 abc.txt + -rw-r--r-- 1 s-tone s-tone 16 Dec 03 14:57 file_a.txt + -rw-r--r-- 1 s-tone s-tone 50 Dec 03 14:57 file_b.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_c.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_d.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_e.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_f.txt + -rw-r--r-- 1 s-tone s-tone #{file.size} #{file.mtime} test_ls.rb + LIST + all = true + reverse = false + long_format = true + files = FileMetadata.get_files(all, reverse) + output = FileListPrinter.new(files, long_format) + ls_content = output.generate + assert_equal expected, ls_content + end + + def test_reverse_long_format + file = FileMetadata.new('test_ls.rb') + expected = <<~LIST.chomp + total 20 + -rw-r--r-- 1 s-tone s-tone #{file.size} #{file.mtime} test_ls.rb + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_f.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_e.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_d.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_c.txt + -rw-r--r-- 1 s-tone s-tone 50 Dec 03 14:57 file_b.txt + -rw-r--r-- 1 s-tone s-tone 16 Dec 03 14:57 file_a.txt + -rw-r--r-- 1 s-tone s-tone 3001 Dec 03 14:54 abc.txt + LIST + all = false + reverse = true + long_format = true + files = FileMetadata.get_files(all, reverse) + output = FileListPrinter.new(files, long_format) + ls_content = output.generate + assert_equal expected, ls_content + end + + def test_reverse_liong_format_all + file = FileMetadata.new('test_ls.rb') + current_directory = FileMetadata.new('.') + expected = <<~LIST.chomp + total 24 + -rw-r--r-- 1 s-tone s-tone #{file.size} #{file.mtime} test_ls.rb + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_f.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_e.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_d.txt + -rw-r--r-- 1 s-tone s-tone 0 Dec 03 14:56 file_c.txt + -rw-r--r-- 1 s-tone s-tone 50 Dec 03 14:57 file_b.txt + -rw-r--r-- 1 s-tone s-tone 16 Dec 03 14:57 file_a.txt + -rw-r--r-- 1 s-tone s-tone 3001 Dec 03 14:54 abc.txt + drwxr-xr-x 2 s-tone s-tone #{current_directory.size} #{current_directory.mtime} . + LIST + all = true + reverse = true + long_format = true + files = FileMetadata.get_files(all, reverse) + output = FileListPrinter.new(files, long_format) + ls_content = output.generate + assert_equal expected, ls_content + end +end