Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
ace2983
add:ls -lの実装
kutimiti1234 Aug 12, 2024
c900f9f
clean:不要な条件を削除
kutimiti1234 Aug 16, 2024
5436be0
clean:LsLongとLsShortの共通の振る舞いを持つ親クラスLsCommandを設定
kutimiti1234 Aug 16, 2024
50fed16
clean:冗長なパス指定を削除
kutimiti1234 Aug 16, 2024
dbf3857
clean:適切なメソッド名へ変更
kutimiti1234 Aug 16, 2024
27a620f
clean:render機能をentry側に移管
kutimiti1234 Aug 20, 2024
b9b39c4
add:dirクラスにデフォルトのlsコマンド機能を実装
kutimiti1234 Aug 24, 2024
c543bc7
add:filelistクラスにデフォルトのlsコマンド機能を実装
kutimiti1234 Aug 24, 2024
71d9b2e
add:lsコマンドの制御用クラスを作成。main.rbも整備
kutimiti1234 Aug 24, 2024
fa552f4
add:Dirクラスの実態がEntryクラスよりもFileEntriesListの機能に似ているため、Entryクラスを廃した。また、Di…
kutimiti1234 Aug 24, 2024
4627718
clean:誤ったパスを設定した際に気づきやすいように
kutimiti1234 Aug 24, 2024
56e63a2
add:reverseオプションを実装
kutimiti1234 Aug 25, 2024
e24e0d4
clean:mainメソッドの表示の幅サイズを画面に
kutimiti1234 Aug 25, 2024
4206ffe
fix:引数なしの場合に何も表示されない事象を修正
kutimiti1234 Aug 25, 2024
6412c37
clean:インスタンス変数にする必要のないものを修正
kutimiti1234 Aug 25, 2024
88144b9
clean:わかりやすい変数名に変更
kutimiti1234 Aug 25, 2024
36864e4
clean:不要な再代入を削除
kutimiti1234 Aug 25, 2024
801e75b
clean:メソッドを機能ごとに分割
kutimiti1234 Sep 2, 2024
0385b41
fix:メソッド分割によって、parse_optionsを先に実行しないと、引数なしかつオプションありの場合に対応できないため
kutimiti1234 Sep 2, 2024
f15930a
clean:formatが異なるだけのls_shortとls_longのクラスを統合
kutimiti1234 Sep 2, 2024
9c213ff
clean:不要なアクセサメソッドを削除
kutimiti1234 Sep 2, 2024
be4239e
clean:entriesのみでクラスの集合であることを表すことができるため
kutimiti1234 Sep 3, 2024
3fa75b5
delete:一旦、fileを直接指定した場合の挙動を削除
kutimiti1234 Sep 10, 2024
f3a0dc4
clean:file_entryクラスをstatsを直接扱わないように設定
kutimiti1234 Sep 10, 2024
80e990c
clean:Rubocop
kutimiti1234 Sep 14, 2024
4f85c9d
delete:不要となったentriesモジュールを削除
kutimiti1234 Sep 14, 2024
7b9ea53
clean:DirEntryクラスからフォーマット機能を排除
kutimiti1234 Sep 15, 2024
5e92813
clean:メソッド名が命名規則として不自然だったため修正。FileEntriesListクラスに必要であったクラスインスタンス変数を削除
kutimiti1234 Sep 15, 2024
37facfa
clean:ls -l コマンドで通常表示するのはテキストのbasenameのため
kutimiti1234 Sep 15, 2024
7985169
clean:テスト対象のクラスの変更を受けてテストも変更
kutimiti1234 Sep 15, 2024
d5a4b45
clean:parse_options→parse_pathsの順で実行しない場合不具合が起きることをコメントで明記
kutimiti1234 Sep 15, 2024
ad522fc
clean:ls_longのフォーマット用クラスをinclude
kutimiti1234 Sep 15, 2024
73800f7
clean:LsLongクラスを変更したため、テストも変更
kutimiti1234 Sep 15, 2024
565528f
clean:複数のファイルを対象とした場合に毎回LsLongクラスを生成しなくても済むように修正
kutimiti1234 Sep 15, 2024
57fb6a0
clean:Rubocop
kutimiti1234 Sep 15, 2024
56a9c32
add:shebangを追加
kutimiti1234 Sep 15, 2024
8dbfbea
clean:LsShortクラスに通常時のlsコマンドのフォーマット機能を切り出した
kutimiti1234 Sep 15, 2024
da33e50
add:LsShortクラスのためのテスト用フォルダを作成
kutimiti1234 Sep 15, 2024
4e160f1
fix:LsShortフォーマットで表示されるファイルの間に空白一文字を追加
kutimiti1234 Sep 15, 2024
98c40fe
clean:MAX_DISPLAY_WIDTHはLsShortクラスのみで使うため、LsShortクラス内の定数として設定
kutimiti1234 Sep 16, 2024
4e21399
clean:IOクラスのrequireを忘れていたため
kutimiti1234 Sep 16, 2024
eba2aaa
clean:ぼっち演算子を避けるため
kutimiti1234 Sep 16, 2024
df55607
fix:指定したディレクトリ内にファイルが1つもない場合エラーが発生するため、ガード節を配置
kutimiti1234 Sep 16, 2024
43bcfb5
clean:Rubocop
kutimiti1234 Sep 16, 2024
aa76923
clean:重複した処理を簡潔に
kutimiti1234 Sep 16, 2024
e311f64
clean:LsShortの定数の定義場所を移動したため
kutimiti1234 Sep 16, 2024
ba17355
clean:それぞれのファイルタイプに対応
kutimiti1234 Sep 16, 2024
bdedfb3
add:複数のディレクトリを指定した場合に対応
kutimiti1234 Sep 16, 2024
29ee691
fix:LsCommandクラスの修正に伴ってテストケースも変更
kutimiti1234 Sep 16, 2024
3d76d92
fix:LsShortクラスの追加に伴ってテストケースも変更
kutimiti1234 Sep 16, 2024
13f08d5
clean:不要な条件節を削除
kutimiti1234 Sep 16, 2024
99e5208
clean:クラス名の省略を避けた
kutimiti1234 Sep 16, 2024
dd95986
fix:ファイルの日時のフォーマットが適用されていなかったため
kutimiti1234 Sep 16, 2024
001a21b
clean:クラス名をDirectoryEntryに統一したため
kutimiti1234 Sep 16, 2024
e34c448
fix:tapは実行した際のレシーバを返すため、yield_selfで対応
kutimiti1234 Sep 16, 2024
04b78bf
clean:不要な処理を削除
kutimiti1234 Sep 16, 2024
8e3869a
clean:無駄な行を削除
kutimiti1234 Sep 16, 2024
ee0a1de
clean:クラス外で呼び出されないメソッドのため
kutimiti1234 Sep 16, 2024
3a434d6
clean:Rubocop
kutimiti1234 Sep 16, 2024
9f901b8
clean:評判のよくないメソッド名なので、別のエイリアスを使う
kutimiti1234 Sep 16, 2024
0cec18e
clean:不要な変数への代入を省略
kutimiti1234 Sep 16, 2024
2d88508
clean;端末の環境によって画面サイズは異なるため定数から変更
kutimiti1234 Sep 16, 2024
03a5d77
clean:Rubocop
kutimiti1234 Sep 16, 2024
6ac6c30
clean:より分かりやすい変数名に変更
kutimiti1234 Sep 16, 2024
ffaa759
clean:変数名が冗長だったため変更
kutimiti1234 Sep 18, 2024
24b7f08
clean:変数名をメソッド側に統一
kutimiti1234 Sep 18, 2024
b4bd436
clean:クラス内部で閉じている変数のため、initializeメソッドの引数から除外
kutimiti1234 Sep 18, 2024
8afe3b4
clean:クラス名を実態に合った名前に
kutimiti1234 Sep 18, 2024
1c70bb5
clean:冗長なクラス設計を排除
kutimiti1234 Sep 18, 2024
669dd28
clean:クラス設計の変更を受けてテスト内容を変更
kutimiti1234 Sep 18, 2024
4d1c8a2
clean:テストファイルの名前を間違えていたたため
kutimiti1234 Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions 07.ls_object/directory_entry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

require_relative 'file_entry'

class DirectoryEntry
attr_accessor :path, :file_entries

def initialize(path, options)
@path = path
@options = options
@file_entries = collect_file_entries
end

def find_max_sizes
%i[nlink user group size time].to_h do |key|
[key, @file_entries.map { |entry| entry.send(key).size }.max]
end
end

def total
@file_entries.map(&:blocks).sum
end

private

def collect_file_entries
pattern = @path.join('*')
params = @options[:dot_match] ? [pattern, File::FNM_DOTMATCH] : [pattern]
Dir.glob(*params)
.map { |file| FileEntry.new(file) }
.sort_by(&:name)
.then { |file_entries| @options[:reverse] ? file_entries.reverse : file_entries }
end
end
62 changes: 62 additions & 0 deletions 07.ls_object/file_entry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

オブジェクトっぽくなりましたね! 👍


require 'etc'

FILE_TYPES = {
'file' => '-',
'directory' => 'd',
'characterSpecial' => 'c',
'blockSpecial' => 'b',
'link' => 'l',
'socket' => 's',
'fifo' => 'p'
}.freeze
BLOCK_SIZE = 1024

class FileEntry
attr_reader :path

def initialize(path)
@path = path
@stat = File.lstat(@path)
end

def mode
file_type = FILE_TYPES[@stat.ftype]
file_permissions = @stat.mode.to_s(8).rjust(6, '0')[3..5].chars.map do |c|
rwx = %w[- x w . r]
rwx[c.to_i & 0b100] + rwx[c.to_i & 0b010] + rwx[c.to_i & 0b001]
end.join

file_type + file_permissions
end

def nlink
@stat.nlink.to_s
end

def user
Etc.getpwuid(@stat.uid).name
end

def group
Etc.getgrgid(@stat.gid).name
end

def size
@stat.size.to_s
end

def time
@stat.mtime.strftime('%-m月 %e %H:%M')
end

def name
File.basename(@path)
end

def blocks
# File::statのブロックサイズの単位は512bytesであるから変換する
@stat.blocks * (512 / BLOCK_SIZE.to_f)
end
end
18 changes: 18 additions & 0 deletions 07.ls_object/long_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

class LongFormatter
def run(entries)
header = ["合計 #{entries.total.to_i}"]
max_sizes = entries.find_max_sizes
body = entries.file_entries.map do |file|
"#{file.mode} "\
"#{file.nlink.rjust(max_sizes[:nlink])} " \
"#{file.user.ljust(max_sizes[:user])} " \
"#{file.group.ljust(max_sizes[:group])} " \
"#{file.size.rjust(max_sizes[:size])} " \
"#{file.time.rjust(max_sizes[:time])} " \
"#{file.name}" \
end.join("\n")
[header, body].join("\n")
end
end
27 changes: 27 additions & 0 deletions 07.ls_object/ls_command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

require 'io/console'
require_relative 'directory_entry'
require_relative 'short_formatter'
require_relative 'long_formatter'

class LsCommand
def initialize(paths, options)
@paths = paths
@options = options
@formatter = @options[:long_format] ? LongFormatter.new : ShortFormatter.new
@directories = @paths.map { |path| DirectoryEntry.new(path, @options) }
.sort_by(&:path)
.then { |directories| @options[:reverse] ? directories.reverse : directories }
end

def run
output = @directories.map do |dir_entry|
header = "#{dir_entry.path}:" if @directories.count > 1
body = @formatter.run(dir_entry)
[header, body].compact.join("\n")
end.join("\n\n").rstrip

puts output
end
end
16 changes: 16 additions & 0 deletions 07.ls_object/main.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'optparse'
require 'pathname'
require_relative 'ls_command'

opt = OptionParser.new
options = { long_format: false, reverse: false, dot_match: false }
opt.on('-l') { |v| options[:long_format] = v }
opt.on('-r') { |v| options[:reverse] = v }
opt.on('-a') { |v| options[:dot_match] = v }
opt.parse!(ARGV)
paths = ARGV.empty? ? [Pathname('.')] : ARGV.map { |path| Pathname(path) }
command = LsCommand.new(paths, options)
command.run
36 changes: 36 additions & 0 deletions 07.ls_object/short_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

require 'io/console'
class ShortFormatter
def initialize
@window_width = IO.console.winsize[1]
end

def run(entries)
return if entries.file_entries.count.zero?

max_file_name_count = entries.file_entries.map { |f| f.name.size }.max
col_count = @window_width / (max_file_name_count + 1)
row_count = col_count.zero? ? entries.count : (entries.file_entries.count.to_f / col_count).ceil
transposed_file_entries = safe_transpose(entries.file_entries.each_slice(row_count).to_a)
format_table(transposed_file_entries, max_file_name_count)
end

private

def safe_transpose(nested_file_entries)
nested_file_entries[0].zip(*nested_file_entries[1..]).map(&:compact)
end

def format_table(file_entries, max_file_name_size)
file_entries.map do |row_file_entries|
render_short_format_row(row_file_entries, max_file_name_size)
end.join("\n")
end

def render_short_format_row(row_file_entries, max_file_name_size)
row_file_entries.map do |file_entry|
file_entry.name.ljust(max_file_name_size + 1)
end.join.rstrip
end
end
15 changes: 15 additions & 0 deletions test/directory_entry_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require 'minitest/autorun'
require 'pathname'
require 'io/console'
require_relative '../07.ls_object/directory_entry'

class DirEntryTest < Minitest::Test
def test_dir_total
expected_total = 4
options = { dot_match: false }
directory = DirectoryEntry.new(Pathname('test/files/'), options)
assert_equal expected_total, directory.total
end
end
79 changes: 79 additions & 0 deletions test/file_entry_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# frozen_string_literal: true

require 'minitest/autorun'
require 'pathname'
require_relative '../07.ls_object/file_entry'

class FileEntryTest < Minitest::Test
def test_monde_file
expected_mode = <<~TEXT.chomp
-rw-r--r--
TEXT
file = FileEntry.new(Pathname('./test/files/test.txt'))
assert_equal expected_mode, file.mode
end

def test_monde_directory
expected_mode = <<~TEXT.chomp
drwxr-xr-x
TEXT
file = FileEntry.new(Pathname('./test/files'))
assert_equal expected_mode, file.mode
end

def test_monde_link
expected_mode = <<~TEXT.chomp
lrwxrwxrwx
TEXT
file = FileEntry.new(Pathname('./test/files/text_link'))
assert_equal expected_mode, file.mode
end

def test_nlink
expected_mode = <<~TEXT.chomp
1
TEXT
file = FileEntry.new(Pathname('./test/files/test.txt'))
assert_equal expected_mode, file.nlink
end

def test_user
expected_mode = <<~TEXT.chomp
migi
TEXT
file = FileEntry.new(Pathname('./test/files/test.txt'))
assert_equal expected_mode, file.user
end

def test_group
expected_mode = <<~TEXT.chomp
migi
TEXT
file = FileEntry.new(Pathname('./test/files/test.txt'))
assert_equal expected_mode, file.group
end

def test_size
expected_mode = <<~TEXT.chomp
5
TEXT
file = FileEntry.new(Pathname('./test/files/test.txt'))
assert_equal expected_mode, file.size
end

def test_time
expected_mode = <<~TEXT.chomp
9月 11 00:11
TEXT
file = FileEntry.new(Pathname('./test/files/test.txt'))
assert_equal expected_mode, file.time
end

def test_mode
expected_mode = <<~TEXT.chomp
test.txt
TEXT
file = FileEntry.new(Pathname('./test/files/test.txt'))
assert_equal expected_mode, file.name
end
end
1 change: 1 addition & 0 deletions test/files/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
1 change: 1 addition & 0 deletions test/files/text_link
21 changes: 21 additions & 0 deletions test/long_formatter_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

require 'minitest/autorun'
require 'pathname'
require 'io/console'
require_relative '../07.ls_object/long_formatter'
require_relative '../07.ls_object/directory_entry'

class DirEntryTest < Minitest::Test
def test_ls_long_if_target_is_file
expected = <<~TEXT.chomp
合計 4
-rw-r--r-- 1 migi migi 5 9月 11 00:11 test.txt
lrwxrwxrwx 1 migi migi 8 9月 11 00:21 text_link
TEXT
options = { dot_match: false }
directory = DirectoryEntry.new(Pathname('./test/files'), options)
formatter = LongFormatter.new
assert_equal expected, formatter.run(directory)
end
end
53 changes: 53 additions & 0 deletions test/ls_command_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

require 'minitest/autorun'
require 'pathname'
require_relative '../07.ls_object/ls_command'

class LsCommandTest < Minitest::Test
def test_run_with_multiple_directories
LsCommand.class_eval do
attr_accessor :formatter
end

ShortFormatter.class_eval do
attr_accessor :window_width
end

expected = <<~TEXT
./test/ls_short_test_files/:
123456789 defghijk z zzzzz
56789 directory zz zzzzzz
9999 ls.rb zzz
abc qwertfdsdf zzzz

./test/ls_short_test_files/:
123456789 defghijk z zzzzz
56789 directory zz zzzzzz
9999 ls.rb zzz
abc qwertfdsdf zzzz
TEXT
path1 = Pathname('./test/ls_short_test_files/')
path2 = Pathname('./test/ls_short_test_files/')
paths = [path1, path2]
options = { long_format: false, dot_match: false }
command = LsCommand.new(paths, options)
command.instance_variable_set(:@format, ShortFormatter.new)
command.formatter.window_width = 50
assert_output(expected) { command.run }
end

def test_run_long
expected = <<~TEXT
合計 4
-rw-r--r-- 1 migi migi 5 9月 11 00:11 test.txt
lrwxrwxrwx 1 migi migi 8 9月 11 00:21 text_link
TEXT
options = { long_format: true, dot_match: false }

path = Pathname('./test/files')
paths = [path]
command = LsCommand.new(paths, options)
assert_output(expected) { command.run }
end
end
Loading