Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

オブジェクト指向版lsコマンドを作ってみた #2

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions 08.ls_object/.rubocop.yml
@@ -0,0 +1,11 @@
inherit_gem:
rubocop-fjord:
- "config/rubocop.yml"

AllCops:
Exclude:
- test/fixtures/**/*

Metrics/MethodLength:
Exclude:
- test/**/*
9 changes: 9 additions & 0 deletions 08.ls_object/Rakefile
@@ -0,0 +1,9 @@
# frozen_string_literal: true

require 'rake/testtask'

Rake::TestTask.new do |t|
t.pattern = 'test/**/*_test.rb'
end

task default: :test
6 changes: 6 additions & 0 deletions 08.ls_object/bin/ls
@@ -0,0 +1,6 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require_relative '../lib/ls_app'

LsApp.main
18 changes: 18 additions & 0 deletions 08.ls_object/lib/ls_app.rb
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require_relative 'params'
require_relative 'models/ls_file'
require_relative 'views/short_format'
require_relative 'views/long_format'

module LsApp
def self.main(argv = ARGV)
params = Params.new(argv)
ls_files = LsFile.all(params)
if params.long_format?
puts LongFormat.new(ls_files).render
else
puts ShortFormat.new(ls_files).render
end
end
end
65 changes: 65 additions & 0 deletions 08.ls_object/lib/models/ls_file.rb
@@ -0,0 +1,65 @@
# frozen_string_literal: true

require 'etc'
require 'pathname'

class LsFile
def self.all(params)
pattern = File.join(params.target_directory, '*')
flag = params.dot_match? ? File::FNM_DOTMATCH : 0
paths = Dir.glob(pattern, flag)
paths << File.join(params.target_directory, '..') if params.dot_match?
sorted_paths = params.reverse? ? paths.sort.reverse : paths.sort
sorted_paths.map { |path| LsFile.new(path) }
end

def initialize(path)
@pathname = Pathname.new(path)
end

def name
@pathname.basename.to_s
end

def block_size
file_stat.blocks
end

def file_type
file_stat.ftype
end

def permission
file_stat.mode.to_s(8)[-3..]
end

def link_count
@pathname.directory? ? @pathname.entries.size : 1
end

def owner_name
Etc.getpwuid(file_stat.uid).name
end

def group_name
Etc.getgrgid(file_stat.gid).name
end

def bytesize
@pathname.size
end

def mtime
@pathname.mtime
end

def to_s
name
end

private

def file_stat
@pathname.stat
end
end
31 changes: 31 additions & 0 deletions 08.ls_object/lib/params.rb
@@ -0,0 +1,31 @@
# frozen_string_literal: true

require 'optparse'

class Params
attr_reader :target_directory

def initialize(argv)
opt = OptionParser.new

@params = {}
opt.on('-a') { @params[:a] = true }
opt.on('-l') { @params[:l] = true }
opt.on('-r') { @params[:r] = true }

paths = opt.parse!(argv)
@target_directory = paths[0] || Dir.getwd
end

def dot_match?
!!@params[:a]
end

def reverse?
!!@params[:r]
end

def long_format?
!!@params[:l]
end
end
61 changes: 61 additions & 0 deletions 08.ls_object/lib/views/long_format.rb
@@ -0,0 +1,61 @@
# frozen_string_literal: true

class LongFormat
FILE_TYPE_TABLE = {
'file' => '-',
'directory' => 'd'
}.freeze

PERMISSION_TABLE = {
'1' => '--x',
'2' => '-w-',
'3' => '-wx',
'4' => 'r--',
'5' => 'r-x',
'6' => 'rw-',
'7' => 'rwx'
}.freeze

def initialize(ls_files)
@ls_files = ls_files
end

def render
total = @ls_files.sum(&:block_size)
header = "total #{total}"
widths = calc_widths
body = @ls_files.map { |ls_file| format_row(ls_file, widths) }
[header, *body].join("\n")
end

private

def format_row(ls_file, widths)
[
"#{format_mode(ls_file)} ",
ls_file.link_count.to_s.rjust(widths[:link_count]),
"#{ls_file.owner_name.ljust(widths[:owner_name])} ",
"#{ls_file.group_name.ljust(widths[:group_name])} ",
ls_file.bytesize.to_s.rjust(widths[:bytesize]),
ls_file.mtime.strftime('%b %d %H:%M'),
ls_file.name
].join(' ')
end

def format_mode(ls_file)
file_type = FILE_TYPE_TABLE[ls_file.file_type]
permission = ls_file.permission.each_char.map { |c| PERMISSION_TABLE[c] }.join
file_type + permission
end

def calc_widths
widths = Hash.new { |h, k| h[k] = [] }
@ls_files.each do |ls_file|
widths[:link_count] << ls_file.link_count.to_s.size
widths[:owner_name] << ls_file.owner_name.size
widths[:group_name] << ls_file.group_name.size
widths[:bytesize] << ls_file.bytesize.to_s.size
end
widths.transform_values(&:max)
end
end
30 changes: 30 additions & 0 deletions 08.ls_object/lib/views/short_format.rb
@@ -0,0 +1,30 @@
# frozen_string_literal: true

class ShortFormat
COLUMN_COUNT = 3

def initialize(ls_files)
@ls_files = ls_files
end

def render
column_width = @ls_files.map { |ls_file| ls_file.name.size }.max
ls_file_table = generate_ls_file_table
ls_file_table.map { |row| format_row(row, column_width) }.join("\n")
end

private

def format_row(row, column_width)
row.map { |ls_file| ls_file.name.ljust(column_width) }.join(' ').strip
end

def generate_ls_file_table
row_count = (@ls_files.size.to_f / COLUMN_COUNT).ceil
ls_file_table = @ls_files.each_slice(row_count).to_a
blank_count = row_count - ls_file_table[-1].size
blank_ls_files = Array.new(blank_count, LsFile.new(''))
ls_file_table[-1].push(*blank_ls_files)
ls_file_table.transpose
end
end
74 changes: 74 additions & 0 deletions 08.ls_object/test/ls_app_test.rb
@@ -0,0 +1,74 @@
# frozen_string_literal: true

require 'minitest/autorun'
require_relative '../lib/ls_app'

class LsAppTest < Minitest::Test
def setup
@directory = Pathname.new(__dir__).join('fixtures/config')
end

def test_long_format
expected = <<~TEXT
total 80
drwxr-xr-x 15 jnito staff 480 Apr 29 12:20 .
drwxr-xr-x 4 jnito staff 128 Apr 29 12:37 ..
-rw-r--r-- 1 jnito staff 740 Mar 20 17:45 application.rb
-rw-r--r-- 1 jnito staff 207 Mar 20 17:45 boot.rb
-rw-r--r-- 1 jnito staff 193 Mar 20 17:45 cable.yml
-rw-r--r-- 1 jnito staff 464 Mar 20 17:45 credentials.yml.enc
-rw-r--r-- 1 jnito staff 620 Mar 20 17:45 database.yml
-rw-r--r-- 1 jnito staff 128 Mar 20 17:45 environment.rb
drwxr-xr-x 5 jnito staff 160 Mar 20 17:45 environments
-rw-r--r-- 1 jnito staff 381 Apr 10 07:35 importmap.rb
drwxr-xr-x 9 jnito staff 288 Mar 20 17:45 initializers
drwxr-xr-x 5 jnito staff 160 Mar 20 17:45 locales
-rw-r--r-- 1 jnito staff 1792 Mar 20 17:45 puma.rb
-rw-r--r-- 1 jnito staff 322 Mar 20 17:45 routes.rb
-rw-r--r-- 1 jnito staff 1152 Mar 20 17:45 storage.yml
TEXT
argv = ['-al', @directory]
assert_output(expected) do
LsApp.main(argv)
end
end

def test_short_format_without_options
expected = <<~TEXT
application.rb environment.rb puma.rb
boot.rb environments routes.rb
cable.yml importmap.rb storage.yml
credentials.yml.enc initializers
database.yml locales
TEXT
argv = [@directory]
assert_output(expected) do
LsApp.main(argv)
end
end

def test_short_format_with_a_and_r_options
expected = <<~TEXT
storage.yml importmap.rb cable.yml
routes.rb environments boot.rb
puma.rb environment.rb application.rb
locales database.yml ..
initializers credentials.yml.enc .
TEXT
argv = ['-ar', @directory]
assert_output(expected) do
LsApp.main(argv)
end
end

def test_short_format_without_args
expected = <<~TEXT
Rakefile lib
bin test
TEXT
argv = []
assert_output(expected) do
LsApp.main(argv)
end
end
end