1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
.Views
.IronView
18 using System
.Collections
;
19 using System
.Collections
.Generic
;
21 using System
.Runtime
.Serialization
;
24 using Castle
.MonoRail
.Framework
;
25 using Castle
.MonoRail
.Views
.IronView
.ElementProcessor
;
26 using IronPython
.Hosting
;
31 public class IronPythonViewEngine
: ViewEngineBase
, IInitializable
, ITemplateEngine
33 public const String TemplateExtension
= ".pml";
35 private PythonEngine engine
;
36 private EngineModule globalModule
;
37 private IronPythonTemplateParser parser
;
38 private Dictionary
<string, ViewInfo
> name2View
;
41 /// Initializes a new instance of the <see cref="IronPythonViewEngine"/> class.
43 public IronPythonViewEngine()
45 name2View
= new Dictionary
<string, ViewInfo
>();
48 #region IInitializable
50 public void Initialize()
52 EngineOptions options
= new EngineOptions();
54 // options.ClrDebuggingEnabled = true;
55 // options.ExceptionDetail = true;
56 // options.ShowClrExceptions = true;
57 // options.SkipFirstLine = false;
59 engine
= new PythonEngine(options
);
60 engine
.Import("site");
61 System
.IO
.FileStream fs
= new System
.IO
.FileStream("scripting-log.txt", System
.IO
.FileMode
.Create
);
62 engine
.SetStandardOutput(fs
);
63 engine
.SetStandardError(fs
);
65 globalModule
= engine
.CreateModule("mr", false);
67 // TODO: processors should be configured
68 parser
= new IronPythonTemplateParser(new IElementProcessor
[]
77 new ViewComponentTag(),
83 #region ViewEngineBase overrides
85 public override bool SupportsJSGeneration
90 public override string ViewFileExtension
92 get { return TemplateExtension; }
95 public override string JSGeneratorFileExtension
100 public override bool HasTemplate(string templateName
)
102 return ViewSourceLoader
.HasTemplate(ResolveTemplateName(templateName
));
105 public override void Process(IRailsEngineContext context
, Controller controller
, string templateName
)
107 AdjustContentType(context
);
109 bool hasLayout
= controller
.LayoutName
!= null;
115 // Because we are rendering within a layout we need to catch it first
116 writer
= new StringWriter();
120 // No layout so render direct to the output
121 writer
= context
.Response
.Output
;
124 Dictionary
<string, object> locals
= new Dictionary
<string, object>();
125 PopulateLocals(writer
, context
, controller
, locals
);
127 String fileName
= ResolveTemplateName(templateName
);
128 ProcessViewTemplate(fileName
, context
, locals
);
132 String contents
= (writer
as StringWriter
).GetStringBuilder().ToString();
133 ProcessLayout(contents
, controller
, context
, locals
);
137 public override void Process(TextWriter output
, IRailsEngineContext context
, Controller controller
,
140 throw new NotImplementedException();
143 public override void ProcessPartial(TextWriter output
, IRailsEngineContext context
, Controller controller
,
146 throw new NotImplementedException();
149 public override void GenerateJS(TextWriter output
, IRailsEngineContext context
, Controller controller
,
152 throw new NotImplementedException();
156 /// Need to test this
158 /// <param name="context"></param>
159 /// <param name="controller"></param>
160 /// <param name="contents"></param>
161 public override void ProcessContents(IRailsEngineContext context
, Controller controller
, string contents
)
163 // Dictionary<string, object> locals = new Dictionary<string, object>();
165 // PopulateLocals(context.Response.Output, context, controller, locals);
167 // String script = parser.CreateScriptBlock(
168 // new StringReader(contents), "static content", serviceProvider, this);
170 // compiledCode = engine.Compile(script);
172 // ExecuteScript(compiledCode, context, locals);
177 public CompiledCode
CompilePartialTemplate(IViewSource source
, string partialViewName
,
178 out string functionName
, params String
[] parameters
)
180 partialViewName
= partialViewName
.Replace('/', '_').Replace('\\', '_').Replace('.', '_').ToLowerInvariant();
181 functionName
= "render" + partialViewName
;
185 CompiledCode compiledCode
= null;
187 if (name2View
.TryGetValue(partialViewName
.ToLower(), out viewInfo
))
189 compileView
= viewInfo
.LastModified
!= source
.LastModified
;
190 compiledCode
= viewInfo
.CompiledCode
;
197 Stream viewSourceStream
= source
.OpenViewStream();
201 using(StreamReader reader
= new StreamReader(viewSourceStream
))
203 String script
= parser
.CreateFunctionScriptBlock(reader
, partialViewName
,
204 functionName
, serviceProvider
, this, parameters
);
206 // compiledCode = engine.Compile("def something(out):\n\tout.Write('hellloo')\n");
208 compiledCode
= engine
.Compile(script
);
210 // Evaluates the function
211 compiledCode
.Execute(globalModule
);
212 // compiledCode.Execute();
214 viewInfo
= new ViewInfo();
215 viewInfo
.LastModified
= source
.LastModified
;
216 viewInfo
.CompiledCode
= compiledCode
;
218 viewInfo
.Script
= script
;
221 name2View
[partialViewName
.ToLower()] = viewInfo
;
225 HttpContext
.Current
.Response
.Output
.Write("<!-- Partial script\r\n");
226 HttpContext
.Current
.Response
.Output
.Write(viewInfo
.Script
);
227 HttpContext
.Current
.Response
.Output
.Write("\r\n-->");
228 HttpContext
.Current
.Response
.Output
.Flush();
235 protected string ResolveTemplateName(string area
, string templateName
)
237 return String
.Format("{0}{1}{2}", area
,
238 Path
.DirectorySeparatorChar
, ResolveTemplateName(templateName
));
241 private void ProcessLayout(string innerViewContents
, Controller controller
,
242 IRailsEngineContext context
, Dictionary
<string, object> locals
)
244 String layout
= ResolveTemplateName("layouts", controller
.LayoutName
);
246 locals
["childContent"] = innerViewContents
;
247 locals
["output"] = context
.Response
.Output
;
249 ProcessViewTemplate(layout
, context
, locals
);
252 private void ProcessViewTemplate(String templateFile
, IRailsEngineContext context
,
253 Dictionary
<string, object> locals
)
255 IViewSource source
= ViewSourceLoader
.GetViewSource(templateFile
);
259 throw new RailsException("Could not find view template: " + templateFile
);
262 CompiledCode compiledCode
= CompileTemplate(source
, templateFile
);
264 ExecuteScript(compiledCode
, context
, locals
);
267 private CompiledCode
CompileTemplate(IViewSource source
, string templateFile
)
271 CompiledCode compiledCode
= null;
273 if (name2View
.TryGetValue(templateFile
.ToLower(), out viewInfo
))
275 compileView
= viewInfo
.LastModified
!= source
.LastModified
;
276 compiledCode
= viewInfo
.CompiledCode
;
283 Stream viewSourceStream
= source
.OpenViewStream();
287 using(StreamReader reader
= new StreamReader(viewSourceStream
))
289 String script
= parser
.CreateScriptBlock(reader
, templateFile
, serviceProvider
, this);
291 compiledCode
= engine
.Compile(script
);
293 viewInfo
= new ViewInfo();
294 viewInfo
.LastModified
= source
.LastModified
;
295 viewInfo
.CompiledCode
= compiledCode
;
297 viewInfo
.Script
= script
;
300 name2View
[templateFile
.ToLower()] = viewInfo
;
304 HttpContext
.Current
.Response
.Output
.Write("<!-- Template script \r\n");
305 HttpContext
.Current
.Response
.Output
.Write(viewInfo
.Script
);
306 HttpContext
.Current
.Response
.Output
.Write("\r\n-->");
307 HttpContext
.Current
.Response
.Output
.Flush();
314 private void ExecuteScript(CompiledCode script
,
315 IRailsEngineContext context
,
316 Dictionary
<string, object> locals
)
318 // NullLocalDecorator decorator = new NullLocalDecorator(locals);
322 script
.Execute(globalModule
, locals
);
326 context
.Response
.Write("<p>");
327 context
.Response
.Write(ex
.Message
);
328 context
.Response
.Write("</p>");
329 context
.Response
.Write(ex
.InnerException
);
330 context
.Response
.Write("<pre>");
331 context
.Response
.Write(ex
.StackTrace
);
332 context
.Response
.Write("</pre>");
336 private static void PopulateLocals(TextWriter output
, IRailsEngineContext context
,
337 Controller controller
,
338 Dictionary
<string, object> locals
)
340 locals
["controller"] = controller
;
341 locals
["context"] = context
;
342 locals
["request"] = context
.Request
;
343 locals
["response"] = context
.Response
;
344 locals
["session"] = context
.Session
;
345 locals
["output"] = output
;
347 if (controller
.Resources
!= null)
349 foreach(String key
in controller
.Resources
.Keys
)
351 locals
[key
] = controller
.Resources
[key
];
355 foreach(object key
in controller
.Helpers
.Keys
)
357 locals
[key
.ToString()] = controller
.Helpers
[key
];
360 foreach(DictionaryEntry entry
in controller
.PropertyBag
)
362 locals
[entry
.Key
.ToString()] = entry
.Value
;
365 foreach(String key
in context
.Params
.AllKeys
)
367 if (key
== null) continue; // Nasty bug?
368 object value = context
.Params
[key
];
369 if (value == null) continue;
373 locals
[Flash
.FlashKey
] = context
.Flash
;
375 foreach(DictionaryEntry entry
in context
.Flash
)
377 if (entry
.Value
== null) continue;
378 locals
[entry
.Key
.ToString()] = entry
.Value
;
381 locals
["siteroot"] = context
.ApplicationPath
;
385 internal class ViewInfo
387 public long LastModified
;
388 public CompiledCode CompiledCode
;
389 public String Script
;
393 /// This class prevents a request to a
394 /// undefined local variable.
396 internal class NullLocalDecorator
: IDictionary
<string, object>
398 private readonly Dictionary
<string, object> locals
;
400 public NullLocalDecorator(Dictionary
<string, object> locals
)
402 this.locals
= locals
;
405 #region IDictionary<string, object>
407 public void Add(string key
, object value)
409 locals
.Add(key
, value);
417 public bool ContainsKey(string key
)
419 return locals
.ContainsKey(key
);
422 public bool ContainsValue(object value)
424 return locals
.ContainsValue(value);
427 public void GetObjectData(SerializationInfo info
, StreamingContext context
)
429 locals
.GetObjectData(info
, context
);
432 public void OnDeserialization(object sender
)
434 locals
.OnDeserialization(sender
);
437 public bool Remove(string key
)
439 return locals
.Remove(key
);
442 public bool TryGetValue(string key
, out object value)
444 if (!locals
.ContainsKey(key
))
450 return locals
.TryGetValue(key
, out value);
453 public IEqualityComparer
<string> Comparer
455 get { return locals.Comparer; }
460 get { return locals.Count; }
463 public object this[string key
]
465 get { return locals[key]; }
466 set { locals[key] = value; }
469 public ICollection
<string> Keys
471 get { return locals.Keys; }
474 public ICollection
<object> Values
476 get { return locals.Values; }
483 public void Add(KeyValuePair
<string, object> item
)
485 throw new NotImplementedException();
488 public bool Contains(KeyValuePair
<string, object> item
)
490 throw new NotImplementedException();
493 public void CopyTo(KeyValuePair
<string, object>[] array
, int arrayIndex
)
495 throw new NotImplementedException();
498 public bool Remove(KeyValuePair
<string, object> item
)
500 throw new NotImplementedException();
503 public bool IsReadOnly
508 IEnumerator
<KeyValuePair
<string, object>> IEnumerable
<KeyValuePair
<string, object>>.GetEnumerator()
510 return locals
.GetEnumerator();
513 public IEnumerator
GetEnumerator()
515 return locals
.GetEnumerator();