// TODO: remove public void Process(DocumentBuildContext context, string outputDirectory) { var baseDirectory = context.BuildOutputFolder; if (string.IsNullOrEmpty(outputDirectory)) outputDirectory = Environment.CurrentDirectory; if (string.IsNullOrEmpty(baseDirectory)) baseDirectory = Environment.CurrentDirectory; if (!Directory.Exists(outputDirectory)) Directory.CreateDirectory(outputDirectory); // 1. Copy dependent files with path relative to the base output directory ProcessDependencies(outputDirectory); UpdateFileMap(context, outputDirectory, Templates); List<TemplateManifestItem> manifest = new List<TemplateManifestItem>(); // 3. Process every model and save to output directory foreach (var item in context.Manifest) { var manifestItem = Transform(context, item, Templates, outputDirectory, true, s => s + ".json"); manifest.Add(manifestItem); } // Save manifest var manifestPath = Path.Combine(outputDirectory, ManifestFileName); JsonUtility.Serialize(manifestPath, manifest); Logger.Log(LogLevel.Verbose, $"Manifest file saved to {manifestPath}."); }
private void BuildCore( IDocumentProcessor processor, IEnumerable <FileAndType> files, ImmutableDictionary <string, object> metadata, FileMetadata fileMetadata, DocumentBuildContext context) { Logger.LogInfo($"Plug-in {processor.Name}: Loading document..."); using (var hostService = new HostService( from file in files select Load(processor, metadata, fileMetadata, file))) { hostService.SourceFiles = context.AllSourceFiles; foreach (var m in hostService.Models) { if (m.LocalPathFromRepoRoot == null) { m.LocalPathFromRepoRoot = Path.Combine(m.BaseDir, m.File); } } Logger.LogInfo($"Plug-in {processor.Name}: Document loaded (count = {hostService.Models.Count})."); Logger.LogInfo($"Plug-in {processor.Name}: Preprocessing..."); Prebuild(processor, hostService); Logger.LogInfo($"Plug-in {processor.Name}: Building..."); BuildArticle(processor, hostService); Logger.LogInfo($"Plug-in {processor.Name}: Postprocessing..."); Postbuild(processor, hostService); Logger.LogInfo($"Plug-in {processor.Name}: Saving..."); Save(processor, hostService, context); } }
private void Save(IDocumentProcessor processor, HostService hostService, DocumentBuildContext context) { hostService.Models.RunAll( m => { try { if (m.Type != DocumentType.Override) { using (new LoggerFileScope(m.OriginalFileAndType.File)) { Logger.LogVerbose($"Plug-in {processor.Name}: Saving..."); m.BaseDir = context.BuildOutputFolder; var result = processor.Save(m); if (result != null) { HandleSaveResult(context, hostService, m, result); } } } } finally { m.Dispose(); } }); }
private void Transform(DocumentBuildContext context, TemplateCollection templateCollection, bool exportMetadata) { if (templateCollection == null || templateCollection.Count == 0) { Logger.LogWarning("No template is found."); } else { Logger.LogInfo("Start applying template..."); } var outputDirectory = context.BuildOutputFolder; List <TemplateManifestItem> manifest = new List <TemplateManifestItem>(); // Model can apply multiple template with different extension, so append the view model extension instead of change extension Func <string, string> metadataPathProvider = (s) => { return(s + ViewModelExtension); }; foreach (var item in context.Manifest) { var manifestItem = TemplateProcessor.Transform(context, item, templateCollection, outputDirectory, exportMetadata, metadataPathProvider); manifest.Add(manifestItem); } // Save manifest var manifestPath = Path.Combine(outputDirectory, ManifestFileName); JsonUtility.Serialize(manifestPath, manifest); Logger.Log(LogLevel.Verbose, $"Manifest file saved to {manifestPath}."); }
private void Save(IDocumentProcessor processor, HostService hostService, DocumentBuildContext context) { hostService.Models.RunAll( m => { if (m.Type != DocumentType.Override) { using (new LoggerFileScope(m.OriginalFileAndType.File)) { Logger.LogVerbose($"Plug-in {processor.Name}: Saving..."); m.BaseDir = context.BuildOutputFolder; if (m.PathRewriter != null) { m.File = m.PathRewriter(m.File); } var result = processor.Save(m); if (result != null) { m.File = TemplateProcessor.UpdateFilePath(m.File, result.DocumentType, context.TemplateCollection); result.ModelFile = TemplateProcessor.UpdateFilePath(result.ModelFile, result.DocumentType, context.TemplateCollection); HandleSaveResult(context, hostService, m, result); } } } }); }
private static void HandleUids(DocumentBuildContext context, SaveResult result) { if (result.LinkToUids.Count > 0) { context.XRef.UnionWith(result.LinkToUids.Where(s => s != null)); } }
private void GetTocInfo(DocumentBuildContext context, ManifestItem item) { string relativePath = item.OriginalFile; var tocMap = context.TocMap; var fileMap = context.FileMap; HashSet<string> parentTocs; string parentToc = null; string rootToc = null; string currentPath = ((RelativePath)relativePath).GetPathFromWorkingFolder(); while (tocMap.TryGetValue(currentPath, out parentTocs) && parentTocs.Count > 0) { // Get the first toc only currentPath = parentTocs.First(); rootToc = currentPath; if (parentToc == null) parentToc = currentPath; currentPath = ((RelativePath)currentPath).GetPathFromWorkingFolder(); } if (rootToc != null) { rootToc = fileMap[((RelativePath)rootToc).GetPathFromWorkingFolder()]; PathUtility.TryGetPathFromWorkingFolder(rootToc, out rootToc); RootTocPath = rootToc; } if (parentToc == null) TocPath = RootTocPath; else { parentToc = fileMap[((RelativePath)parentToc).GetPathFromWorkingFolder()]; PathUtility.TryGetPathFromWorkingFolder(parentToc, out parentToc); TocPath = parentToc; } }
public SystemAttributes(DocumentBuildContext context, ManifestItem item, string lang) { Language = lang; GetTocInfo(context, item); TocRelativePath = TocPath == null ? null : ((RelativePath)TocPath).MakeRelativeTo((RelativePath)item.ModelFile); RootTocRelativePath = RootTocPath == null ? null : ((RelativePath)RootTocPath).MakeRelativeTo((RelativePath)item.ModelFile); RelativePathToRoot = (RelativePath.Empty).MakeRelativeTo((RelativePath)item.ModelFile); }
public void Build(DocumentBuildParameters parameters) { if (parameters == null) { throw new ArgumentNullException(nameof(parameters)); } if (parameters.OutputBaseDir == null) { throw new ArgumentException("Output folder cannot be null.", nameof(parameters) + "." + nameof(parameters.OutputBaseDir)); } if (parameters.Files == null) { throw new ArgumentException("Source files cannot be null.", nameof(parameters) + "." + nameof(parameters.Files)); } if (parameters.Metadata == null) { parameters.Metadata = ImmutableDictionary<string, object>.Empty; } using (new LoggerPhaseScope(Phase)) { Directory.CreateDirectory(parameters.OutputBaseDir); var context = new DocumentBuildContext( Path.Combine(Environment.CurrentDirectory, parameters.OutputBaseDir), parameters.Files.EnumerateFiles(), parameters.ExternalReferencePackages); Logger.LogInfo("Start building document ..."); foreach (var item in from file in parameters.Files.EnumerateFiles() group file by (from processor in Processors let priority = processor.GetProcessingPriority(file) where priority != ProcessingPriority.NotSupportted orderby priority descending select processor).FirstOrDefault()) { if (item.Key != null) { BuildCore(item.Key, item, parameters.Metadata, parameters.FileMetadata, context); } else { var sb = new StringBuilder(); sb.AppendLine("Cannot handle following file:"); foreach (var f in item) { sb.Append("\t"); sb.AppendLine(f.File); } Logger.LogWarning(sb.ToString()); } } context.SerializeTo(parameters.OutputBaseDir); Logger.LogInfo("Building document completed."); } }
public void Build(DocumentBuildParameters parameters) { if (parameters == null) { throw new ArgumentNullException(nameof(parameters)); } if (parameters.OutputBaseDir == null) { throw new ArgumentException("Output folder cannot be null.", nameof(parameters) + "." + nameof(parameters.OutputBaseDir)); } if (parameters.Files == null) { throw new ArgumentException("Source files cannot be null.", nameof(parameters) + "." + nameof(parameters.Files)); } if (parameters.Metadata == null) { parameters.Metadata = ImmutableDictionary <string, object> .Empty; } using (new LoggerPhaseScope(Phase)) { Directory.CreateDirectory(parameters.OutputBaseDir); var context = new DocumentBuildContext( Path.Combine(Environment.CurrentDirectory, parameters.OutputBaseDir), parameters.Files.EnumerateFiles(), parameters.ExternalReferencePackages); Logger.LogInfo("Start building document ..."); foreach (var item in from file in parameters.Files.EnumerateFiles() group file by(from processor in Processors let priority = processor.GetProcessingPriority(file) where priority != ProcessingPriority.NotSupportted orderby priority descending select processor).FirstOrDefault()) { if (item.Key != null) { BuildCore(item.Key, item, parameters.Metadata, parameters.FileMetadata, context); } else { var sb = new StringBuilder(); sb.AppendLine("Cannot handle following file:"); foreach (var f in item) { sb.Append("\t"); sb.AppendLine(f.File); } Logger.LogWarning(sb.ToString()); } } context.SerializeTo(parameters.OutputBaseDir); Logger.LogInfo("Building document completed."); } }
public static DocumentBuildContext DeserializeFrom(string outputBaseDir) { var context = new DocumentBuildContext(outputBaseDir); context.Manifest = YamlUtility.Deserialize<List<ManifestItem>>(Path.Combine(outputBaseDir, ManifestFileName)); context.FileMap = new Dictionary<string, string>(YamlUtility.Deserialize<Dictionary<string, string>>(Path.Combine(outputBaseDir, FileMapFileName)), FilePathComparer.OSPlatformSensitiveStringComparer); context.ExternalXRefSpec = YamlUtility.Deserialize<List<XRefSpec>>(Path.Combine(outputBaseDir, ExternalXRefSpecFileName)).ToDictionary(x => x.Uid, x => x.ToReadOnly()); context.XRefSpecMap = YamlUtility.Deserialize<List<XRefSpec>>(Path.Combine(outputBaseDir, InternalXRefSpecFileName)).ToDictionary(x => x.Uid, x => x.ToReadOnly()); context.TocMap = new Dictionary<string, HashSet<string>>(YamlUtility.Deserialize<Dictionary<string, HashSet<string>>>(Path.Combine(outputBaseDir, TocFileName)), FilePathComparer.OSPlatformSensitiveStringComparer); return context; }
private static void HandleUids(DocumentBuildContext context, FileModel model, SaveResult result) { foreach (var uid in model.Uids) { context.UidMap[uid] = ((RelativePath)model.File).GetPathFromWorkingFolder(); } if (result.LinkToUids.Length > 0) { context.XRef.UnionWith(result.LinkToUids); } }
public static DocumentBuildContext DeserializeFrom(string outputBaseDir) { var context = new DocumentBuildContext(outputBaseDir); context.Manifest = YamlUtility.Deserialize <List <ManifestItem> >(Path.Combine(outputBaseDir, ManifestFileName)); context.FileMap = new Dictionary <string, string>(YamlUtility.Deserialize <Dictionary <string, string> >(Path.Combine(outputBaseDir, FileMapFileName)), FilePathComparer.OSPlatformSensitiveStringComparer); context.ExternalXRefSpec = YamlUtility.Deserialize <List <XRefSpec> >(Path.Combine(outputBaseDir, ExternalXRefSpecFileName)).ToDictionary(x => x.Uid, x => x.ToReadOnly()); context.XRefSpecMap = YamlUtility.Deserialize <List <XRefSpec> >(Path.Combine(outputBaseDir, InternalXRefSpecFileName)).ToDictionary(x => x.Uid, x => x.ToReadOnly()); context.TocMap = new Dictionary <string, HashSet <string> >(YamlUtility.Deserialize <Dictionary <string, HashSet <string> > >(Path.Combine(outputBaseDir, TocFileName)), FilePathComparer.OSPlatformSensitiveStringComparer); return(context); }
private static void RegisterManifest(DocumentBuildContext context, FileModel model, SaveResult result) { context.Manifest.Add(new ManifestItem { DocumentType = result.DocumentType, ModelFile = result.ModelFile, ResourceFile = result.ResourceFile, OriginalFile = model.OriginalFileAndType.File, // TODO: What is API doc's LocalPathToRepo? => defined in ManagedReferenceDocumentProcessor LocalPathFromRepoRoot = model.LocalPathFromRepoRoot }); }
private static ManifestItem GetManifestItem(DocumentBuildContext context, FileModel model, SaveResult result) { return(new ManifestItem { DocumentType = result.DocumentType, ModelFile = result.ModelFile, ResourceFile = result.ResourceFile, Key = model.Key, // TODO: What is API doc's LocalPathToRepo? => defined in ManagedReferenceDocumentProcessor LocalPathFromRepoRoot = model.LocalPathFromRepoRoot, Model = model.ModelWithCache, InputFolder = model.OriginalFileAndType.BaseDir }); }
private void HandleSaveResult( DocumentBuildContext context, HostService hostService, FileModel model, SaveResult result) { context.FileMap[((RelativePath)model.OriginalFileAndType.File).GetPathFromWorkingFolder()] = ((RelativePath)model.File).GetPathFromWorkingFolder(); DocumentException.RunAll( () => CheckFileLink(hostService, model, result), () => HandleUids(context, model, result), () => HandleToc(context, result), () => RegisterXRefSpec(context, model, result), () => RegisterManifest(context, model, result)); }
private ManifestItem HandleSaveResult( DocumentBuildContext context, HostService hostService, FileModel model, SaveResult result) { context.FileMap[model.Key] = ((RelativePath)model.File).GetPathFromWorkingFolder(); DocumentException.RunAll( () => CheckFileLink(hostService, result), () => HandleUids(context, result), () => HandleToc(context, result), () => RegisterXRefSpec(context, result)); return(GetManifestItem(context, model, result)); }
private void RegisterXRefSpec(DocumentBuildContext context, FileModel model, SaveResult result) { foreach (var spec in result.XRefSpecs) { if (!string.IsNullOrWhiteSpace(spec?.Uid)) { if (context.XRefSpecMap.ContainsKey(spec.Uid)) { Logger.LogWarning($"Uid({spec.Uid}) duplicated.", file: model.LocalPathFromRepoRoot); } else { context.XRefSpecMap[spec.Uid] = spec.ToReadOnly(); } } } }
private void RegisterXRefSpec(DocumentBuildContext context, SaveResult result) { foreach (var spec in result.XRefSpecs) { if (!string.IsNullOrWhiteSpace(spec?.Uid)) { XRefSpec xref; if (context.XRefSpecMap.TryGetValue(spec.Uid, out xref)) { Logger.LogWarning($"Uid({spec.Uid}) has already been defined in {((RelativePath)xref.Href).RemoveWorkingFolder()}."); } else { context.XRefSpecMap[spec.Uid] = spec.ToReadOnly(); } } } }
private void UpdateContext(DocumentBuildContext context) { //update internal XrefMap if (context.XRefSpecMap != null) { foreach (var pair in context.XRefSpecMap) { string targetFilePath; if (context.FileMap.TryGetValue(pair.Value.Href, out targetFilePath)) { pair.Value.Href = targetFilePath; } else { Logger.LogWarning($"{pair.Value.Href} is not found in .filemap"); } } } context.SetExternalXRefSpec(); }
public static void UpdateFileMap(DocumentBuildContext context, string outputDirectory, TemplateCollection templateCollection) { //update internal XrefMap if (context.XRefSpecMap != null) { foreach (var pair in context.XRefSpecMap) { string targetFilePath; if (context.FileMap.TryGetValue(pair.Value.Href, out targetFilePath)) { pair.Value.Href = targetFilePath; } else { Logger.LogWarning($"{pair.Value.Href} is not found in .filemap"); } } } context.SetExternalXRefSpec(); }
private static void HandleToc(DocumentBuildContext context, SaveResult result) { if (result.TocMap?.Count > 0) { foreach (var toc in result.TocMap) { HashSet <string> list; if (context.TocMap.TryGetValue(toc.Key, out list)) { foreach (var item in toc.Value) { list.Add(item); } } else { context.TocMap[toc.Key] = toc.Value; } } } }
private static void HandleUids(DocumentBuildContext context, FileModel model, SaveResult result) { foreach (var uid in model.Uids) { context.UidMap[uid] = ((RelativePath)model.File).GetPathFromWorkingFolder(); } if (result.LinkToUids.Count > 0) { foreach (var item in result.LinkToUids) { HashSet <string> files; if (context.XRef.TryGetValue(item.Key, out files)) { files.UnionWith(item.Value); } else { context.XRef[item.Key] = item.Value; } } } }
private void ProcessTemplate(DocumentBuildContext context, string outputDirectory) { if (_templates == null || _templates.Count == 0) { Logger.Log(LogLevel.Info, "Template is not specified, files will not be transformed."); return; } using (var templateResource = new CompositeResourceCollectionWithOverridden(_templates.Select(s => _finder.Find(s)).Where(s => s != null))) { if (templateResource.IsEmpty) { Logger.Log(LogLevel.Warning, $"No template resource found for [{_templates.ToDelimitedString()}]."); } else { Logger.Log(LogLevel.Verbose, "Template resource found, starting applying template."); using (var processor = new TemplateProcessor(templateResource)) { processor.Process(context, outputDirectory); } } } }
private static void TranformHtml(DocumentBuildContext context, string transformed, string relativeModelPath, string outputPath) { // Update HREF and XREF var internalXref = context.XRefSpecMap; var externalXref = context.ExternalXRefSpec; HtmlAgilityPack.HtmlDocument html = new HtmlAgilityPack.HtmlDocument(); html.LoadHtml(transformed); var xrefExceptions = new List<CrossReferenceNotResolvedException>(); var xrefNodes = html.DocumentNode.SelectNodes("//xref/@href"); if (xrefNodes != null) { foreach(var xref in xrefNodes) { try { UpdateXref(xref, internalXref, externalXref, Language); } catch (CrossReferenceNotResolvedException e) { xrefExceptions.Add(e); } } } var srcNodes = html.DocumentNode.SelectNodes("//*/@src"); if (srcNodes != null) foreach (var link in srcNodes) { UpdateHref(link, "src", context.FileMap, relativeModelPath); } var hrefNodes = html.DocumentNode.SelectNodes("//*/@href"); if (hrefNodes != null) { foreach (var link in hrefNodes) { UpdateHref(link, "href", context.FileMap, relativeModelPath); } } // Save with extension changed var subDirectory = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(subDirectory) && !Directory.Exists(subDirectory)) Directory.CreateDirectory(subDirectory); html.Save(outputPath, Encoding.UTF8); if (xrefExceptions.Count > 0) { throw new AggregateException(xrefExceptions); } }
private void Transform(DocumentBuildContext context, TemplateCollection templateCollection, bool exportMetadata) { if (templateCollection == null || templateCollection.Count == 0) { Logger.LogWarning("No template is found."); } else { Logger.LogInfo("Start applying template..."); } var outputDirectory = context.BuildOutputFolder; List<TemplateManifestItem> manifest = new List<TemplateManifestItem>(); // Model can apply multiple template with different extension, so append the view model extension instead of change extension Func<string, string> metadataPathProvider = (s) => { return s + ViewModelExtension; }; foreach (var item in context.Manifest) { var manifestItem = TemplateProcessor.Transform(context, item, templateCollection, outputDirectory, exportMetadata, metadataPathProvider); manifest.Add(manifestItem); } // Save manifest var manifestPath = Path.Combine(outputDirectory, ManifestFileName); JsonUtility.Serialize(manifestPath, manifest); Logger.Log(LogLevel.Verbose, $"Manifest file saved to {manifestPath}."); }
public void ProcessTemplateAndTheme(DocumentBuildContext context, string outputDirectory, bool overwrite) { ProcessTemplate(context, outputDirectory); ProcessTheme(outputDirectory, overwrite); }
private static void HandleToc(DocumentBuildContext context, SaveResult result) { if (result.TocMap?.Count > 0) { foreach (var toc in result.TocMap) { HashSet<string> list; if (context.TocMap.TryGetValue(toc.Key, out list)) { foreach (var item in toc.Value) { list.Add(item); } } else { context.TocMap[toc.Key] = toc.Value; } } } }
private static void HandleUids(DocumentBuildContext context, FileModel model, SaveResult result) { foreach (var uid in model.Uids) { context.UidMap[uid] = ((RelativePath)model.File).GetPathFromWorkingFolder(); } if (result.LinkToUids.Count > 0) { foreach(var item in result.LinkToUids) { HashSet<string> files; if (context.XRef.TryGetValue(item.Key, out files)) { files.UnionWith(item.Value); } else { context.XRef[item.Key] = item.Value; } } } }
private static void HandleUids(DocumentBuildContext context, FileModel model, SaveResult result) { if (result.LinkToUids.Count > 0) { context.XRef.UnionWith(result.LinkToUids); } }
public void Build(DocumentBuildParameters parameters) { if (parameters == null) { throw new ArgumentNullException(nameof(parameters)); } if (parameters.OutputBaseDir == null) { throw new ArgumentException("Output folder cannot be null.", nameof(parameters) + "." + nameof(parameters.OutputBaseDir)); } if (parameters.Files == null) { throw new ArgumentException("Source files cannot be null.", nameof(parameters) + "." + nameof(parameters.Files)); } if (parameters.Metadata == null) { parameters.Metadata = ImmutableDictionary<string, object>.Empty; } using (new LoggerPhaseScope(PhaseName)) { Directory.CreateDirectory(parameters.OutputBaseDir); var context = new DocumentBuildContext( Path.Combine(Environment.CurrentDirectory, parameters.OutputBaseDir), parameters.Files.EnumerateFiles(), parameters.ExternalReferencePackages, parameters.TemplateCollection ); Logger.LogInfo("Start building document..."); IEnumerable<InnerBuildContext> innerContexts = Enumerable.Empty<InnerBuildContext>(); try { innerContexts = GetInnerContexts(parameters, Processors).ToList(); foreach (var item in innerContexts) { BuildCore(item.HostService, item.Processor, context); } foreach (var item in innerContexts) { UpdateHref(item.HostService, item.Processor, context); } UpdateContext(context); if (parameters.ExportRawModel) { Logger.LogInfo("Start exporting raw model..."); foreach(var item in context.Manifest) { var model = item.Model; if (model.Content != null) { var rawModelPath = Path.Combine(model.BaseDir, Path.ChangeExtension(model.File, RawModelExtension)); JsonUtility.Serialize(rawModelPath, model.Content); } } } Transform(context, parameters.TemplateCollection, parameters.ExportViewModel); } finally { foreach (var item in innerContexts) { if (item.HostService != null) { Cleanup(item.HostService); item.HostService.Dispose(); } } } Logger.LogInfo("Building document completed."); } }
private IEnumerable <ManifestItemWithContext> BuildCore(InnerBuildContext buildContext, DocumentBuildContext context) { var processor = buildContext.Processor; var hostService = buildContext.HostService; Logger.LogVerbose($"Plug-in {processor.Name}: Loading document..."); hostService.SourceFiles = context.AllSourceFiles; foreach (var m in hostService.Models) { if (m.LocalPathFromRepoRoot == null) { m.LocalPathFromRepoRoot = Path.Combine(m.BaseDir, m.File).ToDisplayPath(); } } var steps = string.Join("=>", processor.BuildSteps.OrderBy(step => step.BuildOrder).Select(s => s.Name)); Logger.LogInfo($"Building {hostService.Models.Count} file(s) in {processor.Name}({steps})..."); Logger.LogVerbose($"Plug-in {processor.Name}: Preprocessing..."); Prebuild(processor, hostService); Logger.LogVerbose($"Plug-in {processor.Name}: Building..."); BuildArticle(processor, hostService); Logger.LogVerbose($"Plug-in {processor.Name}: Postprocessing..."); Postbuild(processor, hostService); Logger.LogVerbose($"Plug-in {processor.Name}: Generating manifest..."); return(ExportManifest(buildContext, context)); }
private void BuildCore(HostService hostService, IDocumentProcessor processor, DocumentBuildContext context) { Logger.LogInfo($"Plug-in {processor.Name}: Loading document..."); hostService.SourceFiles = context.AllSourceFiles; foreach (var m in hostService.Models) { if (m.LocalPathFromRepoRoot == null) { m.LocalPathFromRepoRoot = Path.Combine(m.BaseDir, m.File); } } Logger.LogInfo($"Plug-in {processor.Name}: Document loaded (count = {hostService.Models.Count})."); Logger.LogInfo($"Plug-in {processor.Name}: Preprocessing..."); Prebuild(processor, hostService); Logger.LogInfo($"Plug-in {processor.Name}: Building..."); BuildArticle(processor, hostService); Logger.LogInfo($"Plug-in {processor.Name}: Postprocessing..."); Postbuild(processor, hostService); Logger.LogInfo($"Plug-in {processor.Name}: Saving..."); Save(processor, hostService, context); }
private void BuildCore( IDocumentProcessor processor, IEnumerable<FileAndType> files, ImmutableDictionary<string, object> metadata, FileMetadata fileMetadata, DocumentBuildContext context) { Logger.LogInfo($"Plug-in {processor.Name}: Loading document..."); using (var hostService = new HostService( from file in files select Load(processor, metadata, fileMetadata, file))) { hostService.SourceFiles = context.AllSourceFiles; foreach (var m in hostService.Models) { if (m.LocalPathFromRepoRoot == null) { m.LocalPathFromRepoRoot = Path.Combine(m.BaseDir, m.File); } } Logger.LogInfo($"Plug-in {processor.Name}: Document loaded (count = {hostService.Models.Count})."); Logger.LogInfo($"Plug-in {processor.Name}: Preprocessing..."); Prebuild(processor, hostService); Logger.LogInfo($"Plug-in {processor.Name}: Building..."); BuildArticle(processor, hostService); Logger.LogInfo($"Plug-in {processor.Name}: Postprocessing..."); Postbuild(processor, hostService); Logger.LogInfo($"Plug-in {processor.Name}: Saving..."); Save(processor, hostService, context); } }
private void TranformHtml(DocumentBuildContext context, string transformed, string relativeModelPath, string outputPath) { // Update HREF and XREF HtmlAgilityPack.HtmlDocument html = new HtmlAgilityPack.HtmlDocument(); html.LoadHtml(transformed); var srcNodes = html.DocumentNode.SelectNodes("//*/@src"); if (srcNodes != null) foreach (var link in srcNodes) { UpdateSrc(link, context.FileMap, s => UpdateFilePath(s, relativeModelPath)); } var hrefNodes = html.DocumentNode.SelectNodes("//*/@href"); if (hrefNodes != null) foreach (var link in hrefNodes) { // xref is generated by docfx, and is lower-cased if (link.Name == "xref") { UpdateXref(link, context.XRefSpecMap, context.ExternalXRefSpec, s => UpdateFilePath(s, relativeModelPath), Language); } else { UpdateHref(link, context.FileMap, s => UpdateFilePath(s, relativeModelPath)); } } // Save with extension changed var subDirectory = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(subDirectory) && !Directory.Exists(subDirectory)) Directory.CreateDirectory(subDirectory); html.Save(outputPath, Encoding.UTF8); }
private void UpdateHref(HostService hostService, IDocumentProcessor processor, DocumentBuildContext context) { Func <string, string, string> updater = (originalPathToFile, filePathToRoot) => { string href; if (string.IsNullOrEmpty(originalPathToFile) || !context.FileMap.TryGetValue(originalPathToFile, out href)) { return(originalPathToFile); } var relativePath = ((RelativePath)href).MakeRelativeTo(((RelativePath)filePathToRoot).GetPathFromWorkingFolder()); return(relativePath); }; hostService.Models.RunAll( m => { using (new LoggerFileScope(m.OriginalFileAndType.File)) { Logger.LogVerbose($"Plug-in {processor.Name}: Updating href..."); processor.UpdateHref(m, updater); } }); }
public void Process(DocumentBuildContext context, string outputDirectory) { var baseDirectory = context.BuildOutputFolder; if (string.IsNullOrEmpty(outputDirectory)) outputDirectory = Environment.CurrentDirectory; if (string.IsNullOrEmpty(baseDirectory)) baseDirectory = Environment.CurrentDirectory; if (!Directory.Exists(outputDirectory)) Directory.CreateDirectory(outputDirectory); // 1. Copy dependent files with path relative to the base output directory ProcessDependencies(outputDirectory); Dictionary<string, HashSet<string>> unProcessedType = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase); Dictionary<string, string> extMapping = new Dictionary<string, string>(); // 2. Get extension for each item foreach (var item in context.Manifest) { if (item.ModelFile == null) throw new ArgumentNullException("Model file path must be specified!"); if (item.DocumentType == null) throw new ArgumentNullException($"Document type is not allowed to be NULL for ${item.ModelFile}!"); // NOTE: Resource is not supported for applying templates if (item.DocumentType.Equals("Resource", StringComparison.OrdinalIgnoreCase)) continue; var templates = _templates[item.DocumentType]; // Get default template extension if (templates == null || templates.Count == 0) { HashSet<string> unProcessedFiles; if (unProcessedType.TryGetValue(item.DocumentType, out unProcessedFiles)) { unProcessedFiles.Add(item.ModelFile); } else { unProcessedType[item.DocumentType] = new HashSet<string>(FilePathComparer.OSPlatformSensitiveComparer) { item.ModelFile }; } } else { var defaultTemplate = templates.FirstOrDefault(s => s.IsPrimary) ?? templates[0]; string key = ((RelativePath)item.OriginalFile).GetPathFromWorkingFolder(); string value; if (context.FileMap.TryGetValue(key, out value)) { context.FileMap[key] = Path.ChangeExtension(value, defaultTemplate.Extension); extMapping[key] = defaultTemplate.Extension; } else { Logger.Log(LogLevel.Warning, $"{key} is not found in .filemap"); } } } //update internal XrefMap if (context.XRefSpecMap != null) { foreach (var pair in context.XRefSpecMap) { string ext; if (extMapping.TryGetValue(pair.Value.Href, out ext)) { pair.Value.Href = Path.ChangeExtension(pair.Value.Href, ext); } } } if (unProcessedType.Count > 0) { StringBuilder sb = new StringBuilder("There is no template processing:"); foreach (var type in unProcessedType) { sb.AppendLine($"- Document type: \"{type.Key}\""); sb.AppendLine($"- Files:"); foreach (var file in type.Value) { sb.AppendLine($" -\"{file}\""); } } Logger.Log(LogLevel.Warning, sb.ToString());// not processed but copied to '{modelOutputPath}'"); } List<TemplateManifestItem> manifest = new List<TemplateManifestItem>(); // 3. Process every model and save to output directory foreach (var item in context.Manifest) { var manifestItem = new TemplateManifestItem { DocumentType = item.DocumentType, OriginalFile = item.LocalPathFromRepoRoot, OutputFiles = new Dictionary<string, string>() }; try { var templates = _templates[item.DocumentType]; // 1. process model if (templates == null) { // TODO: what if template to transform the type is not found? DO NOTHING? // CopyFile(modelFile, modelOutputPath); } else { var modelFile = Path.Combine(baseDirectory, item.ModelFile); var systemAttrs = new SystemAttributes(context, item, TemplateProcessor.Language); foreach (var template in templates) { var extension = template.Extension; string outputFile = Path.ChangeExtension(item.ModelFile, extension); string outputPath = Path.Combine(outputDirectory ?? string.Empty, outputFile); var dir = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); var transformed = template.Transform(modelFile, systemAttrs); if (!string.IsNullOrWhiteSpace(transformed)) { if (extension.Equals(".html", StringComparison.OrdinalIgnoreCase)) { TranformHtml(context, transformed, item.ModelFile, outputPath); } else { File.WriteAllText(outputPath, transformed, Encoding.UTF8); } Logger.Log(LogLevel.Verbose, $"Transformed model \"{item.ModelFile}\" to \"{outputPath}\"."); } else { // TODO: WHAT to do if is transformed to empty string? STILL creat empty file? Logger.Log(LogLevel.Warning, $"Model \"{item.ModelFile}\" is transformed to empty string with template \"{template.Name}\""); File.WriteAllText(outputPath, string.Empty); } manifestItem.OutputFiles.Add(extension, outputFile); } } // 2. process resource if (item.ResourceFile != null) { manifestItem.OutputFiles.Add("resource", item.ResourceFile); PathUtility.CopyFile(Path.Combine(baseDirectory, item.ResourceFile), Path.Combine(outputDirectory, item.ResourceFile), true); } } catch (Exception e) { Logger.Log(LogLevel.Warning, $"Unable to transform {item.ModelFile}: {e.Message}. Ignored."); } manifest.Add(manifestItem); } // Save manifest var manifestPath = Path.Combine(outputDirectory, ManifestFileName); JsonUtility.Serialize(manifestPath, manifest); Logger.Log(LogLevel.Verbose, $"Manifest file saved to {manifestPath}."); }
public void Build(DocumentBuildParameters parameters) { if (parameters == null) { throw new ArgumentNullException(nameof(parameters)); } if (parameters.OutputBaseDir == null) { throw new ArgumentException("Output folder cannot be null.", nameof(parameters) + "." + nameof(parameters.OutputBaseDir)); } if (parameters.Files == null) { throw new ArgumentException("Source files cannot be null.", nameof(parameters) + "." + nameof(parameters.Files)); } if (parameters.Metadata == null) { parameters.Metadata = ImmutableDictionary <string, object> .Empty; } using (new LoggerPhaseScope(PhaseName)) { Directory.CreateDirectory(parameters.OutputBaseDir); var context = new DocumentBuildContext( Path.Combine(Environment.CurrentDirectory, parameters.OutputBaseDir), parameters.Files.EnumerateFiles(), parameters.ExternalReferencePackages ); Logger.LogVerbose("Start building document..."); IEnumerable <InnerBuildContext> innerContexts = Enumerable.Empty <InnerBuildContext>(); try { using (var processor = parameters.TemplateManager?.GetTemplateProcessor()) { innerContexts = GetInnerContexts(parameters, Processors, processor).ToList(); var manifest = new List <ManifestItemWithContext>(); foreach (var item in innerContexts) { manifest.AddRange(BuildCore(item, context)); } // Use manifest from now on UpdateContext(context); UpdateHref(manifest, context); TemplateProcessor.Transform(processor, manifest.Select(s => s.Item).ToList(), context, parameters.ApplyTemplateSettings); Logger.LogInfo($"Building {manifest.Count} file(s) completed."); } } finally { foreach (var item in innerContexts) { if (item.HostService != null) { Cleanup(item.HostService); item.HostService.Dispose(); } } } } }
// TODO: change to use IDocumentBuildContext public static TemplateManifestItem Transform(DocumentBuildContext context, ManifestItem item, TemplateCollection templateCollection, string outputDirectory, bool exportMetadata, Func<string, string> metadataFilePathProvider) { if (item.Model == null || item.Model.Content == null) throw new ArgumentNullException("Content for item.Model should not be null!"); var baseDirectory = context.BuildOutputFolder ?? string.Empty; var manifestItem = new TemplateManifestItem { DocumentType = item.DocumentType, OriginalFile = item.LocalPathFromRepoRoot, OutputFiles = new Dictionary<string, string>() }; HashSet<string> missingUids = new HashSet<string>(); if (templateCollection == null || templateCollection.Count == 0) { return manifestItem; } try { var model = item.Model.Content; var templates = templateCollection[item.DocumentType]; // 1. process model if (templates == null) { // Logger.LogWarning($"There is no template processing {item.DocumentType} document \"{item.LocalPathFromRepoRoot}\""); } else { var systemAttrs = new SystemAttributes(context, item, TemplateProcessor.Language); foreach (var template in templates) { var extension = template.Extension; string outputFile = Path.ChangeExtension(item.ModelFile, extension); string outputPath = Path.Combine(outputDirectory ?? string.Empty, outputFile); var dir = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); string transformed; var result = template.TransformModel(model, systemAttrs); if (exportMetadata) { if (metadataFilePathProvider == null) { throw new ArgumentNullException(nameof(metadataFilePathProvider)); } JsonUtility.Serialize(metadataFilePathProvider(outputPath), result.Model); } transformed = result.Result; if (!string.IsNullOrWhiteSpace(transformed)) { if (extension.Equals(".html", StringComparison.OrdinalIgnoreCase)) { try { TranformHtml(context, transformed, item.ModelFile, outputPath); } catch (AggregateException e) { e.Handle(s => { var xrefExcetpion = s as CrossReferenceNotResolvedException; if (xrefExcetpion != null) { missingUids.Add(xrefExcetpion.UidRawText); return true; } else { return false; } }); } } else { File.WriteAllText(outputPath, transformed, Encoding.UTF8); } Logger.Log(LogLevel.Verbose, $"Transformed model \"{item.ModelFile}\" to \"{outputPath}\"."); } else { // TODO: WHAT to do if is transformed to empty string? STILL creat empty file? Logger.LogWarning($"Model \"{item.ModelFile}\" is transformed to empty string with template \"{template.Name}\""); File.WriteAllText(outputPath, string.Empty); } manifestItem.OutputFiles.Add(extension, outputFile); } } // 2. process resource if (item.ResourceFile != null) { PathUtility.CopyFile(Path.Combine(baseDirectory, item.ResourceFile), Path.Combine(outputDirectory, item.ResourceFile), true); manifestItem.OutputFiles.Add("resource", item.ResourceFile); } } catch (Exception e) { Logger.LogError($"Unable to transform {item.ModelFile}: {e.Message}. Ignored."); throw; } if (missingUids.Count > 0) { var uids = string.Join(", ", missingUids.Select(s => $"\"{s}\"")); Logger.LogWarning($"Unable to resolve cross-reference {uids} for \"{manifestItem.OriginalFile.ToDisplayPath()}\""); } return manifestItem; }
public static TemplateManifestItem Transform(DocumentBuildContext context, ManifestItem item, TemplateCollection templateCollection, string outputDirectory, bool exportMetadata, Func<string, string> metadataFilePathProvider) { var baseDirectory = context.BuildOutputFolder ?? string.Empty; var manifestItem = new TemplateManifestItem { DocumentType = item.DocumentType, OriginalFile = item.LocalPathFromRepoRoot, OutputFiles = new Dictionary<string, string>() }; if (templateCollection == null || templateCollection.Count == 0) { return manifestItem; } try { var model = item.Model?.Content; var templates = templateCollection[item.DocumentType]; // 1. process model if (templates == null) { // Logger.LogWarning($"There is no template processing {item.DocumentType} document \"{item.LocalPathFromRepoRoot}\""); } else { var modelFile = Path.Combine(baseDirectory, item.ModelFile); var systemAttrs = new SystemAttributes(context, item, TemplateProcessor.Language); foreach (var template in templates) { var extension = template.Extension; string outputFile = Path.ChangeExtension(item.ModelFile, extension); string outputPath = Path.Combine(outputDirectory ?? string.Empty, outputFile); var dir = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); string transformed; if (model == null) { // TODO: remove // currently keep to pass UT transformed = template.Transform(item.ModelFile, systemAttrs); } else { var result = template.TransformModel(model, systemAttrs); if (exportMetadata) { if (metadataFilePathProvider == null) { throw new ArgumentNullException(nameof(metadataFilePathProvider)); } JsonUtility.Serialize(metadataFilePathProvider(outputPath), result.Model); } transformed = result.Result; } if (!string.IsNullOrWhiteSpace(transformed)) { if (extension.Equals(".html", StringComparison.OrdinalIgnoreCase)) { TranformHtml(context, transformed, item.ModelFile, outputPath); } else { File.WriteAllText(outputPath, transformed, Encoding.UTF8); } Logger.Log(LogLevel.Verbose, $"Transformed model \"{item.ModelFile}\" to \"{outputPath}\"."); } else { // TODO: WHAT to do if is transformed to empty string? STILL creat empty file? Logger.LogWarning($"Model \"{item.ModelFile}\" is transformed to empty string with template \"{template.Name}\""); File.WriteAllText(outputPath, string.Empty); } manifestItem.OutputFiles.Add(extension, outputFile); } } // 2. process resource if (item.ResourceFile != null) { PathUtility.CopyFile(Path.Combine(baseDirectory, item.ResourceFile), Path.Combine(outputDirectory, item.ResourceFile), true); manifestItem.OutputFiles.Add("resource", item.ResourceFile); } } catch (Exception e) { Logger.LogWarning($"Unable to transform {item.ModelFile}: {e.Message}. Ignored."); } return manifestItem; }
private void RegisterXRefSpec(DocumentBuildContext context, FileModel model, SaveResult result) { foreach (var spec in result.XRefSpecs) { if (!string.IsNullOrWhiteSpace(spec?.Uid)) { XRefSpec xref; if (context.XRefSpecMap.TryGetValue(spec.Uid, out xref)) { Logger.LogWarning($"Uid({spec.Uid}) has already been defined in {((RelativePath)xref.Href).RemoveWorkingFolder()}.", file: model.LocalPathFromRepoRoot); } else { context.XRefSpecMap[spec.Uid] = spec.ToReadOnly(); } } } }
private void UpdateHref(HostService hostService, IDocumentProcessor processor, DocumentBuildContext context) { Func<string, string, string> updater = (originalPathToFile, filePathToRoot) => { string href; if (string.IsNullOrEmpty(originalPathToFile) || !context.FileMap.TryGetValue(originalPathToFile, out href)) { return originalPathToFile; } var relativePath = ((RelativePath)href).MakeRelativeTo(((RelativePath)filePathToRoot).GetPathFromWorkingFolder()); return relativePath; }; hostService.Models.RunAll( m => { using (new LoggerFileScope(m.OriginalFileAndType.File)) { Logger.LogVerbose($"Plug-in {processor.Name}: Updating href..."); processor.UpdateHref(m, updater); } }); }
private IEnumerable <ManifestItemWithContext> ExportManifest(InnerBuildContext buildContext, DocumentBuildContext context) { var hostService = buildContext.HostService; var processor = buildContext.Processor; var templateProcessor = buildContext.TemplateProcessor; var manifestItems = new List <ManifestItemWithContext>(); hostService.Models.RunAll(m => { if (m.Type != DocumentType.Override) { using (new LoggerFileScope(m.LocalPathFromRepoRoot)) { Logger.LogVerbose($"Plug-in {processor.Name}: Saving..."); m.BaseDir = context.BuildOutputFolder; if (m.PathRewriter != null) { m.File = m.PathRewriter(m.File); } var result = processor.Save(m); if (result != null) { if (templateProcessor != null) { m.File = templateProcessor.UpdateFileExtension(m.File, result.DocumentType); result.ModelFile = templateProcessor.UpdateFileExtension(result.ModelFile, result.DocumentType); } var item = HandleSaveResult(context, hostService, m, result); manifestItems.Add(new ManifestItemWithContext(item, m, processor)); } } } }); return(manifestItems); }
public void Build(DocumentBuildParameters parameters) { if (parameters == null) { throw new ArgumentNullException(nameof(parameters)); } if (parameters.OutputBaseDir == null) { throw new ArgumentException("Output folder cannot be null.", nameof(parameters) + "." + nameof(parameters.OutputBaseDir)); } if (parameters.Files == null) { throw new ArgumentException("Source files cannot be null.", nameof(parameters) + "." + nameof(parameters.Files)); } if (parameters.Metadata == null) { parameters.Metadata = ImmutableDictionary <string, object> .Empty; } using (new LoggerPhaseScope(PhaseName)) { Directory.CreateDirectory(parameters.OutputBaseDir); var context = new DocumentBuildContext( Path.Combine(Environment.CurrentDirectory, parameters.OutputBaseDir), parameters.Files.EnumerateFiles(), parameters.ExternalReferencePackages, parameters.TemplateCollection ); Logger.LogInfo("Start building document..."); IEnumerable <InnerBuildContext> innerContexts = Enumerable.Empty <InnerBuildContext>(); try { innerContexts = GetInnerContexts(parameters, Processors).ToList(); foreach (var item in innerContexts) { BuildCore(item.HostService, item.Processor, context); } foreach (var item in innerContexts) { UpdateHref(item.HostService, item.Processor, context); } UpdateContext(context); if (parameters.ExportRawModel) { Logger.LogInfo("Start exporting raw model..."); foreach (var item in context.Manifest) { var model = item.Model; if (model.Content != null) { var rawModelPath = Path.Combine(model.BaseDir, Path.ChangeExtension(model.File, RawModelExtension)); JsonUtility.Serialize(rawModelPath, model.Content); } } } Transform(context, parameters.TemplateCollection, parameters.ExportViewModel); } finally { foreach (var item in innerContexts) { if (item.HostService != null) { Cleanup(item.HostService); item.HostService.Dispose(); } } } Logger.LogInfo("Building document completed."); } }
private void UpdateContext(DocumentBuildContext context) { context.SetExternalXRefSpec(); }