Skip to content

Allow LibreAutomate documentation to be served locally using "docfx serve" #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,15 @@ ModelManifest.xml
*.bak
/Au/global.json
.tools/

# LibreAutomate compiliation of docs
/LA.Workspace.For.Scripts/*
!/LA.Workspace.For.Scripts/readme.txt
!/LA.Workspace.For.Scripts/files.xml
!/LA.Workspace.For.Scripts/state.db
!/LA.Workspace.For.Scripts/files/

# Documentation generation
/Other/DocFx/_doc/cookbook/*
/Other/DocPreProcessed/*
/Other/DocFxProcessed/*
13 changes: 10 additions & 3 deletions Au.Editor/App/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ AppSettings _Loaded() {
(font_recipeText ??= new()).Normalize("Calibri", 10.5);
(font_recipeCode ??= new()).Normalize("Consolas", 9);
(font_find ??= new()).Normalize("Consolas", 9);
DOptions.PushLocalDocOptionsToHelpUtils(this);
debug ??= new();
delm ??= new();
recorder ??= new();
Expand Down Expand Up @@ -98,9 +99,15 @@ public void Normalize() {
//Options > Other
public bool? comp_printCompiled = false;
public string internetSearchUrl = "https://www.google.com/search?q=";

//code editor
public bool edit_wrap, edit_noImages;

//Options > Docs
public bool enableLocalDocumentation;
public string documentationDir = HelpUtil.stdDocumentationDir;
public string docFxExecutable = HelpUtil.stdDocFxExecutable;
public string docFxPort = HelpUtil.stdDocFxPort;

//code editor
public bool edit_wrap, edit_noImages;
public string edit_theme;

//code info, autocorrection, formatting
Expand Down
76 changes: 74 additions & 2 deletions Au.Editor/App/DOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public static void AaShow(EPage? page = null) {
_Hotkeys();
_Other();
_OS();
_Docs();

//_tc.SelectedIndex = 2;

Expand Down Expand Up @@ -669,8 +670,70 @@ unsafe void _OS() {
Api.SystemParametersInfo(Api.SPI_SETKEYBOARDCUES, 0, (void*)(underlineAK ? 0 : 1), save: true, notify: true);
};
}

protected override void OnPreviewKeyDown(KeyEventArgs e) {
void _Docs()

{
var b = _Page("Docs");

b.R.Add(out KCheckBox enableLocalDocumentation, "Enable Access to Local Documentation").Checked(App.Settings.enableLocalDocumentation);
b.R.Add(out KCheckBox nonDefaultDocumentationDir, "Use a directory for documentation other than the default").Checked(0 != string.Compare(HelpUtil.stdDocumentationDir, App.Settings.documentationDir, StringComparison.OrdinalIgnoreCase));
b.R.Add<TextBlock>("Local documentation directory").Margin(44)
.Add(out TextBox documentationDir, App.Settings.documentationDir);
b.R.Add(out KCheckBox nonDefaultDocFxExecutable, "Use DocFx Executable other than the default").Checked(0 != string.Compare(HelpUtil.stdDocFxExecutable, App.Settings.docFxExecutable, StringComparison.OrdinalIgnoreCase));
b.R.Add<TextBlock>("DocFx executable full path").Margin(44)
.Add(out TextBox docFxExecutable, App.Settings.docFxExecutable);
b.R.Add(out KCheckBox nonDefaultPort, "Use a Port other than the default").Checked(0 != string.Compare(HelpUtil.stdDocFxPort, App.Settings.docFxPort));
b.R.Add<TextBlock>("Port").Margin(44)
.Add(out TextBox docFxPort, App.Settings.docFxPort).Margin(22, 0, 0, 0)
.Validation(o => ((o as TextBox).Text.ToInt() is >= 0 and <= 65535) ? null : "0-65535");
b.End();

b.Loaded += () => _updateEnabledStates();
enableLocalDocumentation.CheckChanged += (sender, e) => _updateEnabledStates();
nonDefaultDocumentationDir.CheckChanged += (sender, e) => _updateEnabledStates();
nonDefaultDocFxExecutable.CheckChanged += (sender, e) => _updateEnabledStates();
nonDefaultPort.CheckChanged += (sender, e) => _updateEnabledStates();

void _updateEnabledStates ()
{
nonDefaultDocumentationDir.IsEnabled = enableLocalDocumentation.IsChecked;
documentationDir.IsEnabled = (enableLocalDocumentation.IsChecked && nonDefaultDocumentationDir.IsChecked);
nonDefaultDocFxExecutable.IsEnabled = enableLocalDocumentation.IsChecked;
docFxExecutable.IsEnabled = (enableLocalDocumentation.IsChecked && nonDefaultDocFxExecutable.IsChecked);
nonDefaultPort.IsEnabled = enableLocalDocumentation.IsChecked;
docFxPort.IsEnabled = (enableLocalDocumentation.IsChecked && nonDefaultPort.IsChecked);
}


nonDefaultDocumentationDir.CheckChanged += (sender, e) =>
{
if (!nonDefaultDocumentationDir.IsChecked) // If unchecked, set default path
documentationDir.Text = HelpUtil.stdDocumentationDir;
};

nonDefaultDocFxExecutable.CheckChanged += (sender, e) =>
{
if (!nonDefaultDocFxExecutable.IsChecked) // If unchecked, set default path for executable
docFxExecutable.Text = HelpUtil.stdDocFxExecutable;
};

nonDefaultPort.CheckChanged += (sender, e) =>
{
if (!nonDefaultPort.IsChecked) // If unchecked, set default port
docFxPort.Text = HelpUtil.stdDocFxPort;
};

_b.OkApply += e => {
App.Settings.enableLocalDocumentation = enableLocalDocumentation.IsChecked;
App.Settings.documentationDir = nonDefaultDocumentationDir.IsChecked ? (documentationDir.TextOrNull() ?? HelpUtil.stdDocumentationDir) : HelpUtil.stdDocumentationDir;
App.Settings.docFxExecutable = nonDefaultDocFxExecutable.IsChecked ? (docFxExecutable.TextOrNull() ?? HelpUtil.stdDocFxExecutable) : HelpUtil.stdDocFxExecutable;
int p = docFxPort.Text.ToInt();
App.Settings.docFxPort = nonDefaultPort.IsChecked ? (docFxPort.TextOrNull() ?? HelpUtil.stdDocFxPort) : HelpUtil.stdDocFxPort;
PushLocalDocOptionsToHelpUtils(App.Settings);
};
}

protected override void OnPreviewKeyDown(KeyEventArgs e) {
if (e.Key == Key.F1 && Keyboard.Modifiers == 0) {
HelpUtil.AuHelp("editor/Program settings");
e.Handled = true;
Expand All @@ -685,4 +748,13 @@ static class _Api {
internal unsafe delegate int FONTENUMPROC(Api.LOGFONT* lf, IntPtr tm, uint fontType, nint lParam);

}

public static void PushLocalDocOptionsToHelpUtils(AppSettings appSettings)
{
HelpUtil.enableLocalDocumentation = appSettings.enableLocalDocumentation;
HelpUtil.documentationDir = appSettings.documentationDir;
HelpUtil.docFxExecutable = appSettings.docFxExecutable;
HelpUtil.docFxPort = appSettings.docFxPort;

}
}
2 changes: 1 addition & 1 deletion Au.Editor/App/Menus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ public static class Output {
[Command(target = "")]
public static class Help {
[Command(image = "*Modern.Home" + blue)]
public static void Website() { HelpUtil.AuHelp(""); }
public static void Website() { HelpUtil.AuHelp("", true); }

[Command(image = "*BoxIcons.RegularLibrary" + darkYellow)]
public static void Library_help() { HelpUtil.AuHelp("api/"); }
Expand Down
83 changes: 75 additions & 8 deletions Au/Au.More/HelpUtil.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Security.Policy;

namespace Au.More
{
/// <summary>
Expand All @@ -6,25 +8,90 @@ namespace Au.More
public static class HelpUtil
{
/// <summary>
/// Opens an Au library help topic online.
/// Determines whether local rather than online documentation is shown for calls to <see cref="Au.More.HelpUtil.AuHelp(string)"/>.
/// </summary>
/// <param name="topic">Topic file name, like <c>"Au.wnd.find"</c> or <c>"wnd.find"</c> or <c>"articles/Wildcard expression"</c>.</param>
public static void AuHelp(string topic) {
run.itSafe(AuHelpUrl(topic));
public static bool enableLocalDocumentation;
/// <summary>
/// Directory where local documentation is found. Defaults to <see cref="Au.More.HelpUtil.stdDocumentationDir"/>.
/// </summary>
public static string documentationDir;
/// <summary>
/// Usual directory where local documentation is found. Set to <c>\docs</c> in the folder <see cref="Au.folders.Editor"/> at runtime.
/// </summary>
public static readonly string stdDocumentationDir = folders.Editor + @"\docs";
/// <summary>
/// Full path to <c>docfx.exe</c>. Defaults to <see cref="Au.More.HelpUtil.stdDocFxExecutable"/>.
/// </summary>
public static string docFxExecutable;
/// <summary>
/// Usual full path to <c>docfx.exe</c>. Set to <c>\.dotnet\tools\docfx.exe</c> in the folder <see cref="Au.folders.Profile"/> at runtime.
/// </summary>
public static readonly string stdDocFxExecutable = folders.Profile + @"\.dotnet\tools\docfx.exe";
/// <summary>
/// Port number used by docfx to serve the local documentation. Passed as <c>--port</c> parameter. Defaults to <see cref="Au.More.HelpUtil.stdDocFxPort"/>.
/// </summary>
public static string docFxPort;
/// <summary>
/// Usual port number to pass to docfx to serve the local documentation. Passed as <c>--port</c> parameter. Set to <c>8080</c>.
/// </summary>
public static readonly string stdDocFxPort = "8080";

/// <summary>
/// Opens an Au library help topic. If <see cref="Au.More.HelpUtil.enableLocalDocumentation"/> is false then help is obtained from <b>https://www.libreautomate.com/</b>.
/// Otherwise docfx.exe is called using the path <see cref="Au.More.HelpUtil.docFxExecutable"/> with the <c>serve</c> command and the <c>--port</c> parameter and help is obtained from the url <b>http://localhost:port"/</b> where <b>port</b> is <see cref="Au.More.HelpUtil.docFxPort"/>.
/// </summary>
/// <param name="topic">Topic file name, like <c>"Au.wnd.find"</c> or <c>"wnd.find"</c> or <c>"articles/Wildcard expression"</c>.</param>
public static void AuHelp(string topic, bool forceNonLocalDocumentation = false) {
run.itSafe(AuHelpUrl(topic, forceNonLocalDocumentation));
}

/// <summary>
/// Gets URL of an Au library help topic.
/// </summary>
/// <param name="topic">Topic file name, like <c>"Au.wnd.find"</c> or <c>"wnd.find"</c> or <c>"articles/Wildcard expression"</c>.</param>
public static string AuHelpUrl(string topic) {
public static string AuHelpUrl(string topic, bool forceNonLocalDocumentation = false) {
string url;
if (topic.Ends(".this[]")) topic = topic.ReplaceAt(^7.., ".Item");
else if (topic.Ends(".this")) topic = topic.ReplaceAt(^5.., ".Item");
else if (topic.Ends("[]")) topic = topic.ReplaceAt(^2.., ".Item");

var url = "https://www.libreautomate.com/";
if (!topic.NE()) url = url + (topic.Contains('/') ? null : (topic.Starts("Au.") ? "api/" : "api/Au.")) + topic + (topic.Ends('/') || topic.Ends(".html") ? null : ".html");
if (enableLocalDocumentation && !forceNonLocalDocumentation)
{
LaunchLocalDocFxServer();
url = @"http://localhost:" + docFxPort + @"/";
}
else
{
url = "https://www.libreautomate.com/";
}
if (!topic.NE()) url = url + (topic.Contains('/') ? null : (topic.Starts("Au.") ? "api/" : "api/Au.")) + topic + (topic.Ends('/') || topic.Ends(".html") ? null : ".html");
return url;
}
}
/// <summary>
/// Launches DocFx.exe server if it is not already running and creates an event handler to close it when this process exits.
/// </summary>
private static void LaunchLocalDocFxServer()
{
documentationDir ??= stdDocumentationDir;
docFxPort ??= stdDocFxPort;
string cmdLine = $@"serve ""{documentationDir}"" --port {docFxPort}";
foreach (int pidInLoop in process.getProcessIds("docfx.exe"))
{
if (string.Equals(process.getCommandLine(pidInLoop, true), cmdLine)) return;
}

docFxExecutable ??= stdDocFxExecutable;
RResult rresult = run.it(docFxExecutable, cmdLine, flags: RFlags.InheritAdmin, dirEtc: new() { WindowState = ProcessWindowStyle.Hidden });
var pid = rresult.ProcessId;
process.getTimes(pid, out long created, out long _);

process.thisProcessExit += (Exception ex) =>
{
if (process.getTimes(pid, out long createdInEventHandler, out long _))
{
if (created == createdInEventHandler) process.terminate(pid);
}
};
}
}
}
19 changes: 19 additions & 0 deletions LA.Workspace.For.Scripts/Readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
This is for creating docs which is done within working LibraAutomate (LA) environment

Setup the LA workspace
1. Edit files.xml in this directory. Change the Scripts path in line 3 to the absolute path to the scripts directory.
The relative path from this readme file is ..\Scripts but the absolute path will depend
upon the name and location of your repository for LA
2. Build LA --> Install LA --> Run LA
3. Within LA, Open the workspace whose relative path from this readme file is ..\LA.Workspace.For.Scripts
(LA menu --> File --> workspace --> open workspace

Build the docs
From LA run the script @Au docs --> Au docs. This will run the _Build function. See the documentation for that function for further details.
If starting from nothing, need to enable the booleans cookbook, preprocess, docFxBuild, and postprocess.

To use the docs
Copy the docs created in the previous step to their final destination. Expected location is \Program files\LibreAutomate\Docs.
Download the program DocFx.exe which requries the .NET SDK
Turn on location documentation From LA --> tools --> options. May need to modify the default location

54 changes: 54 additions & 0 deletions LA.Workspace.For.Scripts/files.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<files>
<c n="global.cs" i="50" />
<c n="passwords.cs" i="94" />
<c n="Sftp.cs" i="93" />
<d n="Scripts" i="51" path="C:\Users\dks\Source\repos\LibreAutomate\Scripts">
<d n="@Au docs" i="52">
<s n="Au docs.cs" i="53" />
<c n="AuDocs analyze.cs" i="54" />
<c n="AuDocs code.cs" i="55" />
<c n="AuDocs cookbook.cs" i="56" />
<c n="AuDocs other tasks.cs" i="57" />
<c n="AuDocs text.cs" i="58" />
<c n="AuDocs.cs" i="59" />
<n n="Readme.txt" i="60" />
<s n="VS goto.cs" i="61" />
</d>
<d n="@WinAPI converter" i="62">
<s n="WinAPI converter.cs" i="63" />
<c n="WinApiConverter+.cs" i="64" />
<c n="WinApiConverter.cs" i="65" />
</d>
<c n="Create cookbook.db.cs" i="66" />
<s n="Create NuGet package.cs" i="67" />
<d n="old" i="68">
<d n="Windows SDK to C#" i="69">
<d n="@SDK converter" i="70">
<c n="Classes.cs" i="71" />
<c n="Constants.cs" i="72" />
<c n="Converter.cs" i="73" />
<c n="Expr.cs" i="74" />
<c n="Functions.cs" i="75" />
<c n="Parameters.cs" i="76" />
<s n="SDK converter.cs" i="77" />
<c n="Tokenize.cs" i="78" />
<c n="Types.cs" i="79" />
<c n="Util.cs" i="80" />
</d>
<n n="Readme.txt" i="81" />
<d n="Scripts" i="82">
<d n="once" i="83">
<s n="SDK get dll names.cs" i="84" />
<s n="SDK get GUID.cs" i="85" />
</d>
<s n="SDK append 32-bit diff.cs" i="86" />
<s n="SDK create database.cs" i="87" />
<n n="SDK headers.h" i="88" />
<s n="SDK preprocessor.cs" i="89" />
<c n="SdkUtil.cs" i="90" />
</d>
</d>
</d>
<s n="Update version.txt.cs" i="91" />
</d>
</files>
59 changes: 59 additions & 0 deletions LA.Workspace.For.Scripts/files/Sftp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// file copied from Other\BuildEvents 2/23/25

/*/ nuget -\SSH.NET; /*/
using Microsoft.Win32;
using Renci.SshNet;
using System.Text.Json;
using System.Windows.Controls;


static class Sftp {
public static SftpConnectionInfo GetConnectionInfo(string where = "libreautomate.com") {
string rk = @"HKEY_CURRENT_USER\Software\Au";
var j = JsonSerializer.Deserialize<SftpConnectionInfo>(Registry.GetValue(rk, where, "{}") as string);
g1:
if (j.host.NE() || j.port == 0 || j.user.NE() || j.pass.NE()) {
var b = new wpfBuilder("SSH connection").WinSize(400);
b.R.Add("IP", out TextBox tIp, j.host).Focus(); //note: use IP. With hostname fails when using CloudFlare CDN.
b.R.Add("Port", out TextBox tPort, j.port.ToS());
b.R.Add("User", out TextBox tUser, j.user);
b.R.Add("Password", out TextBox tPass, j.pass.NE() ? null : Convert2.AesDecryptS(j.pass, "8470"));
b.R.AddOkCancel();
b.End();
if (!b.ShowDialog()) return null;
j.host = tIp.Text;
j.port = tPort.Text.ToInt();
j.user = tUser.Text;
j.pass = Convert2.AesEncryptS(tPass.Text, "8470");
Registry.SetValue(rk, where, JsonSerializer.Serialize(j));
goto g1;
}
j.pass = Convert2.AesDecryptS(j.pass, "8470");
return j;
}

public static void ConnectAndUpload(this SftpClient ftp, string ftpDir, string localFile) {
ftp.Connect();
ftp.ChangeDirectory(ftpDir);
using var stream = File.OpenRead(localFile);
ftp.UploadFile(stream, pathname.getName(localFile));
}

/// <summary>
/// Uploads a file to libreautomate.com (IP 194.5.156.231).
/// </summary>
/// <param name="ftpDir">Eg <c>"domains/libreautomate.com/public_html"</c></param>
/// <param name="localFile"></param>
public static void UploadToLA(string ftpDir, string localFile) {
var ci = Sftp.GetConnectionInfo(); if (ci == null) return;
using var ftp = new SftpClient(ci.host, ci.port, ci.user, ci.pass);
ConnectAndUpload(ftp, ftpDir, localFile);
}
}

record SftpConnectionInfo {
public string host { get; set; }
public int port { get; set; }
public string user { get; set; }
public string pass { get; set; }
}
Loading