sempare / sempare-delphi-template-engine

Sempare Template (scripting) Engine for Delphi allows for flexible dynamic text generation. It can be used for generating email, html, reports, source code, xml, configuration, etc.
Apache License 2.0
144 stars 18 forks source link

Suggestion - Output exception message to the error code in-place #59

Closed edwinyzh closed 1 year ago

edwinyzh commented 3 years ago

Let's say a template is consists of tens or hundreds of lines. Assume most of the template code are OK, but one of them has syntax error : <% errorCodeHere %>

It'll be great if the compilation goes on and insert the Exception ClassName and Error Message in-place into somewhere in between <% errorCodeHere %> in the result string.

Hope it makes sense.

darnocian commented 3 years ago

I think it depends on how you are outputting stuff. Ideally, there should not be any issue. If you are following the model of passing data into the template rather than using functions to fetch data - there should be no issue. if there is an exception during the data fetching phase, well, you know about it and can then use the error handling template however.

if you want to do something like you suggest, I think you would have to have something like: <% errorCode := getErrorCode(); %> <% errorCode %>

if there is no errorCode, it can be blank...

you probably know this already, but fundamentally, the template engine is not trying to be something like php... otherwise it would have all of the features and be a general purpose programming language. If data and computation is done before template processing takes place, there is less of a dependency on the template engine helper functions to have to have concerns about threading and access to shared sate, etc... as it can just query the data structure it has been presented with...

darnocian commented 3 years ago

actually, I was reviewing what you said again about nested templates...

The template engine uses 'stack frames' for storing variables. I can't recall offhand, but it may be that when going across template boundaries, a new stack frame is created. However, the way variable resolution works is by traversing the various stack frames until the relevant variable is found. if not found, it is added to the current stack frame.

with this in mind, what I'm thinking you could try... in the parent template, create a variable <% errorCode := '' %>

then in the child template, if you assign <% errorCode := 'some value' %>, it should be visible in the parent template... I can't say 100% for sure, but it might work.

edwinyzh commented 3 years ago

Sorry for not being clear enough for the suggestion. I hope the following example explains the issue better. Assume we have a html template file:

<html>
...many lines here
// the end user writes the template code and have written it wrongly:
<% CallCustomFunction(Anything might go wrong here for non-techies) %>
...many lines here
</html>

With the current implementation, Template.Eval will raise exception and the template processing will be halted when if there is a parsing error, so the Eval result will be zero or full.

My suggestion is not to raise exception (thus stopping the processing) but insert the error message in where the error happened. Take the above as an example, the processed output should be something like:

<html>
...many lines here
// the end user writes the template code and have written it wrongly:
<% Template error here, error code: 1, error message: 'blablabla', original code: 'CallCustomFunction(...)' %>
...many lines here
</html>
darnocian commented 3 years ago

I think I see what you are getting it...

It is more about recovery during the parsing process.

I think the demo app demonstrates how to get error line and position, but does not render anything.

If you have a server app, it may be more ideal to parse the templates on startup, and then 'eval' the parsed template ... which should be a little faster. this might not be the case for you right now though, but just mentioning... if there is a problem, it is an application issue and should just be fixed in my opinion. ;) but if I recall, in your scenario you are working on an editor, so recovery during preview may be nicer...

Recovery and injection of an error message may be a good extension. thank you for the suggestion.

darnocian commented 3 years ago

Recovery can be a tricky problem, but I'm just thinking about how it can be done...

So I think I'd add an option to support recovery, as it might not always be automatically desirable.

As an error essentially takes place within the <% %> type script tags, the recovery process would log the error (there may be some configuration as to a prefix/suffix around the error - e.g. so it fits into html, etc) and ignores everything until it finds the next closing %> type tag at which point it just, where it switches state back into text mode and resumes normal parsing.

darnocian commented 1 year ago

There is a new option on the context - eoDebug. When this is enabled, you can also leverage the new property on the context: DebugMessageFormat.

If you want to use html output, simply do something like:

DebugMessageFormat := '<b>Error:</b><i>%s</i>';
darnocian commented 1 year ago
procedure TTestTemplate.TestException;
var
  LContext: ITemplateContext;
  Functions: ITemplateFunctions;
begin
  Functions := CreateTemplateFunctions;
  Functions.RegisterDefaults;
  Functions.AddFunctions(TMyExceptProc);
  LContext := Template.Context([eoEmbedException]);
  LContext.Functions := Functions;
  Assert.AreEqual(#$D#$A#$D#$A'ERROR:  (Line 1, Column 16) test'#$D#$A#$D#$A, Template.Eval(LContext, '<% RaiseExcept(''test'') %>'));
  LContext.DebugErrorFormat := '<b>Error:</b><i>%s</i>';
  Assert.AreEqual('<b>Error:</b><i> (Line 1, Column 16) test2</i>', Template.Eval(LContext, '<% RaiseExcept(''test2'') %>'));
end;