Skip to content

Commit

Permalink
Add basic static rendering (#3411)
Browse files Browse the repository at this point in the history
  • Loading branch information
yufeih authored Sep 12, 2018
1 parent 3643c9e commit fd1578d
Show file tree
Hide file tree
Showing 18 changed files with 169 additions and 51 deletions.
21 changes: 21 additions & 0 deletions docs/specs/rendering.yml
Original file line number Diff line number Diff line change
@@ -0,0 +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:
18 changes: 18 additions & 0 deletions schemas/Conceptual.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"html": {
"type": "string"
},
"title": {
"type": "string"
},
"htmlTitle": {
"type": "string"
},
"wordCount": {
"type": "integer"
}
}
}
6 changes: 6 additions & 0 deletions schemas/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@
"path": {
"type": "string"
},
"json": {
"type": "boolean"
},
"uglifyUrl": {
"type": "boolean"
},
"copyResources": {
"type": "boolean"
}
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.Docs.Template/Models/Conceptual.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ 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.
public class Conceptual
[PageSchema]
public sealed class Conceptual
{
public string Html { get; set; }

Expand Down
Empty file.
6 changes: 5 additions & 1 deletion src/docfx/build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
10 changes: 8 additions & 2 deletions src/docfx/build/page/BuildPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.Docs.Build
{
internal static class BuildPage
{
public static async Task<(IEnumerable<Error> errors, PageModel result, DependencyMap dependencies)> Build(
public static async Task<(IEnumerable<Error> errors, object result, DependencyMap dependencies)> Build(
Document file,
TableOfContentsMap tocMap,
ContributionInfo contribution,
Expand Down Expand Up @@ -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<Error> errors, Schema schema, PageModel model)>
Expand Down
6 changes: 4 additions & 2 deletions src/docfx/build/resolve/Resolve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)
{
Expand Down
1 change: 1 addition & 0 deletions src/docfx/cli/CommandLineOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public JObject ToJObject()
["output"] = new JObject
{
["path"] = Output != null ? (JValue)Output : JValue.CreateNull(),
["json"] = Legacy ? (JValue)true : JValue.CreateNull(),
},
};
}
Expand Down
7 changes: 4 additions & 3 deletions src/docfx/config/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,15 @@ private static (List<Error>, Config) LoadCore(string docsetPath, string configPa

if (extend)
{
var extendErros = new List<Error>();
(extendErros, finalConfigObject) = ExtendConfigs(finalConfigObject, docsetPath, restoreMap ?? new RestoreMap(docsetPath));
errors.AddRange(extendErros);
var extendErrors = new List<Error>();
(extendErrors, finalConfigObject) = ExtendConfigs(finalConfigObject, docsetPath, restoreMap ?? new RestoreMap(docsetPath));
errors.AddRange(extendErrors);
}

var deserializeErrors = new List<Error>();
(deserializeErrors, config) = JsonUtility.ToObject<Config>(finalConfigObject);
errors.AddRange(deserializeErrors);

return (errors, config);
}

Expand Down
12 changes: 12 additions & 0 deletions src/docfx/config/OutputConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ internal sealed class OutputConfig
/// </summary>
public readonly string Path = "_site";

/// <summary>
/// Gets whether to output JSON model.
/// </summary>
public readonly bool Json = false;

/// <summary>
/// Gets whether to use ugly url or pretty url when <see cref="Json"/> is set to false.
/// - Pretty url: a.md --> a/index.html
/// - Ugly url: a.md --> a.html
/// </summary>
public readonly bool UglifyUrl = false;

/// <summary>
/// Gets whether resources are copied to output.
/// </summary>
Expand Down
15 changes: 15 additions & 0 deletions src/docfx/docset/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ public void WriteJson(object graph, string destRelativePath)
}
}

/// <summary>
/// Writes the input text to an output file.
/// Throws if multiple threads trying to write to the same destination concurrently.
/// </summary>
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);
}

/// <summary>
/// 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.
Expand Down
41 changes: 25 additions & 16 deletions src/docfx/docset/Document.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ 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 siteUrl = PathToAbsoluteUrl(sitePath, type, schema);
var sitePath = FilePathToSitePath(routedFilePath, type, schema, docset.Config.Output.Json, docset.Config.Output.UglifyUrl);
var siteUrl = PathToAbsoluteUrl(sitePath, type, schema, docset.Config.Output.Json);
var outputPath = sitePath;
var contentType = redirectionUrl != null ? ContentType.Redirection : type;

Expand Down Expand Up @@ -293,7 +293,7 @@ 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, bool uglifyUrl)
{
switch (contentType)
{
Expand All @@ -302,8 +302,18 @@ internal static string FilePathToSitePath(string path, ContentType contentType,
{
if (Path.GetFileNameWithoutExtension(path).Equals("index", PathUtility.PathComparison))
{
return Path.Combine(Path.GetDirectoryName(path), "index.json").Replace('\\', '/');
var extension = json ? ".json" : ".html";
return Path.Combine(Path.GetDirectoryName(path), "index" + extension).Replace('\\', '/');
}
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:
Expand All @@ -313,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('\\', '/');

Expand All @@ -328,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)
{
url = url.Substring(0, extensionIndex);
}
if (url.Equals("index", PathUtility.PathComparison))
var fileName = Path.GetFileNameWithoutExtension(path);
if (fileName.Equals("index", PathUtility.PathComparison))
{
return ".";
var i = url.LastIndexOf('/');
return i >= 0 ? url.Substring(0, i + 1) : ".";
}
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:
Expand Down
10 changes: 5 additions & 5 deletions src/docfx/lib/JsonUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace Microsoft.Docs.Build
/// </summary>
internal static class JsonUtility
{
public static readonly JsonSerializer DefaultDeserializer = new JsonSerializer
public static readonly JsonSerializer DefaultSerializer = new JsonSerializer
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new JsonContractResolver(),
Expand Down Expand Up @@ -182,7 +182,7 @@ public static (List<Error>, object) ToObject(
var serializer = new JsonSerializer
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = DefaultDeserializer.ContractResolver,
ContractResolver = DefaultSerializer.ContractResolver,
};
serializer.Error += HandleError;
var value = token.ToObject(type, serializer);
Expand Down Expand Up @@ -349,7 +349,7 @@ private static void ReportUnknownFields(this JToken token, List<Error> 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;
Expand All @@ -371,7 +371,7 @@ private static Type GetCollectionItemTypeIfArrayType(Type type)

private static Type GetNestedTypeAndCheckForUnknownField(Type type, JProperty prop, List<Error> errors)
{
var contract = DefaultDeserializer.ContractResolver.ResolveContract(type);
var contract = DefaultSerializer.ContractResolver.ResolveContract(type);

if (contract is JsonObjectContract objectContract)
{
Expand All @@ -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)
Expand Down
40 changes: 24 additions & 16 deletions test/docfx.Test/build/DocumentTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,38 @@ 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("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.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,
bool uglifyUrl,
ContentType expectedContentType,
string expectedSitePath,
string expectedSiteUrl,
string expectedRelativeSiteUrl)
{
Assert.Equal(expectedContentType, Document.GetContentType(path));
Assert.Equal(expectedSitePath, Document.FilePathToSitePath(path, expectedContentType, null));
Assert.Equal(expectedSiteUrl, Document.PathToAbsoluteUrl(expectedSitePath, expectedContentType, null));
Assert.Equal(expectedRelativeSiteUrl, Document.PathToRelativeUrl(expectedSitePath, expectedContentType, null));
Assert.Equal(expectedSitePath, Document.FilePathToSitePath(path, expectedContentType, null, json, uglifyUrl));
Assert.Equal(expectedSiteUrl, Document.PathToAbsoluteUrl(expectedSitePath, expectedContentType, null, json));
Assert.Equal(expectedRelativeSiteUrl, Document.PathToRelativeUrl(expectedSitePath, expectedContentType, null, json));
}
}
}
Loading

0 comments on commit fd1578d

Please sign in to comment.