Added ability to order the execution of dictionary adapter behaviors.
[castle.git] / Experiments / Castle.MonoRail.Generator / Generators / ControllerGenerator.cs
blobcf61a3f8a2a9d534a43d36cd90c2f86d1a90fbdb
1 // Copyright 2004-2007 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.MonoRail.Generator.Generators
17 using System;
18 using System.IO;
19 using System.Text;
20 using System.CodeDom;
21 using System.Collections;
22 using System.CodeDom.Compiler;
24 using Microsoft.CSharp;
25 using Microsoft.VisualBasic;
27 /// <summary>
28 ///
29 /// </summary>
30 public class ControllerGenerator : AbstractGenerator, IGenerator
32 private bool isNVelocity;
33 private bool isSmart;
34 private bool isCSharp;
35 private DirectoryInfo controllersDir;
36 private DirectoryInfo viewsDir;
37 private DirectoryInfo testsDir;
39 public ControllerGenerator()
43 #region IGenerator Members
45 public bool Accept(String name, IDictionary options, TextWriter output)
47 if ("controller".Equals(name))
49 if (options.Values.Count == 1)
51 output.WriteLine("Creates a controller and it's view skeletons");
52 output.WriteLine("");
53 output.WriteLine("name : Controller name");
54 output.WriteLine("actions : Comma separated list of action names");
55 output.WriteLine("ns : Namespace");
56 output.WriteLine("outdir : Target directory (must exists)");
57 output.WriteLine("area : [Optional] Controller area/section");
58 output.WriteLine("smart : [Optional] Extends SmartDispatcherController (defaults to false)");
59 output.WriteLine("view : [Optional] aspnet|nvelocity (defaults to nvelocity)");
60 output.WriteLine("lang : [Optional] c#|vb.net (defaults to c#)");
61 output.WriteLine("");
62 output.WriteLine("Examples:");
63 output.WriteLine("");
64 output.WriteLine(@"> generator controller name:Home actions:Index,About outdir:c:\temp");
65 output.WriteLine("");
66 output.WriteLine(@"> generator controller name:Account ns:My.Project actions:Save,Authenticate smart view:aspnet outdir:c:\temp");
67 output.WriteLine("");
68 output.WriteLine(@"> generator controller name:Home actions:Index,About smart lang:vb.net outdir:c:\temp");
69 output.WriteLine("");
70 output.WriteLine("Remarks:");
71 output.WriteLine("");
72 output.WriteLine("The outdir must point to a structure created by 'generator project'");
73 output.WriteLine("as it relies on the existance of a subdir Controllers and a subdir ");
74 output.WriteLine("Views.");
75 output.WriteLine("Also note that if you're using Windsor integration, you need to add");
76 output.WriteLine("the controller definition to your container:");
77 output.WriteLine("");
78 output.WriteLine(" container.AddComponent( 'mycontroller', typeof(HomeController) ); ");
79 output.WriteLine("");
81 return false;
83 else if (!options.Contains("actions"))
85 output.WriteLine("actions must be specified");
87 return false;
89 else if (!options.Contains("outdir"))
91 output.WriteLine("outdir must be specified");
93 return false;
95 else if (!options.Contains("name"))
97 output.WriteLine("name must be specified");
99 return false;
101 else
103 DirectoryInfo info = new DirectoryInfo(options["outdir"] as String);
105 if (!info.Exists)
107 // info.Create(); // Is it safe to use it?
108 output.WriteLine("Error: The specified outdir does not exists.");
110 return false;
114 return true;
117 return false;
120 public void Execute(IDictionary options, TextWriter output)
122 isNVelocity = !(options.Contains("view") && options["view"].Equals("aspnet"));
123 isSmart = options.Contains("smart");
124 isCSharp = !(options.Contains("lang") && options["lang"].Equals("vb.net"));
125 String name = options["name"] as String;
126 String area = options["area"] as String;
127 String[] actions = (options["actions"] as String).Split(',');
129 String ns = options["ns"] as String;
130 if (ns == null) ns = "WebApplication";
132 output.WriteLine("Creating controller source code...");
134 /// Steps to generate a controller
136 // 1. Resolve directories (controllers, view, tests)
138 output.WriteLine("Resolving directories...");
139 ResolveDirs(output, options);
141 // 2. Generate code (controllers, view, tests)
142 // 3. Write them
144 output.WriteLine("Generating source code...");
145 GenerateCodeAndWrite(name, area, actions, ns, output);
148 output.WriteLine("Done!");
151 #endregion
153 private void ResolveDirs(TextWriter output, IDictionary options)
155 DirectoryInfo outdir = new DirectoryInfo(options["outdir"] as String);
159 controllersDir = new DirectoryInfo(Path.Combine(outdir.FullName, "Controllers"));
160 viewsDir = new DirectoryInfo(Path.Combine(outdir.FullName, "views"));
161 testsDir = new DirectoryInfo(Path.Combine(outdir.FullName, "../" + outdir.Parent + ".Tests"));
163 if (!controllersDir.Exists)
165 outdir = new DirectoryInfo( Path.Combine(outdir.FullName, outdir.Name) );
167 controllersDir = new DirectoryInfo(Path.Combine(outdir.FullName, "Controllers"));
168 viewsDir = new DirectoryInfo(Path.Combine(outdir.FullName, "views"));
169 testsDir = new DirectoryInfo(Path.Combine(outdir.FullName, "../" + outdir.Parent + ".Tests"));
172 if (!controllersDir.Exists)
174 throw new ApplicationException("Could not infer directory structure from the specified 'outdir'");
177 catch(Exception ex)
179 output.WriteLine(ex.Message);
180 throw;
184 private void GenerateCodeAndWrite(string name, string area, string[] actions, String ns, TextWriter output)
186 CodeDomProvider provider = (isCSharp) ? new CSharpCodeProvider() as CodeDomProvider : new VBCodeProvider() as CodeDomProvider;
187 String fileExtension = (isCSharp) ? ".cs" : ".vb";
189 GenerateController(provider, fileExtension, ns, name, area, actions, output);
191 GenerateViews(name, actions);
193 GenerateTestCase(provider, fileExtension, ns, name, area, actions, output);
196 private string Quote(string value)
198 return String.Format("\"{0}\"", value);
201 private void GenerateController(CodeDomProvider provider, string fileExtension, string ns, string name,
202 string area, string[] actions, TextWriter output)
204 CodeNamespace thisNs = GenerateControllerCode(ns, name, area, actions);
206 FileInfo controllerFile = new FileInfo(
207 Path.Combine(controllersDir.FullName, name + fileExtension) );
209 if (!controllerFile.Exists)
211 using (StreamWriter sw = new StreamWriter(controllerFile.FullName, false, Encoding.Default))
213 CodeGeneratorOptions opts = new CodeGeneratorOptions();
214 opts.BracingStyle = "C";
215 #if DOTNET2
216 provider.GenerateCodeFromNamespace(thisNs, sw, opts);
217 #else
218 provider.CreateGenerator().GenerateCodeFromNamespace(thisNs, sw, opts);
219 #endif
222 else
224 output.WriteLine("Skipping {0} as the files exists", controllerFile.FullName);
228 private CodeNamespace GenerateControllerCode(string ns, string name, string area, string[] actions)
230 CodeNamespace thisNs = new CodeNamespace(ns);
231 thisNs.Imports.Add(new CodeNamespaceImport("System"));
232 thisNs.Imports.Add(new CodeNamespaceImport("Castle.MonoRail.Framework"));
233 // thisNs.Comments.Add(new CodeCommentStatement("Ignore the above comment or better, delete it"));
235 CodeTypeDeclaration controllerType = new CodeTypeDeclaration(name);
236 thisNs.Types.Add(controllerType);
238 if (area != null)
240 controllerType.CustomAttributes.Add(
241 new CodeAttributeDeclaration("ControllerDetails",
242 new CodeAttributeArgument(new CodeSnippetExpression(Quote(StripControllerFrom(name)))),
243 new CodeAttributeArgument("Area", new CodeSnippetExpression(Quote(area)))) );
245 else
247 controllerType.CustomAttributes.Add(
248 new CodeAttributeDeclaration("ControllerDetails",
249 new CodeAttributeArgument(new CodeSnippetExpression(Quote(StripControllerFrom(name))))) );
252 if (isSmart)
254 controllerType.BaseTypes.Add( "SmartDispatcherController" );
256 else
258 controllerType.BaseTypes.Add( "Controller" );
261 foreach(String action in actions)
263 CodeMemberMethod actionMethod = new CodeMemberMethod();
264 actionMethod.Name = action;
265 actionMethod.Attributes = MemberAttributes.Public;
267 controllerType.Members.Add(actionMethod);
270 return thisNs;
273 private void GenerateViews(String name, String[] actions)
275 DirectoryInfo viewSubDir = viewsDir.CreateSubdirectory(StripControllerFrom(name));
277 foreach(String action in actions)
279 Hashtable ctx = new Hashtable();
280 ctx.Add("controller", name);
281 ctx.Add("action", action);
282 ctx.Add("dir", viewSubDir.FullName);
284 if (isNVelocity)
286 WriteTemplateFile( Path.Combine(viewSubDir.FullName, action + ".vm"), ctx, "newview.vm" );
288 else
290 WriteTemplateFile( Path.Combine(viewSubDir.FullName, action + ".aspx"), ctx, "newviewaspx.vm" );
295 private void GenerateTestCase(CodeDomProvider provider, string extension, string ns, string name, string area,
296 string[] actions, TextWriter output)
298 CodeNamespace nsunit = GenerateControllerTestCode(ns, name, area, actions);
300 FileInfo controllerFile = new FileInfo(
301 Path.Combine(testsDir.FullName, name + "Tests" + extension) );
303 if (!controllerFile.Exists)
305 using (StreamWriter sw = new StreamWriter(controllerFile.FullName, false, Encoding.Default))
307 CodeGeneratorOptions opts = new CodeGeneratorOptions();
308 opts.BracingStyle = "C";
309 #if DOTNET2
310 provider.GenerateCodeFromNamespace(nsunit, sw, opts);
311 #else
312 provider.CreateGenerator().GenerateCodeFromNamespace(nsunit, sw, opts);
313 #endif
316 else
318 output.WriteLine("Skipping {0} as the files exists", controllerFile.FullName);
322 private CodeNamespace GenerateControllerTestCode(string ns, string name, string area, string[] actions)
324 String controllerName = StripControllerFrom(name);
326 CodeNamespace thisNs = new CodeNamespace(ns + ".Tests");
328 thisNs.Imports.Add(new CodeNamespaceImport("System"));
329 thisNs.Imports.Add(new CodeNamespaceImport("NUnit.Framework"));
330 thisNs.Imports.Add(new CodeNamespaceImport("Castle.MonoRail.TestSupport"));
332 CodeTypeDeclaration controllerType = new CodeTypeDeclaration(name + "TestCase");
333 thisNs.Types.Add(controllerType);
335 controllerType.BaseTypes.Add("AbstractMRTestCase");
336 controllerType.CustomAttributes.Add( new CodeAttributeDeclaration("TestFixture") );
338 foreach(String action in actions)
340 CodeMemberMethod actionTestMethod = new CodeMemberMethod();
341 actionTestMethod.Name = action;
342 actionTestMethod.Attributes = MemberAttributes.Public;
343 actionTestMethod.CustomAttributes.Add( new CodeAttributeDeclaration("Test") );
345 String url = null;
347 if (area == null || area.Length == 0)
349 url = String.Format("\"{0}/{1}.rails\"", controllerName, action);
351 else
353 url = String.Format("\"{0}/{1}/{2}.rails\"", area, controllerName, action);
356 String expected = string.Format("\"View for {0} action {1}\"", name, action);
358 CodeMethodInvokeExpression doGetInvoke = new CodeMethodInvokeExpression();
359 doGetInvoke.Method = new CodeMethodReferenceExpression(null, "DoGet");
360 doGetInvoke.Parameters.Add( new CodeSnippetExpression(url) );
361 actionTestMethod.Statements.Add( new CodeExpressionStatement(doGetInvoke) );
363 CodeMethodInvokeExpression assertSuccess = new CodeMethodInvokeExpression();
364 assertSuccess.Method = new CodeMethodReferenceExpression(null, "AssertSuccess");
365 actionTestMethod.Statements.Add( new CodeExpressionStatement(assertSuccess) );
367 CodeMethodInvokeExpression assertReply = new CodeMethodInvokeExpression();
368 assertReply.Method = new CodeMethodReferenceExpression(null, "AssertReplyContains");
369 assertReply.Parameters.Add( new CodeSnippetExpression(expected) );
370 actionTestMethod.Statements.Add( new CodeExpressionStatement(assertReply) );
372 controllerType.Members.Add(actionTestMethod);
375 return thisNs;
378 private string StripControllerFrom(string value)
380 if (value.EndsWith("controller"))
382 return value.Substring(0, value.IndexOf("controller"));
384 else if (value.EndsWith("Controller"))
386 return value.Substring(0, value.IndexOf("Controller"));
389 return value;