Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
76316a4
first commit
yokomaru Sep 14, 2024
db6a683
add: Ls and Test
yokomaru Sep 16, 2024
e33b543
add: a and r options
yokomaru Sep 16, 2024
8df64dd
fix: class name ls_file to file_data
yokomaru Sep 16, 2024
31006e3
add: l option and test
yokomaru Sep 18, 2024
9f1202d
fix: remove FileStatus from FileData
yokomaru Sep 18, 2024
0a23a16
fix: display
yokomaru Sep 18, 2024
52a7e6b
add: 一旦commandパターンで作成
yokomaru Sep 18, 2024
56381d8
add: options
yokomaru Sep 18, 2024
e9a7794
fix: FileStatus and move directory
yokomaru Sep 18, 2024
65df832
fix: ls.rb
yokomaru Sep 18, 2024
99c65ae
fix: test
yokomaru Sep 18, 2024
1f242e4
fix: apply rubocop error check
yokomaru Sep 18, 2024
cec0f05
fix: execute
yokomaru Sep 18, 2024
3905060
add: width
yokomaru Sep 18, 2024
66d2082
fix: format
yokomaru Sep 18, 2024
bd46011
add: format class
yokomaru Sep 19, 2024
522334e
fix: delete require and test and line break
yokomaru Sep 19, 2024
f25684b
fix: move total_block to long_format
yokomaru Sep 19, 2024
981a9f4
delete: unnecessary attr_reader
yokomaru Sep 19, 2024
48b911e
fix: delete line break
yokomaru Sep 19, 2024
7540509
fix: move build_file_status to file_data from file_status
yokomaru Sep 19, 2024
94d606d
update: argment name
yokomaru Sep 19, 2024
89c8670
fix: render
yokomaru Sep 19, 2024
8bd1de4
update: test
yokomaru Sep 19, 2024
f2c5f56
fix: require
yokomaru Sep 19, 2024
589fa08
add: require 'io/console'
yokomaru Sep 19, 2024
534db11
add: instance variable short_format_data
yokomaru Sep 19, 2024
6490225
fix: variable name
yokomaru Sep 20, 2024
998c721
fix: longformat test
yokomaru Sep 20, 2024
ea4d055
delete: Option classes
yokomaru Sep 30, 2024
ddaeb3c
fix: rename Format to Formatter
yokomaru Sep 30, 2024
a99e31f
fix: integrate FileData and FileStatus
yokomaru Sep 30, 2024
433e8ec
delete: test_file.txt
yokomaru Sep 30, 2024
ed15658
fix: rename method name at Formatter
yokomaru Oct 1, 2024
645681b
fix: rename method name at LsCommand
yokomaru Oct 1, 2024
b1254f5
add: special permission
yokomaru Oct 8, 2024
71ece3b
add: filetype
yokomaru Oct 8, 2024
1dfdf57
fix: test symbolic link
yokomaru Oct 8, 2024
10f613b
fix: make symbolic link
yokomaru Oct 8, 2024
16634c1
fix: special permission
yokomaru Oct 17, 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
21 changes: 21 additions & 0 deletions 07.ls_object/bin/ls.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'io/console'
require 'optparse'
require_relative '../lib/ls_command'

if __FILE__ == $PROGRAM_NAME
opt = OptionParser.new

params = { dot_match: false }
opt.on('-a') { |v| params[:dot_match] = v }
opt.on('-r') { |v| params[:reverse] = v }
opt.on('-l') { |v| params[:long_format] = v }
opt.parse!(ARGV)
path = ARGV[0] || '.'
width = IO.console.winsize[1]

ls = LsCommand.new(path, width:, **params)
puts ls.formatted_output
end
82 changes: 82 additions & 0 deletions 07.ls_object/lib/file/file_data.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'etc'
require 'pathname'

class FileData

Choose a reason for hiding this comment

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

こちらと FileStatus を分ける意義があまり感じられませんでした。ファイルのメタデータを扱うクラスが一つあれば十分かなと思いました。どうでしょうか。

attr_reader :name, :file_status

PERMISSION_START_POSITION = 7
PERMISSION_END_POSITION = 9

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

SPECIAL_PERMISSION = {
0 => 's',
1 => 's',
2 => 't'
}.freeze

PERMISSION_TYPE = {
'000' => '---',
'001' => '--x',
'010' => '-w-',
'011' => '-wx',
'100' => 'r--',
'101' => 'r-x',
'110' => 'rw-',
'111' => 'rwx'
}.freeze

def initialize(name, path)
@name = name
@full_path = Pathname(File.absolute_path(@name, path))
@file_type = File.ftype(@full_path)
@file_status = build_file_status(File.lstat(@full_path))
end

private

def build_file_status(status)
{
type: FILE_TYPE[status.ftype],
mode: mode(status),
hardlink_nums: status.nlink.to_s,
owner_name: Etc.getpwuid(status.uid).name,
group_name: Etc.getgrgid(status.gid).name,
bytesize: rdev_or_bytesize(status),
latest_modify_datetime: status.mtime.strftime('%_m %e %H:%M'),
filename: file_name(status),
blocks: status.blocks
}
end

def file_name(status)
status.symlink? ? "#{@name} -> #{File.readlink(@full_path)}" : @name
end

def mode(status)
mode_binary_numbers = status.mode.to_s(2).rjust(16, '0')
# SUID、SGID、STICKEYBITの順番で特殊権限をチェック
mode_binary_numbers[4..6].each_char.with_index.map do |special_permission, i|
range_start = PERMISSION_START_POSITION + (3 * i)
range_end = PERMISSION_END_POSITION + (3 * i)
permission = PERMISSION_TYPE[mode_binary_numbers[range_start..range_end]].dup
permission[2] = permission[2] == 'x' ? SPECIAL_PERMISSION[i] : SPECIAL_PERMISSION[i].upcase if special_permission == '1'
permission
end.join
end

def rdev_or_bytesize(status)
%w[characterSpecial blockSpecial].include?(@file_type) ? format('%#01x', status.rdev.to_s(10)) : status.size.to_s
end
end
9 changes: 9 additions & 0 deletions 07.ls_object/lib/format/formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class Formatter
def initialize(files)
@files = files
end

def format; end
end
50 changes: 50 additions & 0 deletions 07.ls_object/lib/format/long_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

require_relative 'formatter'

class LongFormatter < Formatter
def initialize(files, path)
super(files)
@path = path
@max_sizes = build_max_sizes
@total_block = sum_blocks
@long_format_data = build_long_format_data
end

def format
total = "total #{@total_block}" if File.directory?(@path) || @files.size > 1
[total, *@long_format_data].compact.join("\n")
end

private

def build_long_format_data
@files.map { |file| format_row(file.file_status, *@max_sizes) }
end

def build_max_sizes
%i[hardlink_nums owner_name group_name bytesize].map do |key|
find_max_size(key)
end
end

def find_max_size(key)
@files.map { |file| file.file_status[key].size }.max
end

def format_row(status, max_nlink, max_user, max_group, max_size)
[
"#{status[:type]}#{status[:mode]}",
" #{status[:hardlink_nums].rjust(max_nlink)}",
" #{status[:owner_name].ljust(max_user)}",
" #{status[:group_name].ljust(max_group)}",
" #{status[:bytesize].rjust(max_size)}",
" #{status[:latest_modify_datetime]}",
" #{status[:filename]}"
].join
end

def sum_blocks
@files.sum { |file| file.file_status[:blocks] }
end
end
44 changes: 44 additions & 0 deletions 07.ls_object/lib/format/short_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

require_relative 'formatter'

class ShortFormatter < Formatter
def initialize(files, width)
super(files)
@width = width
@max_file_name = @files.map { |file| File.basename(file.name).size }.max
@col_count = calculate_col_count
@row_count = calculate_row_count
@short_format_data = build_short_format_data
end

def format
@short_format_data.join("\n")
end

private

def build_short_format_data
safe_transpose.map { |row_files| build_short_format_row(row_files) }
end

def build_short_format_row(row_files)
row_files.map do |file|
basename = file ? File.basename(file.name) : ''
basename.ljust(@max_file_name + 1)
end.join.rstrip
end

def calculate_col_count
@width / (@max_file_name + 1)
end

def calculate_row_count
@col_count.zero? ? @files.count : (@files.count.to_f / @col_count).ceil
end

def safe_transpose
nested_file_names = @files.each_slice(@row_count).to_a
nested_file_names[0].zip(*nested_file_names[1..])
end
end
36 changes: 36 additions & 0 deletions 07.ls_object/lib/ls_command.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

require_relative 'file/file_data'
require_relative 'format/long_formatter'
require_relative 'format/short_formatter'

class LsCommand
def initialize(path, width: 80, dot_match: false, reverse: false, long_format: false)
@path = path
@width = width
@dot_match = dot_match
@reverse = reverse
@long_format = long_format
@files = build_dot_match_and_sorted_files
end

def formatted_output
@long_format ? LongFormatter.new(@files, @path).format : ShortFormatter.new(@files, @width).format
end

private

def build_dot_match_and_sorted_files
files = generate_files
matched_files = @dot_match ? files : files.filter { |file| !/^\./.match?(file.name) }
@reverse ? matched_files.sort_by(&:name).reverse : matched_files.sort_by(&:name)
end

def generate_files
if File.directory?(@path)
Dir.open(@path).entries.map { |name| FileData.new(name, @path) }
else
[FileData.new(@path, File.dirname(@path))]
end
end
end
Loading