From 218757c57e281299e8d009a40cdf91055ab6c01b Mon Sep 17 00:00:00 2001 From: Yufei Huang Date: Wed, 12 Sep 2018 14:16:32 +0800 Subject: [PATCH 1/9] Add basic static rendering --- docs/specs/rendering.yml | 9 +++++++++ src/Microsoft.Docs.Template/Models/Conceptual.cs | 1 + .../Views/Template/PageModel.cshtml | 0 src/docfx/build/page/BuildPage.cs | 10 ++++++++-- src/docfx/cli/CommandLineOptions.cs | 1 + src/docfx/config/Config.cs | 7 ++++--- src/docfx/config/OutputConfig.cs | 5 +++++ src/docfx/docset/Document.cs | 8 +++++--- test/docfx.Test/build/DocumentTest.cs | 2 +- test/docfx.Test/docfx.test.yml | 2 ++ test/docfx.Test/e2e/E2ETest.cs | 10 +++++++++- 11 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 docs/specs/rendering.yml create mode 100644 src/Microsoft.Docs.Template/Views/Template/PageModel.cshtml diff --git a/docs/specs/rendering.yml b/docs/specs/rendering.yml new file mode 100644 index 00000000000..5591d97d85c --- /dev/null +++ b/docs/specs/rendering.yml @@ -0,0 +1,9 @@ +# Apply template and produce html page when output is not json +inputs: + docfx.yml: | + output: + json: false + docs/a.md: +outputs: + docs/a.html: + build.manifest: diff --git a/src/Microsoft.Docs.Template/Models/Conceptual.cs b/src/Microsoft.Docs.Template/Models/Conceptual.cs index 9e14676bed3..2e01cdf4878 100644 --- a/src/Microsoft.Docs.Template/Models/Conceptual.cs +++ b/src/Microsoft.Docs.Template/Models/Conceptual.cs @@ -7,6 +7,7 @@ namespace Microsoft.Docs.Build // Conceptual model is more than just an html string, it also contain other properties. // We currently bake these other properties into PageModel to make the output flat, // but it makes conceptual kinda special. We may consider lift them outside PageModel. + [PageSchema] public class Conceptual { public string Html { get; set; } diff --git a/src/Microsoft.Docs.Template/Views/Template/PageModel.cshtml b/src/Microsoft.Docs.Template/Views/Template/PageModel.cshtml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/docfx/build/page/BuildPage.cs b/src/docfx/build/page/BuildPage.cs index 351d31373bb..01c6c3489f5 100644 --- a/src/docfx/build/page/BuildPage.cs +++ b/src/docfx/build/page/BuildPage.cs @@ -13,7 +13,7 @@ namespace Microsoft.Docs.Build { internal static class BuildPage { - public static async Task<(IEnumerable errors, PageModel result, DependencyMap dependencies)> Build( + public static async Task<(IEnumerable errors, object result, DependencyMap dependencies)> Build( Document file, TableOfContentsMap tocMap, ContributionInfo contribution, @@ -43,7 +43,13 @@ internal static class BuildPage if (error != null) errors.Add(error); - return (errors, model, dependencies.Build()); + var output = (object)model; + if (!file.Docset.Config.Output.Json && schema.Attribute is PageSchemaAttribute) + { + output = await Template.Render(model); + } + + return (errors, output, dependencies.Build()); } private static async Task<(List errors, Schema schema, PageModel model)> diff --git a/src/docfx/cli/CommandLineOptions.cs b/src/docfx/cli/CommandLineOptions.cs index f834a6b3851..ac74b47016e 100644 --- a/src/docfx/cli/CommandLineOptions.cs +++ b/src/docfx/cli/CommandLineOptions.cs @@ -19,6 +19,7 @@ public JObject ToJObject() ["output"] = new JObject { ["path"] = Output != null ? (JValue)Output : JValue.CreateNull(), + ["json"] = Legacy ? (JValue)true : JValue.CreateNull(), }, }; } diff --git a/src/docfx/config/Config.cs b/src/docfx/config/Config.cs index 66315c12de6..2aea3ac7f08 100644 --- a/src/docfx/config/Config.cs +++ b/src/docfx/config/Config.cs @@ -172,14 +172,15 @@ private static (List, Config) LoadCore(string docsetPath, string configPa if (extend) { - var extendErros = new List(); - (extendErros, finalConfigObject) = ExtendConfigs(finalConfigObject, docsetPath, restoreMap ?? new RestoreMap(docsetPath)); - errors.AddRange(extendErros); + var extendErrors = new List(); + (extendErrors, finalConfigObject) = ExtendConfigs(finalConfigObject, docsetPath, restoreMap ?? new RestoreMap(docsetPath)); + errors.AddRange(extendErrors); } var deserializeErrors = new List(); (deserializeErrors, config) = JsonUtility.ToObject(finalConfigObject); errors.AddRange(deserializeErrors); + return (errors, config); } diff --git a/src/docfx/config/OutputConfig.cs b/src/docfx/config/OutputConfig.cs index 377c03bc2e6..6a90440074c 100644 --- a/src/docfx/config/OutputConfig.cs +++ b/src/docfx/config/OutputConfig.cs @@ -10,6 +10,11 @@ internal sealed class OutputConfig /// public readonly string Path = "_site"; + /// + /// Gets whether to output JSON model. + /// + public readonly bool Json = false; + /// /// Gets whether resources are copied to output. /// diff --git a/src/docfx/docset/Document.cs b/src/docfx/docset/Document.cs index e3a42abc7d6..063a8c8db6f 100644 --- a/src/docfx/docset/Document.cs +++ b/src/docfx/docset/Document.cs @@ -214,7 +214,7 @@ public static (Error error, Document doc) TryCreate(Docset docset, string path, var isExperimental = Path.GetFileNameWithoutExtension(filePath).EndsWith(".experimental", PathUtility.PathComparison); var routedFilePath = ApplyRoutes(filePath, docset.Config.Routes); - var sitePath = FilePathToSitePath(routedFilePath, type, schema); + var sitePath = FilePathToSitePath(routedFilePath, type, schema, docset.Config.Output.Json); var siteUrl = PathToAbsoluteUrl(sitePath, type, schema); var outputPath = sitePath; var contentType = redirectionUrl != null ? ContentType.Redirection : type; @@ -293,17 +293,19 @@ internal static ContentType GetContentType(string path) return ContentType.Page; } - internal static string FilePathToSitePath(string path, ContentType contentType, Schema schema) + internal static string FilePathToSitePath(string path, ContentType contentType, Schema schema, bool json) { switch (contentType) { case ContentType.Page: if (schema == null || schema.Attribute is PageSchemaAttribute) { + var extension = json ? ".json" : ".html"; if (Path.GetFileNameWithoutExtension(path).Equals("index", PathUtility.PathComparison)) { - return Path.Combine(Path.GetDirectoryName(path), "index.json").Replace('\\', '/'); + return Path.Combine(Path.GetDirectoryName(path), "index" + extension).Replace('\\', '/'); } + return Path.ChangeExtension(path, extension); } return Path.ChangeExtension(path, ".json"); case ContentType.TableOfContents: diff --git a/test/docfx.Test/build/DocumentTest.cs b/test/docfx.Test/build/DocumentTest.cs index 45635493c4a..b3ebf22fc27 100644 --- a/test/docfx.Test/build/DocumentTest.cs +++ b/test/docfx.Test/build/DocumentTest.cs @@ -29,7 +29,7 @@ internal static void FilePathToUrl( string expectedRelativeSiteUrl) { Assert.Equal(expectedContentType, Document.GetContentType(path)); - Assert.Equal(expectedSitePath, Document.FilePathToSitePath(path, expectedContentType, null)); + Assert.Equal(expectedSitePath, Document.FilePathToSitePath(path, expectedContentType, null, json: true)); Assert.Equal(expectedSiteUrl, Document.PathToAbsoluteUrl(expectedSitePath, expectedContentType, null)); Assert.Equal(expectedRelativeSiteUrl, Document.PathToRelativeUrl(expectedSitePath, expectedContentType, null)); } diff --git a/test/docfx.Test/docfx.test.yml b/test/docfx.Test/docfx.test.yml index b830798752f..b7fd43f8fdd 100644 --- a/test/docfx.Test/docfx.test.yml +++ b/test/docfx.Test/docfx.test.yml @@ -3,3 +3,5 @@ rules: heading-not-found: off resolve-author-failed: off resolve-commit-failed: off +output: + json: true diff --git a/test/docfx.Test/e2e/E2ETest.cs b/test/docfx.Test/e2e/E2ETest.cs index 523937ce17c..5d08c461209 100644 --- a/test/docfx.Test/e2e/E2ETest.cs +++ b/test/docfx.Test/e2e/E2ETest.cs @@ -174,7 +174,7 @@ private static IEnumerable FindTestSpecHeadersInFile(string path) private static void VerifyFile(string file, string content) { - switch (Path.GetExtension(file.ToLower())) + switch (Path.GetExtension(file.ToLowerInvariant())) { case ".json": case ".manifest": @@ -196,6 +196,14 @@ private static void VerifyFile(string file, string content) Assert.Equal(string.Join("\n", expected), string.Join("\n", actual)); } break; + case ".html": + if (!string.IsNullOrEmpty(content)) + { + Assert.Equal( + TestUtility.NormalizeHtml(content), + TestUtility.NormalizeHtml(File.ReadAllText(file))); + } + break; default: Assert.Equal( content?.Trim() ?? "", From cd1576d68eea46194dd0a6bb49d042287528b659 Mon Sep 17 00:00:00 2001 From: Yufei Huang Date: Wed, 12 Sep 2018 14:23:23 +0800 Subject: [PATCH 2/9] seal --- src/Microsoft.Docs.Template/Models/Conceptual.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Docs.Template/Models/Conceptual.cs b/src/Microsoft.Docs.Template/Models/Conceptual.cs index 2e01cdf4878..ac40ffc8ab6 100644 --- a/src/Microsoft.Docs.Template/Models/Conceptual.cs +++ b/src/Microsoft.Docs.Template/Models/Conceptual.cs @@ -8,7 +8,7 @@ namespace Microsoft.Docs.Build // We currently bake these other properties into PageModel to make the output flat, // but it makes conceptual kinda special. We may consider lift them outside PageModel. [PageSchema] - public class Conceptual + public sealed class Conceptual { public string Html { get; set; } From 92acb20844fbc263221655220e9c8e6b6dd1f852 Mon Sep 17 00:00:00 2001 From: Yufei Huang Date: Wed, 12 Sep 2018 14:26:27 +0800 Subject: [PATCH 3/9] conceptual schema --- schemas/Conceptual.json | 18 ++++++++++++++++++ schemas/docfx.json | 3 +++ 2 files changed, 21 insertions(+) create mode 100644 schemas/Conceptual.json diff --git a/schemas/Conceptual.json b/schemas/Conceptual.json new file mode 100644 index 00000000000..a48d4654155 --- /dev/null +++ b/schemas/Conceptual.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "html": { + "type": "string" + }, + "title": { + "type": "string" + }, + "htmlTitle": { + "type": "string" + }, + "wordCount": { + "type": "integer" + } + } +} \ No newline at end of file diff --git a/schemas/docfx.json b/schemas/docfx.json index 82bf409f997..082c0155b4a 100644 --- a/schemas/docfx.json +++ b/schemas/docfx.json @@ -102,6 +102,9 @@ "path": { "type": "string" }, + "json": { + "type": "boolean" + }, "copyResources": { "type": "boolean" } From 7552a8507f3bbf9a736355d39f15299be67c3ebb Mon Sep 17 00:00:00 2001 From: Yufei Huang Date: Wed, 12 Sep 2018 14:49:59 +0800 Subject: [PATCH 4/9] Support pretty and uglify url --- docs/specs/rendering.yml | 12 ++++++++++ src/docfx/config/OutputConfig.cs | 6 +++++ src/docfx/docset/Document.cs | 16 +++++++++---- src/docfx/lib/JsonUtility.cs | 10 ++++---- test/docfx.Test/build/DocumentTest.cs | 34 ++++++++++++++++----------- test/docfx.Test/build/TocTest.cs | 12 ++++++---- 6 files changed, 63 insertions(+), 27 deletions(-) diff --git a/docs/specs/rendering.yml b/docs/specs/rendering.yml index 5591d97d85c..86e998640be 100644 --- a/docs/specs/rendering.yml +++ b/docs/specs/rendering.yml @@ -1,9 +1,21 @@ +--- # Apply template and produce html page when output is not json inputs: docfx.yml: | output: json: false docs/a.md: +outputs: + docs/a/index.html: + build.manifest: +--- +# Use .html extension on uglify url +inputs: + docfx.yml: | + output: + json: false + uglifyUrl: true + docs/a.md: outputs: docs/a.html: build.manifest: diff --git a/src/docfx/config/OutputConfig.cs b/src/docfx/config/OutputConfig.cs index 6a90440074c..00653a1e967 100644 --- a/src/docfx/config/OutputConfig.cs +++ b/src/docfx/config/OutputConfig.cs @@ -15,6 +15,12 @@ internal sealed class OutputConfig /// public readonly bool Json = false; + /// + /// Gets whether to include `.html` in urls. + /// The default value is to generate an `index.html` for each article. + /// + public readonly bool UglifyUrl = false; + /// /// Gets whether resources are copied to output. /// diff --git a/src/docfx/docset/Document.cs b/src/docfx/docset/Document.cs index 063a8c8db6f..cc30238150f 100644 --- a/src/docfx/docset/Document.cs +++ b/src/docfx/docset/Document.cs @@ -214,7 +214,7 @@ public static (Error error, Document doc) TryCreate(Docset docset, string path, var isExperimental = Path.GetFileNameWithoutExtension(filePath).EndsWith(".experimental", PathUtility.PathComparison); var routedFilePath = ApplyRoutes(filePath, docset.Config.Routes); - var sitePath = FilePathToSitePath(routedFilePath, type, schema, docset.Config.Output.Json); + var sitePath = FilePathToSitePath(routedFilePath, type, schema, docset.Config.Output.Json, docset.Config.Output.UglifyUrl); var siteUrl = PathToAbsoluteUrl(sitePath, type, schema); var outputPath = sitePath; var contentType = redirectionUrl != null ? ContentType.Redirection : type; @@ -293,19 +293,27 @@ internal static ContentType GetContentType(string path) return ContentType.Page; } - internal static string FilePathToSitePath(string path, ContentType contentType, Schema schema, bool json) + internal static string FilePathToSitePath(string path, ContentType contentType, Schema schema, bool json, bool uglifyUrl) { switch (contentType) { case ContentType.Page: if (schema == null || schema.Attribute is PageSchemaAttribute) { - var extension = json ? ".json" : ".html"; if (Path.GetFileNameWithoutExtension(path).Equals("index", PathUtility.PathComparison)) { + var extension = json ? ".json" : ".html"; return Path.Combine(Path.GetDirectoryName(path), "index" + extension).Replace('\\', '/'); } - return Path.ChangeExtension(path, extension); + if (json) + { + return Path.ChangeExtension(path, ".json"); + } + if (uglifyUrl) + { + return Path.ChangeExtension(path, ".html"); + } + return Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path), "index.html").Replace('\\', '/'); } return Path.ChangeExtension(path, ".json"); case ContentType.TableOfContents: diff --git a/src/docfx/lib/JsonUtility.cs b/src/docfx/lib/JsonUtility.cs index 063f2d2d263..91623aa0425 100644 --- a/src/docfx/lib/JsonUtility.cs +++ b/src/docfx/lib/JsonUtility.cs @@ -20,7 +20,7 @@ namespace Microsoft.Docs.Build /// internal static class JsonUtility { - public static readonly JsonSerializer DefaultDeserializer = new JsonSerializer + public static readonly JsonSerializer DefaultSerializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore, ContractResolver = new JsonContractResolver(), @@ -182,7 +182,7 @@ public static (List, object) ToObject( var serializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore, - ContractResolver = DefaultDeserializer.ContractResolver, + ContractResolver = DefaultSerializer.ContractResolver, }; serializer.Error += HandleError; var value = token.ToObject(type, serializer); @@ -349,7 +349,7 @@ private static void ReportUnknownFields(this JToken token, List errors, T private static Type GetCollectionItemTypeIfArrayType(Type type) { - var contract = DefaultDeserializer.ContractResolver.ResolveContract(type); + var contract = DefaultSerializer.ContractResolver.ResolveContract(type); if (contract is JsonObjectContract) { return type; @@ -371,7 +371,7 @@ private static Type GetCollectionItemTypeIfArrayType(Type type) private static Type GetNestedTypeAndCheckForUnknownField(Type type, JProperty prop, List errors) { - var contract = DefaultDeserializer.ContractResolver.ResolveContract(type); + var contract = DefaultSerializer.ContractResolver.ResolveContract(type); if (contract is JsonObjectContract objectContract) { @@ -396,7 +396,7 @@ private static Type GetNestedTypeAndCheckForUnknownField(Type type, JProperty pr private static JsonPropertyCollection GetPropertiesFromJsonArrayContract(JsonArrayContract arrayContract) { - var itemContract = DefaultDeserializer.ContractResolver.ResolveContract(arrayContract.CollectionItemType); + var itemContract = DefaultSerializer.ContractResolver.ResolveContract(arrayContract.CollectionItemType); if (itemContract is JsonObjectContract objectContract) return objectContract.Properties; else if (itemContract is JsonArrayContract contract) diff --git a/test/docfx.Test/build/DocumentTest.cs b/test/docfx.Test/build/DocumentTest.cs index b3ebf22fc27..8657b681bc6 100644 --- a/test/docfx.Test/build/DocumentTest.cs +++ b/test/docfx.Test/build/DocumentTest.cs @@ -8,28 +8,34 @@ namespace Microsoft.Docs.Build public static class DocumentTest { [Theory] - [InlineData("docfx.yml", ContentType.Unknown, "docfx.yml", "/docfx.yml", "docfx.yml")] - [InlineData("docfx.json", ContentType.Unknown, "docfx.json", "/docfx.json", "docfx.json")] - [InlineData("a.md", ContentType.Page, "a.json", "/a", "a")] - [InlineData("a/b.md", ContentType.Page, "a/b.json", "/a/b", "a/b")] - [InlineData("index.md", ContentType.Page, "index.json", "/", ".")] - [InlineData("a/index.md", ContentType.Page, "a/index.json", "/a/", "a/")] - [InlineData("a.yml", ContentType.Page, "a.json", "/a", "a")] - [InlineData("a/index.yml", ContentType.Page, "a/index.json", "/a/", "a/")] - [InlineData("TOC.md", ContentType.TableOfContents, "TOC.json", "/TOC.json", "TOC.json")] - [InlineData("TOC.yml", ContentType.TableOfContents, "TOC.json", "/TOC.json", "TOC.json")] - [InlineData("TOC.json", ContentType.TableOfContents, "TOC.json", "/TOC.json", "TOC.json")] - [InlineData("image.png", ContentType.Resource, "image.png", "/image.png", "image.png")] - [InlineData("a&#/b\\.* d.png", ContentType.Resource, "a&#/b\\.* d.png", "/a&#/b/.* d.png", "a&#/b/.* d.png")] + [InlineData("docfx.yml", true, false, ContentType.Unknown, "docfx.yml", "/docfx.yml", "docfx.yml")] + [InlineData("docfx.json", true, false, ContentType.Unknown, "docfx.json", "/docfx.json", "docfx.json")] + [InlineData("a.md", true, false, ContentType.Page, "a.json", "/a", "a")] + [InlineData("a/b.md", true, false, ContentType.Page, "a/b.json", "/a/b", "a/b")] + [InlineData("index.md", true, false, ContentType.Page, "index.json", "/", ".")] + [InlineData("a/index.md", true, false, ContentType.Page, "a/index.json", "/a/", "a/")] + [InlineData("a.yml", true, false, ContentType.Page, "a.json", "/a", "a")] + [InlineData("a/index.yml", true, false, ContentType.Page, "a/index.json", "/a/", "a/")] + [InlineData("TOC.md", true, false, ContentType.TableOfContents, "TOC.json", "/TOC.json", "TOC.json")] + [InlineData("TOC.yml", true, false, ContentType.TableOfContents, "TOC.json", "/TOC.json", "TOC.json")] + [InlineData("TOC.json", true, false, ContentType.TableOfContents, "TOC.json", "/TOC.json", "TOC.json")] + [InlineData("image.png", true, false, ContentType.Resource, "image.png", "/image.png", "image.png")] + [InlineData("a&#/b\\.* d.png", true, false, ContentType.Resource, "a&#/b\\.* d.png", "/a&#/b/.* d.png", "a&#/b/.* d.png")] + [InlineData("a.md", false, false, ContentType.Page, "a/index.html", "/a/", "a/")] + [InlineData("a.md", false, true, ContentType.Page, "a.html", "/a", "a")] + [InlineData("a/index.md", false, false, ContentType.Page, "a/index.html", "/a/", "a/")] + [InlineData("a/index.md", false, true, ContentType.Page, "a/index.html", "/a/", "a/")] internal static void FilePathToUrl( string path, + bool json, + bool uglifyUrl, ContentType expectedContentType, string expectedSitePath, string expectedSiteUrl, string expectedRelativeSiteUrl) { Assert.Equal(expectedContentType, Document.GetContentType(path)); - Assert.Equal(expectedSitePath, Document.FilePathToSitePath(path, expectedContentType, null, json: true)); + Assert.Equal(expectedSitePath, Document.FilePathToSitePath(path, expectedContentType, null, json, uglifyUrl)); Assert.Equal(expectedSiteUrl, Document.PathToAbsoluteUrl(expectedSitePath, expectedContentType, null)); Assert.Equal(expectedRelativeSiteUrl, Document.PathToRelativeUrl(expectedSitePath, expectedContentType, null)); } diff --git a/test/docfx.Test/build/TocTest.cs b/test/docfx.Test/build/TocTest.cs index 11cd12dfdef..50bd70d03b3 100644 --- a/test/docfx.Test/build/TocTest.cs +++ b/test/docfx.Test/build/TocTest.cs @@ -3,13 +3,18 @@ using System; using System.IO; -using Newtonsoft.Json.Linq; using Xunit; namespace Microsoft.Docs.Build { public static class TocTest { + private static readonly Docset s_docset = new Docset( + new Context(new Report(), "."), + Directory.GetCurrentDirectory(), + JsonUtility.Deserialize("{'output': { 'json': true } }".Replace('\'', '\"')).Item2, + new CommandLineOptions()); + [Theory] // same level [InlineData(new[] { "TOC.md" }, "b.md", "TOC.json")] @@ -39,12 +44,11 @@ public static class TocTest public static void FindTocRelativePath(string[] tocFiles, string file, string expectedTocPath) { var builder = new TableOfContentsMapBuilder(); - var docset = new Docset(new Context(new Report(), "."), Directory.GetCurrentDirectory(), new Config(), new CommandLineOptions()); - var (_, document) = Document.TryCreate(docset, file); + var (_, document) = Document.TryCreate(s_docset, file); foreach (var tocFile in tocFiles) { - var (_, toc) = Document.TryCreate(docset, tocFile); + var (_, toc) = Document.TryCreate(s_docset, tocFile); builder.Add(toc, new[] { document }, Array.Empty()); } From cf49cd2f994f9a9af6ce523c19f7e49f6d3688eb Mon Sep 17 00:00:00 2001 From: Yufei Huang Date: Wed, 12 Sep 2018 14:51:15 +0800 Subject: [PATCH 5/9] schema --- schemas/docfx.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/schemas/docfx.json b/schemas/docfx.json index 082c0155b4a..6a34f828a56 100644 --- a/schemas/docfx.json +++ b/schemas/docfx.json @@ -105,6 +105,9 @@ "json": { "type": "boolean" }, + "uglifyUrl": { + "type": "boolean" + }, "copyResources": { "type": "boolean" } From 7121f1ab709763946b0a9a1ce6fdabedea2472bb Mon Sep 17 00:00:00 2001 From: Yufei Huang Date: Wed, 12 Sep 2018 15:29:01 +0800 Subject: [PATCH 6/9] Address PR feedback --- src/docfx/build/resolve/Resolve.cs | 6 ++++-- src/docfx/config/OutputConfig.cs | 5 +++-- src/docfx/docset/Document.cs | 25 ++++++++++++------------- test/docfx.Test/build/DocumentTest.cs | 6 +++--- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/docfx/build/resolve/Resolve.cs b/src/docfx/build/resolve/Resolve.cs index 5eb80c02bb2..658d5ff8d0d 100644 --- a/src/docfx/build/resolve/Resolve.cs +++ b/src/docfx/build/resolve/Resolve.cs @@ -49,7 +49,8 @@ public static (Error error, string href, string fragment, Document file) TryReso { return (error, query + fragment, fragment, null); } - var selfUrl = HrefUtility.EscapeUrl(Document.PathToRelativeUrl(Path.GetFileName(file.SitePath), file.ContentType, file.Schema)); + var selfUrl = HrefUtility.EscapeUrl(Document.PathToRelativeUrl( + Path.GetFileName(file.SitePath), file.ContentType, file.Schema, file.Docset.Config.Output.Json)); return (error, selfUrl + query + fragment, fragment, null); } if (string.IsNullOrEmpty(fragment)) @@ -67,7 +68,8 @@ public static (Error error, string href, string fragment, Document file) TryReso // Make result relative to `resultRelativeTo` var relativePath = PathUtility.GetRelativePathToFile(resultRelativeTo.SitePath, file.SitePath); - var relativeUrl = HrefUtility.EscapeUrl(Document.PathToRelativeUrl(relativePath, file.ContentType, file.Schema)); + var relativeUrl = HrefUtility.EscapeUrl(Document.PathToRelativeUrl( + relativePath, file.ContentType, file.Schema, file.Docset.Config.Output.Json)); if (redirectTo != null) { diff --git a/src/docfx/config/OutputConfig.cs b/src/docfx/config/OutputConfig.cs index 00653a1e967..bf37ca1c811 100644 --- a/src/docfx/config/OutputConfig.cs +++ b/src/docfx/config/OutputConfig.cs @@ -16,8 +16,9 @@ internal sealed class OutputConfig public readonly bool Json = false; /// - /// Gets whether to include `.html` in urls. - /// The default value is to generate an `index.html` for each article. + /// Gets whether to use ugly url or pretty url when is set to true. + /// - Pretty url: a.md --> a/index.html + /// - Ugly url: a.md --> a.html /// public readonly bool UglifyUrl = false; diff --git a/src/docfx/docset/Document.cs b/src/docfx/docset/Document.cs index cc30238150f..3ab2dd3b0a8 100644 --- a/src/docfx/docset/Document.cs +++ b/src/docfx/docset/Document.cs @@ -215,7 +215,7 @@ public static (Error error, Document doc) TryCreate(Docset docset, string path, var routedFilePath = ApplyRoutes(filePath, docset.Config.Routes); var sitePath = FilePathToSitePath(routedFilePath, type, schema, docset.Config.Output.Json, docset.Config.Output.UglifyUrl); - var siteUrl = PathToAbsoluteUrl(sitePath, type, schema); + var siteUrl = PathToAbsoluteUrl(sitePath, type, schema, docset.Config.Output.Json); var outputPath = sitePath; var contentType = redirectionUrl != null ? ContentType.Redirection : type; @@ -323,13 +323,13 @@ internal static string FilePathToSitePath(string path, ContentType contentType, } } - internal static string PathToAbsoluteUrl(string path, ContentType contentType, Schema schema) + internal static string PathToAbsoluteUrl(string path, ContentType contentType, Schema schema, bool json) { - var url = PathToRelativeUrl(path, contentType, schema); + var url = PathToRelativeUrl(path, contentType, schema, json); return url == "." ? "/" : "/" + url; } - internal static string PathToRelativeUrl(string path, ContentType contentType, Schema schema) + internal static string PathToRelativeUrl(string path, ContentType contentType, Schema schema, bool json) { var url = path.Replace('\\', '/'); @@ -338,19 +338,18 @@ internal static string PathToRelativeUrl(string path, ContentType contentType, S case ContentType.Page: if (schema == null || schema.Attribute is PageSchemaAttribute) { - var extensionIndex = url.LastIndexOf('.'); - if (extensionIndex >= 0) + var fileName = Path.GetFileNameWithoutExtension(path); + if (fileName.Equals("index", PathUtility.PathComparison)) { - url = url.Substring(0, extensionIndex); + var i = url.LastIndexOf('/'); + return i >= 0 ? url.Substring(0, i + 1) : "."; } - if (url.Equals("index", PathUtility.PathComparison)) - { - return "."; - } - if (url.EndsWith("/index", PathUtility.PathComparison)) + if (json) { - return url.Substring(0, url.Length - 5); + var i = url.LastIndexOf('.'); + return i >= 0 ? url.Substring(0, i) : url; } + return url; } return url; default: diff --git a/test/docfx.Test/build/DocumentTest.cs b/test/docfx.Test/build/DocumentTest.cs index 8657b681bc6..83f41783912 100644 --- a/test/docfx.Test/build/DocumentTest.cs +++ b/test/docfx.Test/build/DocumentTest.cs @@ -22,7 +22,7 @@ public static class DocumentTest [InlineData("image.png", true, false, ContentType.Resource, "image.png", "/image.png", "image.png")] [InlineData("a&#/b\\.* d.png", true, false, ContentType.Resource, "a&#/b\\.* d.png", "/a&#/b/.* d.png", "a&#/b/.* d.png")] [InlineData("a.md", false, false, ContentType.Page, "a/index.html", "/a/", "a/")] - [InlineData("a.md", false, true, ContentType.Page, "a.html", "/a", "a")] + [InlineData("a.md", false, true, ContentType.Page, "a.html", "/a.html", "a.html")] [InlineData("a/index.md", false, false, ContentType.Page, "a/index.html", "/a/", "a/")] [InlineData("a/index.md", false, true, ContentType.Page, "a/index.html", "/a/", "a/")] internal static void FilePathToUrl( @@ -36,8 +36,8 @@ internal static void FilePathToUrl( { Assert.Equal(expectedContentType, Document.GetContentType(path)); Assert.Equal(expectedSitePath, Document.FilePathToSitePath(path, expectedContentType, null, json, uglifyUrl)); - Assert.Equal(expectedSiteUrl, Document.PathToAbsoluteUrl(expectedSitePath, expectedContentType, null)); - Assert.Equal(expectedRelativeSiteUrl, Document.PathToRelativeUrl(expectedSitePath, expectedContentType, null)); + Assert.Equal(expectedSiteUrl, Document.PathToAbsoluteUrl(expectedSitePath, expectedContentType, null, json)); + Assert.Equal(expectedRelativeSiteUrl, Document.PathToRelativeUrl(expectedSitePath, expectedContentType, null, json)); } } } From cd9f6b544ec08f172864ece5f65582c55c8d06ad Mon Sep 17 00:00:00 2001 From: Yufei Huang Date: Wed, 12 Sep 2018 15:43:05 +0800 Subject: [PATCH 7/9] Write string --- src/docfx/build/Build.cs | 6 +++++- src/docfx/docset/Context.cs | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/docfx/build/Build.cs b/src/docfx/build/Build.cs index 82379e0e83a..fc44bba8691 100644 --- a/src/docfx/build/Build.cs +++ b/src/docfx/build/Build.cs @@ -127,7 +127,11 @@ void ValidateBookmarks() var hasErrors = context.Report(file.ToString(), errors); if (model != null && !hasErrors) { - context.WriteJson(model, file.OutputPath); + if (model is string str) + context.WriteText(str, file.OutputPath); + else + context.WriteJson(model, file.OutputPath); + return (false, dependencies); } diff --git a/src/docfx/docset/Context.cs b/src/docfx/docset/Context.cs index e2ead250505..6c471f066c7 100644 --- a/src/docfx/docset/Context.cs +++ b/src/docfx/docset/Context.cs @@ -70,6 +70,21 @@ public void WriteJson(object graph, string destRelativePath) } } + /// + /// Writes the input text to an output file. + /// Throws if multiple threads trying to write to the same destination concurrently. + /// + public void WriteText(string contents, string destRelativePath) + { + Debug.Assert(!Path.IsPathRooted(destRelativePath)); + + var destinationPath = Path.Combine(_outputPath, destRelativePath); + + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); + + File.WriteAllText(destinationPath, contents); + } + /// /// Copies a file from source to destination, throws if source does not exists. /// Throws if multiple threads trying to write to the same destination concurrently. From 072d5532d74a69b9e5b27f9b62e79ebb89710a39 Mon Sep 17 00:00:00 2001 From: Yufei Huang Date: Wed, 12 Sep 2018 15:45:47 +0800 Subject: [PATCH 8/9] minor changes --- src/docfx/config/OutputConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docfx/config/OutputConfig.cs b/src/docfx/config/OutputConfig.cs index bf37ca1c811..9505d3f5ab0 100644 --- a/src/docfx/config/OutputConfig.cs +++ b/src/docfx/config/OutputConfig.cs @@ -16,7 +16,7 @@ internal sealed class OutputConfig public readonly bool Json = false; /// - /// Gets whether to use ugly url or pretty url when is set to true. + /// Gets whether to use ugly url or pretty url when is set to false. /// - Pretty url: a.md --> a/index.html /// - Ugly url: a.md --> a.html /// From 7bb7582c3d92f44b9fab409790ca6c754b848831 Mon Sep 17 00:00:00 2001 From: Yufei Huang Date: Wed, 12 Sep 2018 15:49:55 +0800 Subject: [PATCH 9/9] Add more URL UT --- test/docfx.Test/build/DocumentTest.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/docfx.Test/build/DocumentTest.cs b/test/docfx.Test/build/DocumentTest.cs index 83f41783912..b2483f3a529 100644 --- a/test/docfx.Test/build/DocumentTest.cs +++ b/test/docfx.Test/build/DocumentTest.cs @@ -12,7 +12,6 @@ public static class DocumentTest [InlineData("docfx.json", true, false, ContentType.Unknown, "docfx.json", "/docfx.json", "docfx.json")] [InlineData("a.md", true, false, ContentType.Page, "a.json", "/a", "a")] [InlineData("a/b.md", true, false, ContentType.Page, "a/b.json", "/a/b", "a/b")] - [InlineData("index.md", true, false, ContentType.Page, "index.json", "/", ".")] [InlineData("a/index.md", true, false, ContentType.Page, "a/index.json", "/a/", "a/")] [InlineData("a.yml", true, false, ContentType.Page, "a.json", "/a", "a")] [InlineData("a/index.yml", true, false, ContentType.Page, "a/index.json", "/a/", "a/")] @@ -25,6 +24,9 @@ public static class DocumentTest [InlineData("a.md", false, true, ContentType.Page, "a.html", "/a.html", "a.html")] [InlineData("a/index.md", false, false, ContentType.Page, "a/index.html", "/a/", "a/")] [InlineData("a/index.md", false, true, ContentType.Page, "a/index.html", "/a/", "a/")] + [InlineData("index.md", false, false, ContentType.Page, "index.html", "/", ".")] + [InlineData("index.md", false, true, ContentType.Page, "index.html", "/", ".")] + [InlineData("index.md", true, false, ContentType.Page, "index.json", "/", ".")] internal static void FilePathToUrl( string path, bool json,