Trac のチケット関係の api は結構あるのですが、ひとまず
- ticket.getActions: アクションを設定する(解決済み、担当者変更など)
- ticket.get: 指定ID のチケットを取得する
- ticket.create: 新しいチケットを作成する
- ticket.update: 既存のチケットを変更する、コメントを付ける
- ticket.delete: 指定ID のチケットを削除する
なところがあれば、大丈夫かなと。細かなところはブラウザ上で操作すればよいので、完全にエミュレートするのは後ほど機会があれば作っていきます。
以下が、TracTools クラス。チケット自体は、object[] になり、そのままでは扱いづらいので Ticket クラスを作っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | namespace moonmile.trac { public class TracTools { public _Setting Setting { get ; set ; } public class _Setting { public string UserName { get ; set ; } public string Password { get ; set ; } public string Url { get ; set ; } } public TracTools() { this .Setting = new _Setting(); } /// <summary> /// プロキシを作成する /// </summary> /// <returns></returns> private ITrac CreateProxy() { ITrac proxy = CookComputing.XmlRpc.XmlRpcProxyGen.Create<ITrac>(); proxy.Url = this .Setting.Url; proxy.UserAgent = "trac-tools" ; proxy.Credentials = new NetworkCredential( Setting.UserName, Setting.Password); return proxy; } /// <summary> /// api をリストアップ /// </summary> /// <returns></returns> public List< string > ListMethods() { //プロキシクラスのインスタンスを作成 ITrac proxy = CreateProxy(); string [] res = proxy.SystemListMethods(); List< string > lst = res.ToList< string >(); return lst; } public string MethodHelp( string name) { //プロキシクラスのインスタンスを作成 ITrac proxy = CreateProxy(); return proxy.SystemMethodHelp(name); } /// <summary> /// アクションをリストアップ /// </summary> /// <param name="id"></param> /// <returns></returns> public object [] TicketGetActions( int id) { ITrac proxy = CreateProxy(); object [] res = proxy.TicketGetActions(id); foreach ( object [] act in res) { Console.WriteLine( "{0}:{1}:{2}" , act[0], act[1], act[2]); } return res; } /// <summary> /// 指定IDのチケットを取得 /// </summary> /// <param name="id"></param> /// <returns></returns> public Ticket TicketGet( int id) { //プロキシクラスのインスタンスを作成 ITrac proxy = CreateProxy(); object []res = proxy.TicketGet(id); return new Ticket(res); } /// <summary> /// チケットを新規作成 /// </summary> /// <param name="ti"></param> /// <returns></returns> public int TicketCreate(Ticket ti) { ITrac proxy = CreateProxy(); int id = proxy.TicketCreate(ti.Summary, ti.Description, ti.Attributes()); return id; } /// <summary> /// チケットにコメントを追加 /// </summary> /// <param name="ti"></param> /// <returns></returns> public Ticket TicketUpdate(Ticket ti) { ITrac proxy = CreateProxy(); object [] res = proxy.TicketUpdate(ti.ID, ti.Comment, ti.Attributes()); return new Ticket(res); } /// <summary> /// チケットのアクションを更新 /// </summary> /// <param name="ti"></param> /// <returns></returns> public Ticket TicketUpdate(Ticket ti, string act) { ITrac proxy = CreateProxy(); XmlRpcStruct st = ti.Attributes(); st.Add( "action" , act); object [] res = proxy.TicketUpdate(ti.ID, ti.Comment, st); return new Ticket(res); } /// <summary> /// チケットを削除 /// </summary> /// <param name="ti"></param> /// <returns></returns> public int TicketDelete( int id) { ITrac proxy = CreateProxy(); int res = proxy.TicketDelete(id); return res; } } public interface ITrac : IXmlRpcProxy { [XmlRpcMethod( "ticket.get" )] object [] TicketGet( int id); [XmlRpcMethod( "ticket.create" )] int TicketCreate( string summary, string desc, XmlRpcStruct attrs); [XmlRpcMethod( "ticket.update" )] object [] TicketUpdate( int id, string comment, XmlRpcStruct attrs); [XmlRpcMethod( "ticket.delete" )] int TicketDelete( int id); [XmlRpcMethod( "ticket.getActions" )] object [] TicketGetActions( int id); } /// <summary> /// チケットの大まかなクラス /// </summary> public class Ticket { public int ID; public DateTime CreateDateTime; public DateTime ChangeDateTime; public string Summary = "" ; public string Reporter = "" ; public string Owner = "" ; public string Description = "" ; public string TicketType = "" ; public string Priority = "" ; public string Comment = "" ; public object [] result = null ; public Ticket() { } public Ticket( object [] ary) { this .FromArray(ary); } public Ticket FromArray( object [] ary) { Ticket ti = this ; ti.ID = ( int )ary[0]; ti.result = ary; foreach ( object o in ary) { XmlRpcStruct st = o as XmlRpcStruct; if (st != null ) { foreach ( string key in st.Keys) { object obj = st[key]; Console.WriteLine( "{0}:{1}:{2}" , key, obj.GetType(), obj); switch (key) { case "owner" : ti.Owner = ( string )obj; break ; case "reporter" : ti.Reporter = ( string )obj; break ; case "summary" : ti.Summary = ( string )obj; break ; case "description" : ti.Description = ( string )obj; break ; case "time" : ti.CreateDateTime = (DateTime)obj; break ; case "changetime" : ti.ChangeDateTime = (DateTime)obj; break ; case "type" : ti.TicketType = ( string )obj; break ; case "priority" : ti.Priority = ( string )obj; break ; } } } } return ti; } public XmlRpcStruct Attributes() { XmlRpcStruct st = new XmlRpcStruct(); st.Add( "owner" , this .Owner ); st.Add( "reporter" , this .Reporter); st.Add( "summary" , this .Summary); st.Add( "description" , this .Description); st.Add( "type" , this .TicketType); st.Add( "priority" , this .Priority); return st; } } } |
これを使ったコマンドラインが以下のコード。ユーザー名やらパスワードが直書きですが、これは後で、wordpress の時と同じように config ファイルから取得する予定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | namespace TracPost { class Program { static void Main( string [] args) { TracTools trac = new TracTools(); trac.Setting.Url = "http://localhost:8000/trac/gokui-ios5/login/rpc" ; trac.Setting.UserName = "masuda" ; trac.Setting.Password = "masuda" ; if (args.Length == 0) { Usage(); return ; } else { switch (args[0]) { case "apilist" : List< string > lst = trac.ListMethods(); foreach ( string name in lst) { Console.WriteLine(name); } break ; case "help" : if (args.Length == 2) { string txt = trac.MethodHelp(args[1]); Console.WriteLine(txt); } break ; case "actions" : if ( args.Length == 2 ) { trac.TicketGetActions( int .Parse(args[1])); } break ; case "get" : if (args.Length == 2) { Ticket ti = trac.TicketGet( int .Parse(args[1])); Console.WriteLine(ti.Summary); Console.WriteLine(ti.Description); } break ; case "post" : if (args.Length == 3) { Ticket ti = new Ticket(); ti.Summary = args[1]; // "投稿" + DateTime.Now.ToString(); ti.Description = args[2]; // "投稿テスト\nat" + DateTime.Now.ToString(); ti.Reporter = "masuda" ; ti.Owner = "someone" ; int id = trac.TicketCreate(ti); Console.WriteLine( "ID:{0}" , id ); } break ; case "update" : if (args.Length == 3) { int id = int .Parse(args[1]); Ticket ti = trac.TicketGet(id); ti.Comment = args[2]; ti = trac.TicketUpdate(ti); } break ; case "action" : if (args.Length == 3) { int id = int .Parse(args[1]); string act = args[2]; Ticket ti = trac.TicketGet(id); ti = trac.TicketUpdate(ti, act); } break ; case "delete" : if (args.Length == 2) { int id = int .Parse(args[1]); int res = trac.TicketDelete(id); Console.WriteLine( "result:{0}" , res); } break ; } } } public static void Usage() { Console.WriteLine( "tracpost list : api list を表示" ); Console.WriteLine( "tracpost help [command]: コマンドのヘルプを表示" ); Console.WriteLine( "tracpost get [id]: 指定IDのチケットを取得" ); Console.WriteLine( "tracpost post [summary] [description]: チケットを登録" ); Console.WriteLine( "tracpost delete [id]: 指定IDのチケットを削除" ); Console.WriteLine( "tracpost update [id] [comment]: コメントを追加" ); Console.WriteLine( "tracpost action [id] [resolve]: アクションを変更" ); } } } |
さて、このコーディングの仕方ですが、順序として「クラス」→「main 関数」の順で作っているわけではなく、「main 関数」で呼び出し側を作った後に、TicketTools クラスをそれに合わせています。
オブジェクト指向設計を思って身構えてしまうと、ついつい複雑なメソッドや余分なプロパティを作ってしまいがちですが、main 関数で、どのようにクラスを利用したいかを書いてから、クラスを実装すると、これらの無駄が省けます。ある意味で、テスト駆動に近いのですが、テスト自体にはあまり重きを置いていなくて、クラスのインターフェース(User Interface Develop Driven とか)を先に決めてどのように使うのかを主導させています。
例えば、新しいチケットをポストする場合は、
1 2 3 4 5 6 | Ticket ti = new Ticket(); ti.Summary = args[1]; ti.Description = args[2]; ti.Reporter = "masuda" ; ti.Owner = "someone" ; int id = trac.TicketCreate(ti); |
のように Ticket オブジェクトを作った後に、適当にプロパティを設定しています。これは、以下のようにも書くことも可能です。
1 2 3 | string summary = args[1]; string desc = args[2]; int id = trac.TicketCreate(summary, desc, "masuda" , "someone" ); |
この場合、TicketCreate メソッドの引数に各パラメータを指定するのですが、フィールドやら何やらが多くなってしまたときには破綻しそうですね。また、チケットの update を考えると、チケットの内容はTicket クラスを使うユーザーとしてなんらかの形でまとめておきたいところです。
で、
1 2 3 4 5 6 7 | public class Ticket { public string summary; public string desc; public string repoter ; public string owner ; } |
のようにユーザー側でクラスを指定して、TicketCreate メソッドをプロパティを指定して呼び出してもいいのですが、これっていちいちユーザー側で指定するのは面倒な話です。Ticket クラスのプロパティ名などを変えたいときも困るし。
という訳で、ユーザーの気持ちになって、ユーザー側のほうからコーディングをすると、先の「trac.TicketCreate(ti)」で呼び出したほうが楽であろう、というのが、このコーディングスタイルのスタートだったりします。