using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; using Morestachio; using Morestachio.Framework.IO.SingleStream; using Morestachio.Rendering; namespace Jellyfin.Server.ServerSetupApp; /// /// Compiles and renders the startup UI Morestachio template. /// Shared by the live and the standalone startup UI preview tool so both /// exercise the exact same template and formatters. /// public sealed class StartupUiRenderer { private readonly IRenderer _renderer; private StartupUiRenderer(IRenderer renderer) { _renderer = renderer; } /// /// Compiles the startup UI template located at . /// /// The full path to the index.mstemplate.html template. /// A ready to use . public static async Task CreateAsync(string templatePath) { var fileTemplate = await File.ReadAllTextAsync(templatePath).ConfigureAwait(false); var renderer = (await ParserOptionsBuilder.New() .WithTemplate(fileTemplate) .WithFormatter( (Version version, int arg) => { // version type does not for some stupid reason implement IFormattable which morestachio relies on for ToString support therefor we need to do it manually. return version.ToString(arg); }, "ToString") .WithFormatter( (StartupLogTopic logEntry, IEnumerable children) => { if (children.Any()) { var maxLevel = logEntry.LogLevel; var stack = new Stack(children); while (maxLevel != LogLevel.Error && stack.Count > 0 && (logEntry = stack.Pop()) is not null) // error is the highest inherted error level. { maxLevel = maxLevel < logEntry.LogLevel ? logEntry.LogLevel : maxLevel; foreach (var child in logEntry.Children) { stack.Push(child); } } return maxLevel; } return logEntry.LogLevel; }, "FormatLogLevel") .WithFormatter( (LogLevel logLevel) => { switch (logLevel) { case LogLevel.Trace: case LogLevel.Debug: case LogLevel.None: return "success"; case LogLevel.Information: return "info"; case LogLevel.Warning: return "warn"; case LogLevel.Error: return "danger"; case LogLevel.Critical: return "danger-strong"; } return string.Empty; }, "ToString") .BuildAndParseAsync() .ConfigureAwait(false)) .CreateCompiledRenderer(); return new StartupUiRenderer(renderer); } /// /// Renders the template with the provided model into the target stream. /// /// The values made available to the template. /// The stream the rendered HTML is written to. /// A Task. public Task RenderAsync(IDictionary model, Stream output) { return _renderer.RenderAsync( model, new ByteCounterStream(output, IODefaults.FileStreamBufferSize, true, _renderer.ParserOptions)); } }