-
-
Notifications
You must be signed in to change notification settings - Fork 362
/
Copy pathIncludeFunction.cs
162 lines (138 loc) · 6.02 KB
/
IncludeFunction.cs
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
// Copyright (c) Alexandre Mutel. All rights reserved.
// Licensed under the BSD-Clause 2 license.
// See license.txt file in the project root for full license information.
#nullable disable
using System;
using System.Collections.Generic;
using Scriban.Parsing;
using Scriban.Runtime;
using Scriban.Syntax;
namespace Scriban.Functions
{
/// <summary>
/// The include function available through the function 'include' in scriban.
/// </summary>
#if SCRIBAN_PUBLIC
public
#else
internal
#endif
sealed partial class IncludeFunction : IScriptCustomFunction
{
public IncludeFunction()
{
}
public object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
{
if (arguments.Count == 0)
{
throw new ScriptRuntimeException(callerContext.Span, "Expecting at least the name of the template to include for the <include> function");
}
var templateName = context.ObjectToString(arguments[0]);
// If template name is empty, throw an exception
if (string.IsNullOrEmpty(templateName))
{
// In a liquid template context, we let an include to continue without failing
if (context is LiquidTemplateContext)
{
return null;
}
throw new ScriptRuntimeException(callerContext.Span, $"Include template name cannot be null or empty");
}
var templateLoader = context.TemplateLoader;
if (templateLoader == null)
{
throw new ScriptRuntimeException(callerContext.Span, $"Unable to include <{templateName}>. No TemplateLoader registered in TemplateContext.TemplateLoader");
}
string templatePath;
try
{
templatePath = templateLoader.GetPath(context, callerContext.Span, templateName);
}
catch (Exception ex) when (!(ex is ScriptRuntimeException))
{
throw new ScriptRuntimeException(callerContext.Span, $"Unexpected exception while getting the path for the include name `{templateName}`", ex);
}
// If template path is empty (probably because template doesn't exist), throw an exception
if (templatePath == null)
{
throw new ScriptRuntimeException(callerContext.Span, $"Include template path is null for `{templateName}");
}
Template template;
if (!context.CachedTemplates.TryGetValue(templatePath, out template))
{
string templateText;
try
{
templateText = templateLoader.Load(context, callerContext.Span, templatePath);
}
catch (Exception ex) when (!(ex is ScriptRuntimeException))
{
throw new ScriptRuntimeException(callerContext.Span, $"Unexpected exception while loading the include `{templateName}` from path `{templatePath}`", ex);
}
if (templateText == null)
{
throw new ScriptRuntimeException(callerContext.Span, $"The result of including `{templateName}->{templatePath}` cannot be null");
}
// Clone parser options
var parserOptions = context.TemplateLoaderParserOptions;
var lexerOptions = context.TemplateLoaderLexerOptions;
template = Template.Parse(templateText, templatePath, parserOptions, lexerOptions);
// If the template has any errors, throw an exception
if (template.HasErrors)
{
throw new ScriptParserRuntimeException(callerContext.Span, $"Error while parsing template `{templateName}` from `{templatePath}`", template.Messages);
}
context.CachedTemplates.Add(templatePath, template);
}
// Make sure that we cannot recursively include a template
object result = null;
context.EnterRecursive(callerContext);
var previousIndent = context.CurrentIndent;
context.CurrentIndent = null;
context.PushOutput();
var previousArguments = context.GetValue(ScriptVariable.Arguments);
try
{
context.SetValue(ScriptVariable.Arguments, arguments, true, true);
if (previousIndent != null)
{
// We reset before and after the fact that we have a new line
context.ResetPreviousNewLine();
}
result = template.Render(context);
if (previousIndent != null)
{
context.ResetPreviousNewLine();
}
}
finally
{
context.PopOutput();
context.CurrentIndent = previousIndent;
context.ExitRecursive(callerContext);
// Remove the arguments
context.DeleteValue(ScriptVariable.Arguments);
if (previousArguments != null)
{
// Restore them if necessary
context.SetValue(ScriptVariable.Arguments, previousArguments, true);
}
}
return result;
}
public int RequiredParameterCount => 1;
public int ParameterCount => 1;
public ScriptVarParamKind VarParamKind => ScriptVarParamKind.Direct;
public Type ReturnType => typeof(object);
public ScriptParameterInfo GetParameterInfo(int index)
{
if (index == 0) return new ScriptParameterInfo(typeof(string), "template_name");
return new ScriptParameterInfo(typeof(object), "value");
}
public int GetParameterIndexByName(string name)
{
throw new NotImplementedException();
}
}
}