forked from fjordllc/ruby-practices
-
Notifications
You must be signed in to change notification settings - Fork 0
オブジェクト指向版lsコマンドの実装 #14
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
Open
kutimiti1234
wants to merge
71
commits into
main
Choose a base branch
from
my-ls_object
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
71 commits
Select commit
Hold shift + click to select a range
ace2983
add:ls -lの実装
kutimiti1234 c900f9f
clean:不要な条件を削除
kutimiti1234 5436be0
clean:LsLongとLsShortの共通の振る舞いを持つ親クラスLsCommandを設定
kutimiti1234 50fed16
clean:冗長なパス指定を削除
kutimiti1234 dbf3857
clean:適切なメソッド名へ変更
kutimiti1234 27a620f
clean:render機能をentry側に移管
kutimiti1234 b9b39c4
add:dirクラスにデフォルトのlsコマンド機能を実装
kutimiti1234 c543bc7
add:filelistクラスにデフォルトのlsコマンド機能を実装
kutimiti1234 71d9b2e
add:lsコマンドの制御用クラスを作成。main.rbも整備
kutimiti1234 fa552f4
add:Dirクラスの実態がEntryクラスよりもFileEntriesListの機能に似ているため、Entryクラスを廃した。また、Di…
kutimiti1234 4627718
clean:誤ったパスを設定した際に気づきやすいように
kutimiti1234 56e63a2
add:reverseオプションを実装
kutimiti1234 e24e0d4
clean:mainメソッドの表示の幅サイズを画面に
kutimiti1234 4206ffe
fix:引数なしの場合に何も表示されない事象を修正
kutimiti1234 6412c37
clean:インスタンス変数にする必要のないものを修正
kutimiti1234 88144b9
clean:わかりやすい変数名に変更
kutimiti1234 36864e4
clean:不要な再代入を削除
kutimiti1234 801e75b
clean:メソッドを機能ごとに分割
kutimiti1234 0385b41
fix:メソッド分割によって、parse_optionsを先に実行しないと、引数なしかつオプションありの場合に対応できないため
kutimiti1234 f15930a
clean:formatが異なるだけのls_shortとls_longのクラスを統合
kutimiti1234 9c213ff
clean:不要なアクセサメソッドを削除
kutimiti1234 be4239e
clean:entriesのみでクラスの集合であることを表すことができるため
kutimiti1234 3fa75b5
delete:一旦、fileを直接指定した場合の挙動を削除
kutimiti1234 f3a0dc4
clean:file_entryクラスをstatsを直接扱わないように設定
kutimiti1234 80e990c
clean:Rubocop
kutimiti1234 4f85c9d
delete:不要となったentriesモジュールを削除
kutimiti1234 7b9ea53
clean:DirEntryクラスからフォーマット機能を排除
kutimiti1234 5e92813
clean:メソッド名が命名規則として不自然だったため修正。FileEntriesListクラスに必要であったクラスインスタンス変数を削除
kutimiti1234 37facfa
clean:ls -l コマンドで通常表示するのはテキストのbasenameのため
kutimiti1234 7985169
clean:テスト対象のクラスの変更を受けてテストも変更
kutimiti1234 d5a4b45
clean:parse_options→parse_pathsの順で実行しない場合不具合が起きることをコメントで明記
kutimiti1234 ad522fc
clean:ls_longのフォーマット用クラスをinclude
kutimiti1234 73800f7
clean:LsLongクラスを変更したため、テストも変更
kutimiti1234 565528f
clean:複数のファイルを対象とした場合に毎回LsLongクラスを生成しなくても済むように修正
kutimiti1234 57fb6a0
clean:Rubocop
kutimiti1234 56a9c32
add:shebangを追加
kutimiti1234 8dbfbea
clean:LsShortクラスに通常時のlsコマンドのフォーマット機能を切り出した
kutimiti1234 da33e50
add:LsShortクラスのためのテスト用フォルダを作成
kutimiti1234 4e160f1
fix:LsShortフォーマットで表示されるファイルの間に空白一文字を追加
kutimiti1234 98c40fe
clean:MAX_DISPLAY_WIDTHはLsShortクラスのみで使うため、LsShortクラス内の定数として設定
kutimiti1234 4e21399
clean:IOクラスのrequireを忘れていたため
kutimiti1234 eba2aaa
clean:ぼっち演算子を避けるため
kutimiti1234 df55607
fix:指定したディレクトリ内にファイルが1つもない場合エラーが発生するため、ガード節を配置
kutimiti1234 43bcfb5
clean:Rubocop
kutimiti1234 aa76923
clean:重複した処理を簡潔に
kutimiti1234 e311f64
clean:LsShortの定数の定義場所を移動したため
kutimiti1234 ba17355
clean:それぞれのファイルタイプに対応
kutimiti1234 bdedfb3
add:複数のディレクトリを指定した場合に対応
kutimiti1234 29ee691
fix:LsCommandクラスの修正に伴ってテストケースも変更
kutimiti1234 3d76d92
fix:LsShortクラスの追加に伴ってテストケースも変更
kutimiti1234 13f08d5
clean:不要な条件節を削除
kutimiti1234 99e5208
clean:クラス名の省略を避けた
kutimiti1234 dd95986
fix:ファイルの日時のフォーマットが適用されていなかったため
kutimiti1234 001a21b
clean:クラス名をDirectoryEntryに統一したため
kutimiti1234 e34c448
fix:tapは実行した際のレシーバを返すため、yield_selfで対応
kutimiti1234 04b78bf
clean:不要な処理を削除
kutimiti1234 8e3869a
clean:無駄な行を削除
kutimiti1234 ee0a1de
clean:クラス外で呼び出されないメソッドのため
kutimiti1234 3a434d6
clean:Rubocop
kutimiti1234 9f901b8
clean:評判のよくないメソッド名なので、別のエイリアスを使う
kutimiti1234 0cec18e
clean:不要な変数への代入を省略
kutimiti1234 2d88508
clean;端末の環境によって画面サイズは異なるため定数から変更
kutimiti1234 03a5d77
clean:Rubocop
kutimiti1234 6ac6c30
clean:より分かりやすい変数名に変更
kutimiti1234 ffaa759
clean:変数名が冗長だったため変更
kutimiti1234 24b7f08
clean:変数名をメソッド側に統一
kutimiti1234 b4bd436
clean:クラス内部で閉じている変数のため、initializeメソッドの引数から除外
kutimiti1234 8afe3b4
clean:クラス名を実態に合った名前に
kutimiti1234 1c70bb5
clean:冗長なクラス設計を排除
kutimiti1234 669dd28
clean:クラス設計の変更を受けてテスト内容を変更
kutimiti1234 4d1c8a2
clean:テストファイルの名前を間違えていたたため
kutimiti1234 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| 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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| test |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| test.txt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
オブジェクトっぽくなりましたね! 👍