public SystemMetadata Generate(InternalManifestItem item) { var attrs = new SystemMetadata { Language = Constants.DefaultLanguage, }; string key = GetFileKey(item.Key); var file = (TypeForwardedToRelativePath)(item.FileWithoutExtension + item.Extension); attrs.RelativePathToRoot = (TypeForwardedToRelativePath.Empty).MakeRelativeTo(file); var fileWithoutWorkingFolder = file.RemoveWorkingFolder(); attrs.Path = fileWithoutWorkingFolder; // 1. Root Toc is always in the top directory of output folder var rootToc = _toc.FirstOrDefault(); if (rootToc != null) { var rootTocPath = rootToc.File.RemoveWorkingFolder(); if (rootTocPath.SubdirectoryCount == 0) { attrs.RootTocPath = rootTocPath; var rootTocRelativePath = rootTocPath.MakeRelativeTo(file); attrs.RelativePathToRootToc = rootTocRelativePath; attrs.RootTocKey = rootToc.Key; Logger.LogVerbose($"Root TOC file {rootTocPath} is found."); } else { Logger.LogVerbose($"Root TOC file from output folder is not found, the toppest TOC file is {rootTocPath}"); } } // 2. The algorithm of toc current article belongs to: // a. If toc can be found in TocMap, return that toc // b. Elsewise, get the nearest toc, **nearest** means nearest toc in **OUTPUT** folder var parentTocFiles = _context.GetTocFileKeySet(key)?.Select(s => new FileInfo(s, (TypeForwardedToRelativePath)_context.GetFilePath(s))); var parentToc = GetNearestToc(parentTocFiles, file) ?? GetDefaultToc(key); if (parentToc != null) { var parentTocPath = parentToc.File.RemoveWorkingFolder(); attrs.TocPath = parentTocPath; var tocRelativePath = parentTocPath.MakeRelativeTo(file); attrs.RelativePathToToc = tocRelativePath; attrs.TocKey = parentToc.Key; Logger.LogVerbose($"TOC file {parentTocPath} is found for {item.LocalPathFromRoot}."); } else { Logger.LogVerbose($"TOC file for {item.LocalPathFromRoot} is not found."); } return attrs; }
private IEnumerable<TransformModelOptions> GetOptionsForEachTemplate(InternalManifestItem item, IDocumentBuildContext context) { if (item == null) { yield break; } foreach (var template in Templates) { if (template.ContainsGetOptions) { var options = template.GetOptions(item.Model.Content); if (options != null) { yield return options; } } } }
public ManifestItemWithContext(InternalManifestItem item, FileModel model, IDocumentProcessor processor, TemplateBundle bundle) { Item = item; FileModel = model; Processor = processor; TemplateBundle = bundle; }
internal TransformModelOptions GetOptions(InternalManifestItem item, IDocumentBuildContext context) { return MergeOptions(GetOptionsForEachTemplate(item, context)); }
/// <summary> /// Must guarantee thread safety /// </summary> /// <param name="item"></param> /// <returns></returns> internal ManifestItem Transform(InternalManifestItem item) { if (item.Model == null || item.Model.Content == null) throw new ArgumentNullException("Content for item.Model should not be null!"); var model = ConvertObjectToDictionary(item.Model.Content); model = AppendGlobalMetadata(model); if (_settings.Options.HasFlag(ApplyTemplateOptions.ExportRawModel)) { ExportModel(model, item.FileWithoutExtension, _settings.RawModelExportSettings); } var manifestItem = new ManifestItem { DocumentType = item.DocumentType, SourceRelativePath = item.LocalPathFromRoot, OutputFiles = new Dictionary<string, OutputFileInfo>(), Metadata = item.Metadata, }; var outputDirectory = _settings.OutputFolder ?? Directory.GetCurrentDirectory(); // 1. process resource if (item.ResourceFile != null) { // Resource file has already been processed in its plugin manifestItem.OutputFiles.Add("resource", new OutputFileInfo { RelativePath = item.ResourceFile, LinkToPath = null, Hash = null }); } // 2. process model var templateBundle = _templateCollection[item.DocumentType]; if (templateBundle == null) { return manifestItem; } HashSet<string> missingUids = new HashSet<string>(); // Must convert to JObject first as we leverage JsonProperty as the property name for the model foreach (var template in templateBundle.Templates) { if (!template.ContainsTemplateRenderer) { continue; } try { var extension = template.Extension; string outputFile = item.FileWithoutExtension + extension; string outputPath = Path.Combine(outputDirectory, outputFile); var dir = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); object viewModel = null; try { viewModel = template.TransformModel(model); } catch (Exception e) { // save raw model for further investigation: var exportSettings = ApplyTemplateSettings.RawModelExportSettingsForDebug; var rawModelPath = ExportModel(model, item.FileWithoutExtension, exportSettings); var message = $"Error transforming model \"{rawModelPath}\" generated from \"{item.LocalPathFromRoot}\" using \"{template.ScriptName}\": {e.Message}"; Logger.LogError(message); throw new DocumentException(message, e); } string result; try { result = template.Transform(viewModel); } catch (Exception e) { // save view model for further investigation: var exportSettings = ApplyTemplateSettings.ViewModelExportSettingsForDebug; var viewModelPath = ExportModel(viewModel, outputFile, exportSettings); var message = $"Error applying template \"{template.Name}\" to view model \"{viewModelPath}\" generated from \"{item.LocalPathFromRoot}\": {e.Message}"; Logger.LogError(message); throw new DocumentException(message, e); } if (_settings.Options.HasFlag(ApplyTemplateOptions.ExportViewModel)) { ExportModel(viewModel, outputFile, _settings.ViewModelExportSettings); } if (_settings.Options.HasFlag(ApplyTemplateOptions.TransformDocument)) { if (string.IsNullOrWhiteSpace(result)) { // TODO: WHAT to do if is transformed to empty string? STILL creat empty file? var exportSettings = ApplyTemplateSettings.ViewModelExportSettingsForDebug; var viewModelPath = ExportModel(viewModel, outputFile, exportSettings); Logger.LogWarning($"Model \"{viewModelPath}\" is transformed to empty string with template \"{template.Name}\""); } TransformDocument(result ?? string.Empty, extension, _context, outputPath, outputFile, missingUids, manifestItem); Logger.LogDiagnostic($"Transformed model \"{item.LocalPathFromRoot}\" to \"{outputPath}\"."); } } catch (PathTooLongException e) { var message = $"Error processing {item.LocalPathFromRoot}: {e.Message}"; throw new PathTooLongException(message, e); } } if (missingUids.Count > 0) { var uids = string.Join(", ", missingUids.Select(s => $"\"{s}\"")); Logger.LogWarning($"Invalid cross reference {uids}.", null, item.LocalPathFromRoot); } return manifestItem; }
public void TestMustacheTemplateProcessSingleTemplateWithNoScriptShouldWork() { // 1. Prepare template var templateName = "NoScript"; string template = @" {{#model}} name1={{name1}}, name2={{name2}}; {{/model}} "; var model = new { model = new object[] { new {name1 = "test1"}, new {name2 = "test2"}, } }; var modelFileName = Path.Combine(_inputFolder, "TestTemplateProcessor_NoScript.yml"); var item = new InternalManifestItem { DocumentType = string.Empty, Key = modelFileName, FileWithoutExtension = Path.ChangeExtension(modelFileName, null), ResourceFile = modelFileName, LocalPathFromRoot = modelFileName, }; ProcessTemplate(templateName, null, new[] { item }, model, _outputFolder, Tuple.Create("default.tmpl", template)); var outputFile = Path.Combine(_outputFolder, Path.ChangeExtension(modelFileName, string.Empty)); Assert.True(File.Exists(outputFile)); Assert.Equal(@" name1=test1, name2=; name1=, name2=test2; ", File.ReadAllText(outputFile)); }
public void TestLiquidTemplateProcessTemplateFolderWithDifferentTypeShouldWork() { var templateName = "TemplateFolder.liquid"; string defaultTemplate = @" default: {% for item in model -%} {{ item.name }} {% endfor -%} "; string conceptualTemplate = @" conceptual: {% for item in model -%} {{ item.name }} {% endfor -%} "; string script = @" exports.transform = function (model){ model.model.push({name:'test2'}); return model; }"; var model = new { model = new List<object> { new {name = "test1"}, }, another = new Dictionary<string, object> { ["key1"] = new { name = "test3" }, ["key2"] = new { name = "test4" } } }; string inputFolder = null; var item1 = new InternalManifestItem { FileWithoutExtension = "TestLiquidTemplateProcessor_TemplateFolderWithDifferentType1", Key = "x.yml", DocumentType = "Conceptual", LocalPathFromRoot = "TestLiquidTemplateProcessor_TemplateFolderWithDifferentType1.md", }; var item2 = new InternalManifestItem { DocumentType = string.Empty, FileWithoutExtension = "TestLiquidTemplateProcessor_TemplateFolderWithDifferentType2", Key = "y.yml", LocalPathFromRoot = "TestLiquidTemplateProcessor_TemplateFolderWithDifferentType2.md", }; ProcessTemplate(templateName, inputFolder, new[] { item1, item2 }, model, _outputFolder, Tuple.Create("default.html.liquid", defaultTemplate), Tuple.Create($"{templateName}/conceptual.md.liquid", conceptualTemplate), Tuple.Create("default.html.js", script), Tuple.Create("conceptual.md.js", script) ); var outputFilePath1 = Path.Combine(_outputFolder, "TestLiquidTemplateProcessor_TemplateFolderWithDifferentType1.md"); Assert.True(File.Exists(outputFilePath1)); Assert.Equal(@" conceptual: test1 test2 ", File.ReadAllText(outputFilePath1)); var outputFilePath2 = Path.Combine(_outputFolder, "TestLiquidTemplateProcessor_TemplateFolderWithDifferentType2.html"); Assert.True(File.Exists(outputFilePath2)); Assert.Equal(@" default: test1 test2 ", File.ReadAllText(outputFilePath2)); }
public void TestLiquidTemplateProcessSingleTemplateWithDependenciesShouldWork() { var templateName = "WithIncludes.liquid"; string template = @" {% ref reference1.html -%} {% ref reference2.html -%} {% for item in model -%} {{ item.name }} {% endfor -%} "; string script = @" exports.transform = function (model){ model.model.push({name:'test2'}); return model; }"; var model = new { model = new List<object> { new {name = "test1"}, } }; var modelFileName = Path.Combine(_inputFolder, "TestLiquidTemplateProcessor_WithIncludes.yml"); string inputFolder = null; var item = new InternalManifestItem { FileWithoutExtension = Path.ChangeExtension(modelFileName, null), DocumentType = string.Empty, Key = modelFileName, LocalPathFromRoot = modelFileName, }; ProcessTemplate(templateName, inputFolder, new[] { item }, model, _outputFolder, Tuple.Create("default.html.liquid", template), Tuple.Create("default.html.js", script), Tuple.Create("reference1.html", string.Empty), Tuple.Create("reference2.html", string.Empty) ); var outputFilePath = Path.Combine(_outputFolder, Path.ChangeExtension(modelFileName, "html")); Assert.True(File.Exists(outputFilePath)); Assert.True(File.Exists(Path.Combine(_outputFolder, "reference1.html"))); Assert.True(File.Exists(Path.Combine(_outputFolder, "reference2.html"))); Assert.Equal(@" test1 test2 ", File.ReadAllText(outputFilePath)); }
public void TestLiquidTemplateProcessSingleTemplateWithNoScriptWithIncludeShouldWork() { // 1. Prepare template var templateName = "NoScriptWithInclude.liquid"; string template = @" {% for item in model -%} {{ item.name }} {% endfor -%} {% include partial1 -%} "; string partial1 = @"partial 1: {% include partial2 -%}"; string partial2 = @"partial 2: {% for item in model -%} {{ item.name }} {% endfor -%} "; var model = new { model = new[] { new {name = "test1"}, new {name = "test2"}, } }; var modelFileName = Path.Combine(_inputFolder, "TestLiquidTemplateProcessor_NoScriptWithPartial.yml"); var item = new InternalManifestItem { DocumentType = string.Empty, Key = modelFileName, FileWithoutExtension = Path.ChangeExtension(modelFileName, null), ResourceFile = modelFileName, LocalPathFromRoot = modelFileName, }; ProcessTemplate( templateName, null, new[] { item }, model, _outputFolder, Tuple.Create("default.liquid", template), Tuple.Create("_partial1.liquid", partial1), Tuple.Create("_partial2.liquid", partial2)); var outputFile = Path.Combine(_outputFolder, Path.ChangeExtension(modelFileName, string.Empty)); Assert.True(File.Exists(outputFile)); Assert.Equal(@" test1 test2 partial 1: partial 2: test1 test2 ", File.ReadAllText(outputFile)); }
public void TestMustacheTemplateProcessInvalidTemplateShouldFail() { var templateName = "InvalidTemplate.html"; string inputFolder = null; var modelFileName = Path.Combine(_inputFolder, "TestTemplateProcessor_InvalidTemplate.yml"); var item = new InternalManifestItem { FileWithoutExtension = Path.ChangeExtension(modelFileName, null), DocumentType = string.Empty, Key = modelFileName, LocalPathFromRoot = modelFileName, }; ProcessTemplate(templateName, inputFolder, new[] { item }, new object(), _outputFolder, Tuple.Create("default.invalidtmpl", string.Empty), Tuple.Create("default.js", string.Empty), Tuple.Create("reference1.html", string.Empty), Tuple.Create("reference2.html", string.Empty) ); }