Skip to content
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

DYN-4687 - docs browser and docs generator produce .md files with names that are too long. #13588

Merged
merged 15 commits into from
Dec 6, 2022
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!--- Autodesk.DesignScript.Geometry.TSpline.TSplineSurface.ByBoxLengths(origin, width, length, height, xSpans, ySpans, zSpans, symmetry, inSmoothMode) --->
<!--- HNVVP7HSR2IM5H5AFWWLLJBXSX2WTG5FEWESXLGORW2CL2CG7C4Q --->
## ByBoxLengths(origin, width, length, height, xSpans, ySpans, zSpans, symmetry, inSmoothMode) - Documentation
This documentation file is auto generated by NodeDocumentationMarkdownGenerator, Version=2.14.0.3986, Culture=neutral, PublicKeyToken=null.

For more information about adding documentation to nodes see https://github.com/DynamoDS/Dynamo/wiki/Create-and-Add-Custom-Documentation-to-Nodes

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!--- Autodesk.DesignScript.Geometry.TSpline.TSplineSurface.ByBoxLengths(cs, width, length, height, xSpans, ySpans, zSpans, symmetry, inSmoothMode)
<!--- VFK33PBU2AHJIHDOMW3NS2YJNGRTQADZ2RUGALOLEWRXK4DM4DTA --->
## ByBoxLengths(cs, width, length, height, xSpans, ySpans, zSpans, symmetry, inSmoothMode) - Documentation
This documentation file is auto generated by NodeDocumentationMarkdownGenerator, Version=2.14.0.3986, Culture=neutral, PublicKeyToken=null.

For more information about adding documentation to nodes see https://github.com/DynamoDS/Dynamo/wiki/Create-and-Add-Custom-Documentation-to-Nodes

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!--- Autodesk.DesignScript.Geometry.TSpline.TSplineSurface.ByBoxLengths(width, length, height, xSpans, ySpans, zSpans, symmetry, inSmoothMode) --->
<!--- WN6BWNG6A6KOPFMMRBHLV7XBOOEGXTSO5I5FZXHKKUG5YO6MNNYA --->
## ByBoxLengths(width, length, height, xSpans, ySpans, zSpans, symmetry, inSmoothMode) - Documentation
This documentation file is auto generated by NodeDocumentationMarkdownGenerator, Version=2.14.0.3986, Culture=neutral, PublicKeyToken=null.

For more information about adding documentation to nodes see https://github.com/DynamoDS/Dynamo/wiki/Create-and-Add-Custom-Documentation-to-Nodes

Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ private set
private Uri link;
private string graphPath;
private string content;
private string name;

private MarkdownHandler MarkdownHandlerInstance => markdownHandler ?? (markdownHandler = new MarkdownHandler());
public bool HasContent => !string.IsNullOrWhiteSpace(this.content);
Expand Down Expand Up @@ -184,6 +185,7 @@ private void HandleLocalResource(OpenDocumentationLinkEventArgs e)
{
string targetContent;
string graph;
string graphName;
Uri link;
switch (e)
{
Expand All @@ -195,19 +197,22 @@ private void HandleLocalResource(OpenDocumentationLinkEventArgs e)
link = string.IsNullOrEmpty(mdLink) ? new Uri(String.Empty, UriKind.Relative) : new Uri(mdLink);
graph = GetGraphLinkFromMDLocation(link);
targetContent = CreateNodeAnnotationContent(openNodeAnnotationEventArgs);
graphName = openNodeAnnotationEventArgs.MinimumQualifiedName;
break;

case OpenDocumentationLinkEventArgs openDocumentationLink:
link = openDocumentationLink.Link;
graph = GetGraphLinkFromMDLocation(link);
targetContent = ResourceUtilities.LoadContentFromResources(openDocumentationLink.Link.ToString(), GetType().Assembly);
graphName = null;
break;

default:
// Navigate to unsupported
targetContent = null;
graph = null;
link = null;
graphName = null;
break;
}

Expand All @@ -220,6 +225,7 @@ private void HandleLocalResource(OpenDocumentationLinkEventArgs e)
this.content = targetContent;
this.Link = link;
this.graphPath = graph;
this.name = graphName;
}
}
catch (FileNotFoundException)
Expand Down Expand Up @@ -358,7 +364,8 @@ internal void InsertGraph()
{
if (graphPath != null)
{
raiseInsertGraph(this, new InsertDocumentationLinkEventArgs(graphPath, Path.GetFileNameWithoutExtension(graphPath)));
var graphName = this.name ?? Path.GetFileNameWithoutExtension(graphPath);
raiseInsertGraph(this, new InsertDocumentationLinkEventArgs(graphPath, graphName));
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Threading;
using Dynamo.Interfaces;
using Dynamo.Logging;
using Dynamo.Utilities;

namespace Dynamo.DocumentationBrowser
{
Expand Down Expand Up @@ -95,10 +96,13 @@ public string GetAnnotationDoc(string nodeNamespace, string packageName)
return output;
}

var shortName = Hash.GetHashFilenameFromString(nodeNamespace);

FileInfo matchingDoc = null;
if (hostDynamoFallbackDocPath != null)
{
matchingDoc = hostDynamoFallbackDocPath.GetFiles($"{nodeNamespace}.md").FirstOrDefault();
matchingDoc = hostDynamoFallbackDocPath.GetFiles($"{shortName}.md").FirstOrDefault() ??
hostDynamoFallbackDocPath.GetFiles($"{nodeNamespace}.md").FirstOrDefault();
if (matchingDoc != null)
{
return matchingDoc.FullName;
Expand All @@ -107,7 +111,8 @@ public string GetAnnotationDoc(string nodeNamespace, string packageName)

if (dynamoCoreFallbackDocPath != null)
{
matchingDoc = dynamoCoreFallbackDocPath.GetFiles($"{nodeNamespace}.md").FirstOrDefault();
matchingDoc = dynamoCoreFallbackDocPath.GetFiles($"{shortName}.md").FirstOrDefault() ??
dynamoCoreFallbackDocPath.GetFiles($"{nodeNamespace}.md").FirstOrDefault();
}

return matchingDoc is null ? string.Empty : matchingDoc.FullName;
Expand Down
97 changes: 97 additions & 0 deletions src/DynamoUtilities/Hash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace Dynamo.Utilities
{
internal class Hash
{
/// <summary>
/// Get the hash value
/// </summary>
/// <param name="bytes">input as a byte array</param>
/// <returns>hash as a byte array</returns>
internal static byte[] GetHash(byte[] bytes)
{
using (var hashAlgorithm = SHA256.Create())
{
return hashAlgorithm.ComputeHash(bytes);
}
}

/// <summary>
/// Get the hash value
/// </summary>
/// <param name="str">input as a string</param>
/// <returns>hash as a byte array</returns>
internal static byte[] GetHashFromString(string str)
{
var bytes = Encoding.UTF8.GetBytes(str);
return GetHash(bytes);
}

/// <summary>
/// Get a valid filename for a hash
/// </summary>
/// <param name="bytes">hash as a byte array</param>
/// <returns>hash as a valid filename string</returns>
internal static string GetFilenameFromHash(byte[] bytes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the encoding/decoding logic specific to filenames ? looks pretty generic to me

Copy link
Contributor Author

@sm6srw sm6srw Dec 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we get back are guaranteed to work as a file name in all file systems. That's why I named it that way (and used base32).

{
return ToBase32String(bytes);
}

/// <summary>
/// Get hash file name
/// </summary>
/// <param name="str">inout as a string</param>
/// <returns>hash as a valid filename</returns>
internal static string GetHashFilenameFromString(string str)
{
var hash = GetHashFromString(str);
return GetFilenameFromHash(hash);
}

/// <summary>
/// /// The different characters allowed in Base32 encoding.
/// </summary>
/// <remarks>
/// This is a 32-character subset of the twenty-six letters A–Z and six digits 2–7.
/// <see cref="https://en.wikipedia.org/wiki/Base32" />
/// </remarks>
internal static string Base32AllowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

/// <summary> /// Converts a byte array into a Base32 string.
/// </summary>
/// <param name="input">The string to convert to Base32.</param>
/// <param name="addPadding">Whether or not to add RFC3548 '='-padding to the string.</param>
/// <returns>A Base32 string.</returns>
/// <remarks>
/// https://tools.ietf.org/html/rfc3548#section-2.2 indicates padding MUST be added unless the reference to the RFC tells us otherswise.
/// https://github.com/google/google-authenticator/wiki/Key-Uri-Format indicates that padding SHOULD be omitted.
/// To meet both requirements, you can omit padding when required.
/// </remarks>
internal static string ToBase32String(byte[] input, bool addPadding = false)
{
if (input == null || input.Length == 0)
{
return string.Empty;
}

var bits = input.Select(b => Convert.ToString(b, 2).PadLeft(8, '0')).Aggregate((a, b) => a + b)
.PadRight((int)(Math.Ceiling((input.Length * 8) / 5d) * 5), '0');
var result = Enumerable.Range(0, bits.Length / 5)
.Select(i => Base32AllowedCharacters.Substring(Convert.ToInt32(bits.Substring(i * 5, 5), 2), 1))
.Aggregate((a, b) => a + b);

if (addPadding)
{
result = result.PadRight((int)(Math.Ceiling(result.Length / 8d) * 8), '=');
}

return result;
}
}
}


5 changes: 3 additions & 2 deletions src/DynamoUtilities/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Reflection;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

Expand All @@ -20,4 +20,5 @@
[assembly: InternalsVisibleTo("DocumentationBrowserViewExtension")]
[assembly: InternalsVisibleTo("DynamoPackages")]
[assembly: InternalsVisibleTo("ProtoScript")]
[assembly: InternalsVisibleTo("ProtoCore")]
[assembly: InternalsVisibleTo("ProtoCore")]
[assembly: InternalsVisibleTo("NodeDocumentationMarkdownGenerator")]
18 changes: 16 additions & 2 deletions src/Tools/NodeDocumentationMarkdownGenerator/CommandHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Text;
using Dynamo.Logging;
using NodeDocumentationMarkdownGenerator.Commands;
Expand Down Expand Up @@ -36,6 +36,20 @@ internal static string HandleFromDirectory(FromDirectoryOptions opts)
return "";
}

internal static string HandleRename(RenameOptions opts)
{
try
{
RenameCommand.HandleRename(opts);
}
catch (Exception e)
{
LogExceptionToConsole(e);
}

return "";
}

internal static void LogExceptionToConsole(Exception e)
{
var strBuilder = new StringBuilder();
Expand Down Expand Up @@ -76,4 +90,4 @@ public void LogWarning(string warning, WarningLevel level)
throw new NotImplementedException();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.IO;
using System.Linq;
using NodeDocumentationMarkdownGenerator.Verbs;

namespace NodeDocumentationMarkdownGenerator.Commands
{
internal static class RenameCommand
{
internal static void HandleRename(RenameOptions opts)
{
if (opts.InputMdFile is null && opts.InputMdDirectory != null)
{
RenameDirectory(opts.InputMdDirectory, opts.MaxLength);
}
else if (opts.InputMdFile != null && opts.InputMdDirectory is null)
{
RenameFile(opts.InputMdFile);
}
else
{
Console.WriteLine("Invalid options: You can rename a single file using the file option\nor rename multiple files in a directory (if they are longer than max length)\nusing the directory option");
}
}

private static void RenameFile(string file)
{
var extension = Path.GetExtension(file);
if (!extension.Equals(".md", StringComparison.InvariantCultureIgnoreCase))
{
Console.WriteLine($"Can only rename MD files: {file}");
return;
}

if (!File.Exists(file))
{
Console.WriteLine($"File not found: {file}");
return;
}

var baseName = Path.GetFileNameWithoutExtension(file);
var shortName = Dynamo.Utilities.Hash.GetHashFilenameFromString(baseName);

RenameFile(file, baseName, shortName);
}

private static void RenameFile(string file, string baseName, string shortName)
{
var content = File.ReadAllText(file);
content = content.Replace(baseName, shortName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saving the hashed name in the file contents as well?

Copy link
Contributor Author

@sm6srw sm6srw Dec 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nop, only the original name. But I could easily do it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant to ask what is the purpose of line 50 if it's not necessary to save the hashed name in the file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is for replacing the base name of all support files (images and example files etc). Sorry, I misunderstood your question.

var path = Path.GetDirectoryName(file);
var newFile = Path.Combine(path, shortName + ".md");
File.WriteAllText(newFile, $"<!--- {baseName} --->\n<!--- {shortName} --->\n" + content);
File.Delete(file);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need to expect permission issues? only devs will run this command ?

Copy link
Contributor Author

@sm6srw sm6srw Dec 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will get exceptions if that is the case and those are catched and reported at a higher level in the handle function for the rename command. Yes, this is an internal tool. It is not shipped.


var allSupportFiles = Directory.GetFiles(path, baseName + ".*", SearchOption.TopDirectoryOnly)
.Select(x => new FileInfo(x)).ToList();
allSupportFiles.AddRange(Directory.GetFiles(path, baseName + "_img.*", SearchOption.TopDirectoryOnly)
.Select(x => new FileInfo(x)).ToList());

foreach (var supportFile in allSupportFiles)
{
var newName = Path.Combine(supportFile.DirectoryName,
supportFile.Name.Replace(baseName, shortName));
supportFile.MoveTo(newName);
}
}

private static void RenameDirectory(string directory, int maxLength)
{
if (!Directory.Exists(directory))
{
Console.WriteLine($"Directory not found: {directory}");
return;
}

var allMdFiles = Directory.GetFiles(directory, "*.md", SearchOption.TopDirectoryOnly).Select(x => new FileInfo(x)).ToList();

foreach (var mdFile in allMdFiles)
{
if (mdFile.Name.Length > maxLength)
{
var baseName = Path.GetFileNameWithoutExtension(mdFile.Name);
var shortName = Dynamo.Utilities.Hash.GetHashFilenameFromString(baseName);
RenameFile(mdFile.FullName, baseName, shortName);
}
}
}
}
}
5 changes: 3 additions & 2 deletions src/Tools/NodeDocumentationMarkdownGenerator/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
Expand Down Expand Up @@ -50,11 +50,12 @@ internal static void Main(string[] args)

ShowWelcomeMessages();

var result = Parser.Default.ParseArguments<FromDirectoryOptions, FromPackageOptions>(args);
var result = Parser.Default.ParseArguments<FromDirectoryOptions, FromPackageOptions, RenameOptions>(args);
var text = result
.MapResult(
(FromDirectoryOptions opts) => CommandHandler.HandleFromDirectory(opts),
(FromPackageOptions opts) => CommandHandler.HandleFromPackage(opts),
(RenameOptions opts) => CommandHandler.HandleRename(opts),
err => "1");
Console.WriteLine($"docs generation tool {sw.Elapsed.TotalSeconds}");
# if DEBUG
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using CommandLine.Text;
using CommandLine;
using System.Collections.Generic;

namespace NodeDocumentationMarkdownGenerator.Verbs
{
[Verb("rename", HelpText = "Renaming utilities for fallback MD files")]
internal class RenameOptions
{
[Option('f', "file", HelpText = "Input MD file. Renames a single MD file including any support files to a shorter length (~56-60 characters) base file name.", Required = false)]
public string InputMdFile { get; set; }
[Option('d', "directory", HelpText = "Input directory. Inspects all MD files in a directory and renames all MD files with a base name longer that maxlength (see below).", Required = false)]
public string InputMdDirectory { get; set; }
[Option('m', "maxlength", HelpText = "Max length of the base file name before renaming to a shorter length (~56-60 characters) base file name.", Required = false, Default = 65)]
public int MaxLength { get; set; }
[Usage(ApplicationAlias = "Dynamo docs generator")]
public static IEnumerable<Example> Examples
{
get
{
yield return new Example("Renaming utilities for fallback MD files", new RenameOptions());
}
}
}
}
Loading