Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 102 additions & 2 deletions src/Microsoft.DocAsCode.App/RunServe.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.DocAsCode.Common;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.DocAsCode.Plugins;

namespace Microsoft.DocAsCode;

internal static class RunServe
{
public static void Exec(string folder, string host, int? port)
public static void Exec(string folder, string host, int? port, bool openBrowser, string openFile)
{
if (string.IsNullOrEmpty(folder))
folder = Directory.GetCurrentDirectory();
Expand Down Expand Up @@ -43,11 +47,107 @@ public static void Exec(string folder, string host, int? port)
Console.WriteLine($"Serving \"{folder}\" on {url}. Press Ctrl+C to shut down.");
using var app = builder.Build();
app.UseFileServer(fileServerOptions);
app.Run();

if (openBrowser || !string.IsNullOrEmpty(openFile))
{
string relativePath = openFile;
var launchUrl = string.IsNullOrEmpty(relativePath)
? url
: ResolveOutputHtmlRelativePath(baseUrl: url, folder, relativePath);

// Start web server.
app.Start();

// Launch browser process.
Console.WriteLine($"Launching browser with url: {launchUrl}.");
LaunchBrowser(launchUrl);

// Wait until server exited.
app.WaitForShutdown();
}
else
{
app.Run();
}
}
catch (System.Reflection.TargetInvocationException)
{
Logger.LogError($"Error serving \"{folder}\" on {url}, check if the port is already being in use.");
}
}

/// <summary>
/// Resolve output HTML file path by `manifest.json` file.
/// If failed to resolve path. return baseUrl.
/// </summary>
private static string ResolveOutputHtmlRelativePath(string baseUrl, string folder, string relativePath)
{
var manifestPath = Path.GetFullPath(Path.Combine(folder, "manifest.json"));
if (!File.Exists(manifestPath))
return baseUrl;

try
{
Manifest manifest = JsonUtility.Deserialize<Manifest>(manifestPath);

// Try to find output html file (html->html)
OutputFileInfo outputFileInfo = manifest.FindOutputFileInfo(relativePath);
if (outputFileInfo != null)
return outputFileInfo.RelativePath;

// Try to resolve output HTML file. (md->html)
relativePath = relativePath.Replace('\\', '/'); // Normalize path.
var manifestFile = manifest.Files
.Where(x => FilePathComparer.OSPlatformSensitiveRelativePathComparer.Equals(x.SourceRelativePath, relativePath))
.FirstOrDefault(x => x.OutputFiles.TryGetValue(".html", out outputFileInfo));

if (outputFileInfo != null)
{
var baseUri = new Uri(baseUrl);
return new Uri(baseUri, relativeUri: outputFileInfo.RelativePath).ToString();
}
}
catch (Exception ex)
{
Logger.LogError($"Failed to resolve output HTML file by exception. file - {relativePath} with error - {ex.Message}");
return baseUrl;
}

// Failed to resolve output HTML file.
Logger.LogError($"Failed to resolve output HTML file. file - {relativePath}");
return baseUrl;
}

private static void LaunchBrowser(string url)
{
try
{
// Windows
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Process.Start("cmd", new[] { "/C", "start", url });
return;
}

// Linux
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url);
return;
}

// OSX
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
return;
}

Logger.LogError($"Could not launch the browser process. Unknown OS platform: {RuntimeInformation.OSDescription}");
}
catch (Exception ex)
{
Logger.LogError($"Could not launch the browser process. with error - {ex.Message}");
}
}
}
2 changes: 1 addition & 1 deletion src/docfx/Models/BuildCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public override int Execute(CommandContext context, BuildCommandOptions settings
var serveDirectory = RunBuild.Exec(config.Item, new(), baseDirectory, settings.OutputFolder);

if (settings.Serve)
RunServe.Exec(serveDirectory, settings.Host, settings.Port);
RunServe.Exec(serveDirectory, settings.Host, settings.Port, settings.OpenBrowser, settings.OpenFile);
});
}

Expand Down
8 changes: 8 additions & 0 deletions src/docfx/Models/BuildCommandOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ internal class BuildCommandOptions : LogOptions
[CommandOption("-p|--port")]
public int? Port { get; set; }

[Description("Open a web browser when the hosted website starts.")]
[CommandOption("--open-browser")]
public bool OpenBrowser { get; set; }

[Description("Open a file in a web browser When the hosted website starts,")]
[CommandOption("--open-file <RELATIVE_PATH>")]
public string OpenFile { get; set; }

[Description("Run in debug mode. With debug mode, raw model and view model will be exported automatically when it encounters error when applying templates. If not specified, it is false.")]
[CommandOption("--debug")]
public bool EnableDebugMode { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/docfx/Models/DefaultCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public override int Execute(CommandContext context, Options options)

if (options.Serve && serveDirectory is not null)
{
RunServe.Exec(serveDirectory, options.Host, options.Port);
RunServe.Exec(serveDirectory, options.Host, options.Port, options.OpenBrowser, options.OpenFile);
}
});
}
Expand Down
10 changes: 9 additions & 1 deletion src/docfx/Models/ServeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,18 @@ internal class Settings : CommandSettings
[Description("Specify the port of the hosted website")]
[CommandOption("-p|--port")]
public int? Port { get; set; }

[Description("Open a web browser when the hosted website starts.")]
[CommandOption("--open-browser")]
public bool OpenBrowser { get; set; }

[Description("Open a file in a web browser When the hosted website starts,")]
[CommandOption("--open-file <RELATIVE_PATH>")]
public string OpenFile { get; set; }
}

public override int Execute([NotNull] CommandContext context, [NotNull] Settings options)
{
return CommandHelper.Run(() => RunServe.Exec(options.Folder, options.Host, options.Port));
return CommandHelper.Run(() => RunServe.Exec(options.Folder, options.Host, options.Port, options.OpenBrowser, options.OpenFile));
}
}