Skip to content

part5 - update, delete, list

前回はreadコマンドを実装した。今回は、updatedeletelistコマンドを実装する。どんどんサクサク進めていこう。

ユーザー情報の更新

やはりUpdadate.fsを作成し、そこでコマンドを作成してみよう。このコマンドはユーザーからIDの入力を受け付け、そのIDに基づいてcsvファイルを読み込む。存在しないときは、存在しないとコンソールに表示する。あとは読み込んだcsvデータをパースし、正しい情報であればそのまま表示、おかしなデータになっていたらデータが破損している旨を報告する。

Update.fs
module update
open read
let createNewCsvData (originalValues: string array) (newName: string) (newAge: string) (newEmail: string) =
let id = originalValues.[0]
let name =
if System.String.IsNullOrEmpty(newName) then
originalValues.[1]
else
newName
let age =
if System.String.IsNullOrEmpty(newAge) then
originalValues.[2]
else
newAge
let email =
if System.String.IsNullOrEmpty(newEmail) then
originalValues.[3]
else
newEmail
sprintf "%s,%s,%s,%s" id name age email
let update =
System.Console.Clear()
printfn "ID を入力してください。"
let id = System.Console.ReadLine()
let path = System.IO.Path.Combine("./users", id + ".csv")
let csvData = readCsv path
match csvData with
| Some values ->
let id = values.[0]
let name = values.[1]
let age = values.[2]
let email = values.[3]
printfn "ID: %s" id
printfn "名前: %s" name
printfn "年齢: %s" age
printfn "メールアドレス: %s" email
printfn "名前を入力してください(1文字以上、150字以内)。空欄の場合は変更しません。"
let newName = System.Console.ReadLine()
printfn "年齢を入力してください(0以上、150以下)。空欄の場合は変更しません。"
let newAge = System.Console.ReadLine()
printfn "メールアドレスを入力してください(任意)。空欄の場合は変更しません。"
let newEmail = System.Console.ReadLine()
let csvData = createNewCsvData values newName newAge newEmail
System.IO.File.WriteAllText(path, csvData) |> ignore
| None -> printfn "ユーザーが見つかりませんでした。"

createNewCsvData関数により、空欄のときにもとの値とする処理を追加している。ほとんどreadコマンドと同じだが、最後にcsvデータを更新する処理が追加されている。だいたいreadcreateを合体させたようなものだろう。readCsv関数はreadモジュールにあるので、Update.fsにもreadモジュールをインポートしている。fsprojファイルにUpdate.fsを追加するとき、依存するモジュールを先に追加する必要がある。

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Create.fs" />
<Compile Include="Read.fs" />
<Compile Include="Update.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
</Project>

Program.fsにも追加したいところだが、delete, listコマンドも作成するので、それらを先に作成する。

ユーザー情報の削除

Delete.fsを作成し、そこでコマンドを作成してみよう。このコマンドはユーザーからIDの入力を受け付け、そのIDに基づいてcsvファイルを削除する。削除が成功した場合は、削除した旨をコンソールに表示する。存在しないときは、存在しないとコンソールに表示する。

module delete
let yesAnswers = [| "y"; "yes"; "Y"; "YES"; "Yes" |]
let delete =
System.Console.Clear()
printfn "削除するユーザーのID を入力してください。"
let id = System.Console.ReadLine()
let path = System.IO.Path.Combine("./users", id + ".csv")
if System.IO.File.Exists(path) then
printfn "ID: %sを削除します。よろしいですか?" id
let answer = System.Console.ReadLine()
if yesAnswers |> Array.contains answer then
System.IO.File.Delete(path)
printfn "ユーザーを削除しました。"
else
printfn "削除をキャンセルしました。"
else
printfn "ユーザーが見つかりませんでした。"

deleteコマンドは、readコマンドと似ているが、最後にファイルを削除する処理が追加されている。削除するかどうかの確認をするために、yesAnswersという配列を用意している。yesAnswersに含まれている入力であれば、削除を実行する。fsprojファイルにDelete.fsを追加するとき、依存するモジュールを先に追加する必要がある。

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Create.fs" />
<Compile Include="Read.fs" />
<Compile Include="Update.fs" />
<Compile Include="Delete.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
</Project>

この調子で、listコマンドも作成してみよう。

ユーザー情報の一覧表示

List.fsを作成し、そこでコマンドを作成してみよう。このコマンドは./usersフォルダ内のすべてのcsvファイルを読み込み、それぞれのユーザー情報を表示する。ユーザーがいない場合は、ユーザーがいない旨を表示する。

module idList
let idList =
System.Console.Clear()
let files = System.IO.Directory.GetFiles("./users")
printfn "ユーザーID一覧"
files
|> Array.map (fun file -> System.IO.Path.GetFileNameWithoutExtension(file))
|> Array.iter (fun id -> printfn "%s" id)

Array.iterは、他の言語でいうところのforEachに近い。意味のある返り値はなく、ただリストの順番に副作用を起こす。

それでは、それぞれの関数をProgram.fsに追加する。その前に、まずはコマンドを追加する。

type Command =
| Create
| Read
| Help
| Update // 追加
| Delete // 追加
| List // 追加
| Unknown of string

Commandが追加されると、パターンマッチングの部分でエラーになっているはずだ。パターンマッチングは網羅的(exhaustive)でなければならない。Helpの部分にUpdateDeleteListを追加する。

let parseCommand args =
match args with
| [| "create" |] -> Create
| [| "read" |] -> Read
| [| "update" |] -> Update
| [| "delete" |] -> Delete
| [| "list" |] -> List
| [| "--help" |] -> Help
| [| "-h" |] -> Help
| [||] -> Help // コマンドがないときもヘルプを表示
| _ -> Unknown args.[0]

あとは実際にコマンドを実行する部分に、Update, Delete, Listを追加する。

全体像は以下の通りとなる。

Program.fs
open create
open read
open update // 追加
open idList // 追加
open delete // 追加
type Command =
| Create
| Read
| Help
| Update // 追加
| Delete // 追加
| List // 追加
| Unknown of string
let parseCommand args =
match args with
| [| "create" |] -> Create
| [| "read" |] -> Read
| [| "update" |] -> Update
| [| "delete" |] -> Delete
| [| "list" |] -> List
| [| "--help" |] -> Help
| [| "-h" |] -> Help
| [||] -> Help // コマンドがないときもヘルプを表示
| _ -> Unknown args.[0]
let showHelp =
printfn "Usage: dotnet run [command]"
printfn "Commands:"
printfn " create Create a new user"
printfn " read <ID> Read a user by ID"
printfn " update Update a user by ID"
printfn " delete Delete a user by ID"
printfn " list List all users"
printfn " --help, -h Show this help"
[<EntryPoint>]
let main argv =
let command = parseCommand argv
match command with
| Create -> create
| Read -> read // 追加
| Update -> update // 追加
| Delete -> delete // 追加
| List -> idList // 追加
| Help -> showHelp
| Unknown s -> printfn "Unknown command: %s" s
0

まとめ

これで、update, delete, listコマンドが実装された。dotnet runで実行してみよう。それぞれのコマンドが実行できることを確認しよう。

次は、いままで放置してきたエラーハンドリングを実装する。いろいろなエラーを見てみぬふりをしてきた。F#はエラー処理の扱いが非常に面白い言語である。