PowerShellで作るdeliciousクライアント その3

前回の続きです。

前回でブックマークの一覧を取得できるようにしましたが、あのままではURLがわかってもブラウザで開くという作業が面倒なので、その辺を簡略化できるように与えられたURLを既定のブラウザで開くコマンドレットを作っておきます。

「Commands」というフォルダを作成して、その中に「BrowseObjectCommand.vb」というファイル名でクラスファイルを追加します。

Commands\BrowseObjectCommand.vb
Namespace Commands
     <Cmdlet("Browse", "Object")> _
     Public Class BrowseObjectCommand
         Inherits PSCmdlet

         Private _url As String
         <Parameter(Mandatory:=True, Position:=0, ValueFromPipelineByPropertyName:=True)> _
         Public Property Url() As String
             Get
                 Return _url
             End Get
             Set(ByVal value As String)
                 _url = value
             End Set
         End Property

         Protected Overrides Sub ProcessRecord()
             Process.Start(Me.Url)
         End Sub
     End Class
End Namespace

PSCmdletクラスを継承します。コマンドレットである事を示す為にCmdletAttribute属性でマークしておきます。引数にはコマンドレットの動詞と名詞を指定します(Browse-Objectという書式になる)。

コマンドレットが実行されるとProcessRecordメソッドが呼び出されるので、これをオーバーライドして、その中ではURLプロパティの値をシェルで呼び出すだけです。

URLプロパティはParameterAttribute属性でマークして、必須属性(Mandatory:=True)、位置が0(Position:=0)、値をパイプラインから取得し、そのオブジェクトのプロパティと結びつけます(ValueFromPipelineByPropertyName:=True)。

このコマンドレットを使うと以下のようにして、URLをブラウザで開くことができるようになります。

PS delicious:\> dir -Count 1 | Browse-Object

話を元に戻しましょう。

ブックマークの追加・編集

ブックマークの追加・編集機能を実装します。

DeliciousクラスにAddBookmarkという名前で引数にDeliciousBookmark型を受け取るメソッドを定義します。

Delicious.vb

Sub AddBookmark(ByVal bookmark As DeliciousBookmark, _
                 Optional ByVal share As Boolean = False, Optional ByVal replace As Boolean = False)
     Dim address = "https://api.del.icio.us/v1/posts/add"
     Dim queries = New Dictionary(Of String, Object)()
     If String.IsNullOrEmpty(bookmark.Url) Then Throw New ArgumentException("[Url]を指定して下さい。")
     If String.IsNullOrEmpty(bookmark.Title) Then Throw New ArgumentException("[Title]を指定して下さい。")

     queries.Add("url", bookmark.Url)
     queries.Add("description", bookmark.Title)
     ' optional
     If Not String.IsNullOrEmpty(bookmark.Comment) Then queries.Add("extended", bookmark.Comment)
     If Not bookmark.Tags Is Nothing AndAlso bookmark.Tags.Length > 0 Then
         queries.Add("tags", String.Join(" ", bookmark.Tags))
     End If
     If Not bookmark.Date Is Nothing Then
         queries.Add("dt", bookmark.Date.Value.ToString("yyyy-MM-ddTHH:mm:ssZ"))
     End If
     queries.Add("shared", IIf(share, "yes", "no"))
     queries.Add("replace", IIf(replace, "yes", "no"))

     Dim wc = New Net.WebClient()
     wc.Credentials = _credential
     wc.Encoding = Text.Encoding.UTF8

     Dim contents = wc.DownloadString(JoinQueries(address, queries))
     Dim result = Text.RegularExpressions.Regex.Match( _
         contents, "<result code=""(?<msg>.*?)"" />", Text.RegularExpressions.RegexOptions.Multiline _
     )
     If result.Success Then
         Dim msg = result.Groups("msg").Value
         ' replaceを指定してね!
         If msg <> "done" Then Throw New ArgumentException(msg)
     End If
End Sub

Private Shared Function JoinQueries(ByVal address As String, ByVal queries As IDictionary(Of String, Object))
     If queries.Count = 0 Then Return address

     Return address + "?" + String.Join( _
         "&", queries.Select( _
             Function(q) String.Format("{0}={1}", q.Key, Web.HttpUtility.UrlEncode(q.Value)) _
         ).ToArray() _
     )
End Function

特に説明する必要は無いでしょう。単に「https://api.del.icio.us/v1/posts/add」というURLにリクエストを送っているだけです。

あと、JoinQueriesメソッドのq.Valueを取得する部分をHttpUtilityクラスを使ってURLエンコードしておきました(System.Webアセンブリへの参照が必要です)。

使うとしたら、以下のようになるでしょうか。

Program.vb
Sub Main()
     Dim obj = New Delicious(New Net.NetworkCredential("ユーザ名", "パスワード"))
     Dim bookmark = New DeliciousBookmark With { _
         .Title = "google", _
         .Url = "http://www.google.co.jp" _
     }
     ' 共有して、上書きする。
     obj.AddBookmark(bookmark, share:=True, replace:=True)
End Sub

では、このメソッドをプロバイダークラスから呼び出すように変更しましょう。

このメソッドを呼び出すには山ほどパラメータが必要になるので 、(インナークラスで)パラメータクラスを作っておきます。

Delicious.vb

Private Class NewItemParameters
     Private _url As String
     <Parameter(Mandatory:=True, Position:=0)> _
     Public Property Url() As String
         Get
             Return _url
         End Get
         Set(ByVal value As String)
             _url = value
         End Set
     End Property

     Private _tags As String()
     <Parameter(Position:=1)> _
     Public Property Tags() As String()
         Get
             Return _tags
         End Get
         Set(ByVal value As String())
             _tags = value
         End Set
     End Property

     Private _comment As String
     <Parameter(Position:=2)> _
     Public Property Comment() As String
         Get
             Return _comment
         End Get
         Set(ByVal value As String)
             _comment = value
         End Set
     End Property

     Private _date As DateTime?
     <Parameter(Position:=3)> _
     Public Property [Date]() As DateTime?
         Get
             Return _date
         End Get
         Set(ByVal value As DateTime?)
             _date = value
         End Set
     End Property

     Private _share As SwitchParameter
     <Parameter()> _
     Public Property Share() As SwitchParameter
         Get
             Return _share
         End Get
         Set(ByVal value As SwitchParameter)
             _share = value
         End Set
     End Property
End Class

URLTagsCommentDateShareというプロパティを定義しています。それぞれParameterAttribute属性でマークして、必須属性にはMandatory:=TruePostion:=nで順番を指定しています。

後はNewItemNew-Itemコマンドレットに対応)メソッドをオーバーライドして、その中でAddBookmarkメソッドを呼び出すだけです。
NewItemDynamicParametersメソッドをオーバーライドして、NewItemParametersクラスのインスタンスを返しておく事をお忘れなく。

Delicious.vb

Protected Overrides Sub NewItem(ByVal path As String, ByVal itemTypeName As String, ByVal newItemValue As Object)
     Dim dynaParams = CType(Me.DynamicParameters, NewItemParameters)

     Dim bookmark = New DeliciousBookmark With { _
         .Title = path, _
         .Url = dynaParams.Url, _
         .Tags = dynaParams.Tags, _
         .Comment = dynaParams.Comment, _
         .Date = dynaParams.Date _
     }
     Me.Delicious.AddBookmark( _
         bookmark, replace:=Me.Force, share:=dynaParams.Share.IsPresent _
     )
     WriteItemObject(bookmark, path, False)
End Sub

Protected Overrides Function NewItemDynamicParameters(ByVal path As String, ByVal itemTypeName As String, ByVal newItemValue As Object) As Object
     Return New NewItemParameters()
End Function

追加に成功すれば(例外が投げられなければ)、DeliciousBookmarkオブジェクトをパイプラインに出力しておきます。

これで、以下のようにしてブックマークを追加する事ができるようになります。

PS delicious:\> ni google http://www.google.co.jp -Tags google -Comment テストです。


タイトル : google
URL      : http://www.google.co.jp
コメント : テストです。
タグ     : {google}
更新日付 :


PS delicious:\> dir -Count 1


タイトル : google
URL      : http://www.google.co.jp/
コメント : テストです。
タグ     : {google}
更新日付 : 2009/03/12 4:30:46

次はブックマークを編集できるようにしておきます。

まずはSet-Itemコマンドレットのパラメータクラスをインナークラスで定義しておきます。

Delicious.vb
Private Class SetItemParameters
     Private _bookmark As DeliciousBookmark
     <Parameter(Mandatory:=True, Position:=0, ValueFromPipeline:=True)> _
     Public Property Bookmark() As DeliciousBookmark
         Get
             Return _bookmark
         End Get
         Set(ByVal value As DeliciousBookmark)
             _bookmark = value
         End Set
     End Property

     Private _share As SwitchParameter
     <Parameter(Position:=1)> _
     Public Property Share() As SwitchParameter
         Get
             Return _share
         End Get
         Set(ByVal value As SwitchParameter)
             _share = value
         End Set
     End Property
End Class

BookmarkShareというプロパティを定義しています。BookmarkプロパティはValueFromPipeline:=Trueにする事でパイプラインから取得できるようにしておきます。

後はSetItemメソッド(Set-Itemコマンドレットに対応)をオーバーライドして、その中でAddBookmarkメソッドをreplace:=Trueで呼び出しています。

Delicious.vb

Protected Overrides Sub SetItem(ByVal path As String, ByVal value As Object)
     Dim dynaParams = CType(Me.DynamicParameters, SetItemParameters)

     Me.Delicious.AddBookmark( _
         dynaParams.Bookmark, replace:=True, share:=dynaParams.Share.IsPresent _
     )
End Sub

Protected Overrides Function SetItemDynamicParameters(ByVal path As String, ByVal value As Object) As Object
     Return New SetItemParameters()
End Function

あと、Deliciousプロパティに一部不具合があったので、以下のように修正しておいて下さい。

Delicious.vb
Private _delicious As Delicious
Public ReadOnly Property Delicious() As Delicious
     Get
         If _delicious Is Nothing Then
             ' Me.PSDriveInfoプロパティでは駄目!!
             Dim driveInfo = Me.SessionState.Drive.Current

             _delicious = New Delicious(driveInfo.Credential.GetNetworkCredential())
         End If
         Return _delicious
     End Get
End Property

これで、以下のようにしてブックマークの情報を編集する事ができるようになります。

PS delicious:\> $bookmark = dir -Count 1
PS delicious:\> $bookmark.Title = "google.co.jp"
PS delicious:\> $bookmark | si

更新系のコマンドレットには、他にもRename-ItemMove-Itemがありますが、これらには対応しない事にします。

次はブックマークの削除機能を実装します。

次回に続く