From 6f5e27fad4ea92eb8a793103147f847c85a36809 Mon Sep 17 00:00:00 2001 From: Rikuoh <mail@riq0h.jp> Date: Sun, 16 Feb 2025 21:39:06 +0900 Subject: [PATCH] 1.0 --- README.md | 1 + cal.rb | 28 +++++++++++++++ ls.rb | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ wc.rb | 60 +++++++++++++++++++++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 README.md create mode 100755 cal.rb create mode 100644 ls.rb create mode 100644 wc.rb diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2d95fb --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +GNU Coreutils commands reimplemented in Ruby. diff --git a/cal.rb b/cal.rb new file mode 100755 index 0000000..3fc01ff --- /dev/null +++ b/cal.rb @@ -0,0 +1,28 @@ +#!/usr/bin/ruby +# frozen_string_literal: true + +require 'date' +require 'optparse' + +year = Date.today.year +month = Date.today.month +week = %w[日 月 火 水 木 金 土] + +opt = OptionParser.new +opt.on('-m month') { |v| month = v.to_i } +opt.on('-y year') { |v| year = v.to_i } +opt.parse(ARGV) + +first_day = Date.new(year, month, 1) +last_day = Date.new(year, month, -1) +puts "#{month.to_s.rjust(7)}月\s#{year}" +puts week.join(' ') +print "\s\s\s" * first_day.wday + +(first_day..last_day).each do |date| + day_string = date.day.to_s.rjust(2, "\s") + day_string = "\e[7m#{day_string}\e[0m" if date == Date.today + print "#{day_string}\s" + puts if date.saturday? +end +puts diff --git a/ls.rb b/ls.rb new file mode 100644 index 0000000..0c7d257 --- /dev/null +++ b/ls.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'optparse' +require 'etc' + +COLUMNS = 3 +MARGIN = 3 + +TYPES = { + 'fifo' => 'p', + 'characterSpecial' => 'c', + 'directory' => 'd', + 'blockSpecial' => 'b', + 'file' => '-', + 'link' => 'l', + 'socket' => 's' +}.freeze + +PERMISSIONS = { + '0' => '---', + '1' => '--x', + '2' => '-w-', + '3' => '-wx', + '4' => 'r--', + '5' => 'r-x', + '6' => 'rw-', + '7' => 'rwx' +}.freeze + +def run + params = ARGV.getopts('a', 'r', 'l') + filenames = params['a'] ? Dir.glob('*', File::FNM_DOTMATCH) : Dir.glob('*') + filenames = filenames.reverse if params['r'] + params['l'] ? output_file_datails(filenames) : output(filenames) +end + +def output(filenames) + rows = filenames.size.ceildiv(COLUMNS) + pads = pad_created(filenames, rows) + (0...rows).each do |row| + COLUMNS.times do |column| + index = row + column * rows + print filenames[index].ljust(pads[column] + COLUMNS) if filenames[index] + end + puts + end +end + +def pad_created(filenames, rows) + filenames.each_slice(rows).map { _1.map(&:length).max } +end + +def output_file_datails(filenames) + puts total_file_blocks(filenames) + filenames.each do |file| + file_stat = File::Stat.new(file) + print TYPES[file_stat.ftype] + print file_mode(file_stat) + print owner_info(file_stat, filenames) + print time_stamp(file_stat) + print symbolic(file) + puts + end +end + +def owner_info(file_stat, filenames) + [ + file_stat.nlink.to_s.prepend(' '), + Etc.getpwuid(file_stat.uid).name, + Etc.getgrgid(file_stat.gid).name, + file_stat.size.to_s.rjust(max_filename_length(filenames)) + ].join(' ') +end + +def symbolic(filenames) + if File.lstat(filenames).symlink? + " #{filenames} -> #{File.readlink(filenames)}" + else + " #{filenames}" + end +end + +def file_mode(file_stat) + file_count = file_stat.mode.to_s(8).slice(-3, 3) + file_permission = file_count.split('').map do |file| + PERMISSIONS[file] + end + file_permission.join('') +end + +def max_filename_length(filenames) + filenames.map { |file| File.size(file) }.max.to_s.length + MARGIN +end + +def time_stamp(file_stat) + file_stat.mtime.strftime('%_m月 %_d %H:%M') +end + +def total_file_blocks(filenames) + blocks = filenames.map do |file| + File::Stat.new(file).blocks + end + "total #{blocks.sum}" +end + +run diff --git a/wc.rb b/wc.rb new file mode 100644 index 0000000..509a821 --- /dev/null +++ b/wc.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'optparse' + +def main + options, filenames = parse_options + file_stats = collect_file_stats(filenames) + total_stat = calculate_total_stat(file_stats) + max_widths = calculate_max_widths(total_stat) + + file_stats << total_stat if filenames.size > 1 + file_stats.each { |file_stat| puts format_row(file_stat, max_widths, options) } +end + +def parse_options + options = {} + OptionParser.new do |opts| + opts.on('-l') { options[:lines] = true } + opts.on('-w') { options[:words] = true } + opts.on('-c') { options[:bytes] = true } + end.parse! + options = { bytes: true, lines: true, words: true } if options.empty? + filenames = ARGV.empty? ? [''] : ARGV + [options, filenames] +end + +def collect_file_stats(filenames) + filenames.map do |filename| + text = filename.empty? ? ARGF.read : File.read(filename) + { + filename: filename, + lines: text.lines.count, + words: text.split.size, + bytes: text.bytesize + } + end +end + +def calculate_total_stat(file_stats) + total_stat = { filename: '合計', lines: 0, words: 0, bytes: 0 } + file_stats.each do |stats| + total_stat[:lines] += stats[:lines] + total_stat[:words] += stats[:words] + total_stat[:bytes] += stats[:bytes] + end + total_stat +end + +def calculate_max_widths(total_stat) + %i[lines words bytes].to_h { |key| [key, total_stat[key].to_s.length] } +end + +def format_row(stats, max_widths, options) + columns = %i[lines words bytes].filter_map do |key| + stats[key].to_s.rjust(max_widths[key]) if options[key] + end + [*columns, stats[:filename]].join(' ') +end + +main