using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using ServiceStack.Extensions;
using ServiceStack.Text;
namespace ServiceStack.Script
{
///
/// Inverse of the #Script Language Template Syntax where each line is a statement
/// i.e. in contrast to #Script's default where text contains embedded template expressions {{ ... }}
///
public sealed class ScriptCode : ScriptLanguage
{
private ScriptCode() {} // force usage of singleton
public static readonly ScriptLanguage Language = new ScriptCode();
public override string Name => "code";
public override List Parse(ScriptContext context, ReadOnlyMemory body, ReadOnlyMemory modifiers)
{
var quiet = false;
if (!modifiers.IsEmpty)
{
quiet = modifiers.EqualsOrdinal("q") || modifiers.EqualsOrdinal("quiet") || modifiers.EqualsOrdinal("mute");
if (!quiet)
throw new NotSupportedException($"Unknown modifier '{modifiers.ToString()}', expected 'code|q', 'code|quiet' or 'code|mute'");
}
var statements = context.ParseCodeStatements(body);
return new List {
new PageJsBlockStatementFragment(new JsBlockStatement(statements)) {
Quiet = quiet,
},
};
}
public override async Task WritePageFragmentAsync(ScriptScopeContext scope, PageFragment fragment, CancellationToken token)
{
var page = scope.PageResult;
if (fragment is PageJsBlockStatementFragment blockFragment)
{
var blockStatements = blockFragment.Block.Statements;
if (blockFragment.Quiet && scope.OutputStream != Stream.Null)
scope = scope.ScopeWithStream(Stream.Null);
await page.WriteStatementsAsync(scope, blockStatements, token).ConfigAwait();
return true;
}
return false;
}
public override async Task WriteStatementAsync(ScriptScopeContext scope, JsStatement statement, CancellationToken token)
{
var page = scope.PageResult;
if (statement is JsExpressionStatement exprStatement)
{
var value = exprStatement.Expression.Evaluate(scope);
if (value != null && !ReferenceEquals(value, JsNull.Value) && value != StopExecution.Value && value != IgnoreResult.Value)
{
var strValue = page.Format.EncodeValue(value);
if (!string.IsNullOrEmpty(strValue))
{
var bytes = strValue.ToUtf8Bytes();
await scope.OutputStream.WriteAsync(bytes, token).ConfigAwait();
}
await scope.OutputStream.WriteAsync(JsTokenUtils.NewLineUtf8, token).ConfigAwait();
}
}
else if (statement is JsFilterExpressionStatement filterStatement)
{
await page.WritePageFragmentAsync(scope, filterStatement.FilterExpression, token).ConfigAwait();
if (!page.Context.RemoveNewLineAfterFiltersNamed.Contains(filterStatement.FilterExpression.LastFilterName))
{
await scope.OutputStream.WriteAsync(JsTokenUtils.NewLineUtf8, token).ConfigAwait();
}
}
else if (statement is JsBlockStatement blockStatement)
{
await page.WriteStatementsAsync(scope, blockStatement.Statements, token).ConfigAwait();
}
else if (statement is JsPageBlockFragmentStatement pageFragmentStatement)
{
await page.WritePageFragmentAsync(scope, pageFragmentStatement.Block, token).ConfigAwait();
}
else return false;
return true;
}
}
public static class ScriptCodeUtils
{
[Obsolete("Use CodeSharpPage")]
public static SharpPage CodeBlock(this ScriptContext context, string code) => context.CodeSharpPage(code);
public static SharpPage CodeSharpPage(this ScriptContext context, string code)
=> context.Pages.OneTimePage(code, context.PageFormats[0].Extension,p => p.ScriptLanguage = ScriptCode.Language);
private static void AssertCode(this ScriptContext context)
{
if (!context.ScriptLanguages.Contains(ScriptCode.Language))
throw new NotSupportedException($"ScriptCode.Language is not registered in {context.GetType().Name}.{nameof(context.ScriptLanguages)}");
}
private static PageResult GetCodePageResult(ScriptContext context, string code, Dictionary args)
{
context.AssertCode();
PageResult pageResult = null;
try
{
var page = context.CodeSharpPage(code);
pageResult = new PageResult(page);
args.Each((x, y) => pageResult.Args[x] = y);
return pageResult;
}
catch (Exception e)
{
if (ScriptContextUtils.ShouldRethrow(e))
throw;
throw ScriptContextUtils.HandleException(e, pageResult ?? new PageResult(context.EmptyPage));
}
}
public static string RenderCode(this ScriptContext context, string code, Dictionary args=null)
{
var pageResult = GetCodePageResult(context, code, args);
return pageResult.RenderScript();
}
public static async Task RenderCodeAsync(this ScriptContext context, string code, Dictionary args=null)
{
var pageResult = GetCodePageResult(context, code, args);
return await pageResult.RenderScriptAsync().ConfigAwait();
}
public static JsBlockStatement ParseCode(this ScriptContext context, string code) =>
context.ParseCode(code.AsMemory());
public static JsBlockStatement ParseCode(this ScriptContext context, ReadOnlyMemory code)
{
var statements = context.ParseCodeStatements(code);
return new JsBlockStatement(statements);
}
public static string EnsureReturn(string code)
{
if (code == null)
throw new ArgumentNullException(nameof(code));
// if code doesn't contain a return, wrap and return the expression
if (code.IndexOf(ScriptConstants.Return,StringComparison.Ordinal) == -1)
code = ScriptConstants.Return + "(" + code + ")";
return code;
}
public static T EvaluateCode(this ScriptContext context, string code, Dictionary args = null) =>
context.EvaluateCode(code, args).ConvertTo();
public static object EvaluateCode(this ScriptContext context, string code, Dictionary args=null)
{
var pageResult = GetCodePageResult(context, code, args);
if (!pageResult.EvaluateResult(out var returnValue))
throw new NotSupportedException(ScriptContextUtils.ErrorNoReturn);
return ScriptLanguage.UnwrapValue(returnValue);
}
public static async Task EvaluateCodeAsync(this ScriptContext context, string code, Dictionary args = null) =>
(await context.EvaluateCodeAsync(code, args).ConfigAwait()).ConvertTo();
public static async Task