PowerShell で ToDo 管理
これはPowerShell Advent Calendar 2014 : ATND 12日目の記事です。
PowerShellでToDo管理するスクリプトモジュールを作りました。
元々、PowerShellでスクリプトを書く練習用に作り始めたのですが、ちょっとしたメモなどを手軽に書き留めておけるので、割と重宝しています。
- コマンド1つでToDoの追加、削除、一覧できます。
- ToDoの項目毎にステータスを付けられます。
- Todo,Doing(着手・進行中),Done(完了)
- Milestone (期日・イベント事等、マイルストーン)
- 特に重要な項目にフラグ
!
を付けることが出来ます。 - ステータス、フラグの状態で色分け表示
コードは、github に置いてあります。 posh-todo.psm1 だけ落とせば使用できます。 https://github.com/pierre3/posh-todo
使い方
使い方は、以下の実行例を参照ください。
ちなみに、PowerShell_ISE 上での使用を推奨しています。(PowerShell.exe の方でも使用は可能ですが、色分けがちゃんとできないです。)
完全に自分用に作ったものなので、他の人にとって使いやすいかどうかは微妙なところですが、気になった方(がもしいれば)是非試してみて下さい。
TIPS
以下、小ネタです。
PowerShell起動時にposh-todoを開始してTODO一覧を表示
PowerShell(ISE)のプロファイルに以下の2行を追加しておくと、起動時にposh-todoが開始されて登録済みのToDo一覧が表示されるようになります。
Import-Module path\to\posh-todo.psm1 Start-PoshTodo path\to\todoFile.json
単にToDo一覧を表示するだけでは味気ないので、ようこそ!的な挨拶文も表示したいですね。
以下をStart-PoshTodo 内で呼ぶようにします。
function Show-StartingMessage { $today = (Get-Date).ToString("yyyy/MM/dd") Write-Host "こんにちは $env:USERNAME さん! 今日は $today です。" Write-Host ('{0}も{1}{2}{3}{4}ぞい!' -f ('今日', '1', '日', 'がん', 'ばる' | % { ($_,'ぞい')[(random 2)] })) }
これでPowerShellを起動するたびに以下のように表示されるようになります。
日付の入力はダイアログで
ToDo追加時のAdd-Todoコマンドで -setDate スイッチを指定すると、次のようなダイアログから日付を指定できるようになります。
このダイアログは、「Hey, Scripting Guy Windows PowerShell を使用して日付を入力する手間を省く方法はありますか 」から頂きました。
一部手を加えて、ダイアログのタイトルを指定したり、Enterキーでダイアログを閉じれるようにしたりしています。
コントロールにイベントハンドラを追加するにはAdd_KeyPress
のようなAdd_ + イベント名
のメソッドを使用するのですね。
ToDoデータは、.Netオブジェクトで保持してJSON 形式で保存
ToDoデータの扱い
ToDoデータは、後で扱うのに型があった方が何かと便利であろうと考え、.Netオブジェクトで保持するようにしました。
Add-Type -TypeDefinition @' using System; public enum TodoStatus { Todo, Milestone, Doing, Done } public class TodoItem { public int index { get; set; } public string text { get; set; } public DateTime date { get; set; } public bool flag { get; set; } public TodoStatus status { get; set; } } '@
保存はJSON
データは、ConvertTo-Json
コマンドとConvertFrom-Json
コマンドを使ってJSON形式で保存、復元します。
書き出し時は、TodoItemの配列をそのままConvertTo-Json
に渡すだけで楽ちんなのですが、
読み取った結果はPSCustomObjectで帰ってくるため、TodoItemオブジェクトに格納するのにひと手間かかります。
# 書き出しはTodoItem の配列をそのまま渡すだけでOK PS C:\> ConvertTo-Json $todo.items | Out-File $path -Encoding utf8 # 読み取った結果はPSCustomObjectの配列で帰ってくる。TodoItemに変換するのがメンドイ PS C:\> obj = Get-Content $todo.filePath -Raw -Encoding UTF8 | ConvertFrom-Json PS C:\> $todo.items = $obj | % { $todoitem = New-Object 'TodoItem' $todoitem.date = $_.date $todoitem.flag = $_.flag $todoitem.index = $_.index $todoitem.status = $_.status $todoitem.text = $_.text $todoitem }
変換部分の記述がちょっと面倒です。.Net(TodoItem)側で何とかしたいですね。
明示的型変換を実装
という事で、明示的型変換を実装してキャスト一発で変換可能にしてみます。
Add-Type -TypeDefinition @' using System; using System.Management.Automation; public enum TodoStatus { Todo, Milestone, Doing, Done } public class TodoItem { public int index { get; set; } public string text { get; set; } public DateTime date { get; set; } public bool flag { get; set; } public TodoStatus status { get; set; } public static explicit operator TodoItem(PSObject source) { return new TodoItem() { index = (int)source.index, text = (string)source.text, date = (DateTime)source.date, flag = (bool)source.flag, status = (TodoStatus)source.status } } } '@
これでOK!と思いきや、これではコンパイルが通りません。
変換元のPSObjectは、PowerShellの世界では動的に型を解決してくれますが、.Net側では「そんなメンバ定義されていませんよ」と怒られてしまいます。
インデクサでできない? index =(int)source["index"]
...これもダメでした。
.Net側でPSObjectのプロパティにアクセスするには?
.Net側ではProperties というメンバからプロパティの値を取得する必要があるようです。
source.Properties["propertyName"].Value
のように記述します。
(このPropertiesですが、PowerShell側からは見えない(参照できない)ため、その存在に気付けず苦労しました。)
Add-Type -TypeDefinition @' using System; using System.Management.Automation; public enum TodoStatus { Todo, Milestone, Doing, Done } public class TodoItem { public int index { get; set; } public string text { get; set; } public DateTime date { get; set; } public bool flag { get; set; } public TodoStatus status { get; set; } public static explicit operator TodoItem(PSObject source) { return new TodoItem() { index = (int)source.Properties["index"].Value, text = (string)source.Properties["text"].Value, date = (DateTime)source.Properties["date"].Value, flag = (bool)source.Properties["flag"].Value, status = (TodoStatus)source.Properties["status"].Value } } } '@
これで、無事コンパイルできるようになりました。キャストもちゃんと動きます。
# 読み取り処理もスッキリ! $obj = Get-Content $todo.filePath -Raw -Encoding UTF8 | ConvertFrom-Json $todo.items = $obj | % { [TodoItem]$_ } # Todoの追加時も、HashTableのキャストでOK! $todo.items += [TodoItem]@{ index = 5; text = "todo"; date = "2014/12/1"; flag = false; status = [TodoStatus]::Todo; }
PSObjectのプロパティにアクセスする方法の別解
Dynamicを使えばPSObjectのプロパティに.
でアクセス可能になります。
但し、Dynamicを使用するには"Microsoft.CSharp"を参照アセンブリに指定してあげる必要があります。
public class TodoItem { public int index { get; set; } public string text { get; set; } public DateTime date { get; set; } public bool flag { get; set; } public TodoStatus status { get; set; } public static explicit operator TodoItem(PSObject source){ dynamic d = source; return new TodoItem(){ index = (int)d.index, text = (string)d.text, date = (DateTime)d.date, flag = (bool)d.flag, status = (TodoStatus)d.status }; } } '@ -ReferencedAssemblies "Microsoft.CSharp"