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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,4 @@ logo.png
/src/DynamoCoreWpf/package-lock.json
/src/DynamoCoreWpf/package.json
/src/DynamoCoreWpf/.npm-cache
/src/DynamoSandbox/node_modules
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
38 changes: 38 additions & 0 deletions src/DynamoUtilities/Hash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Security.Cryptography;
using System.Text;

namespace Dynamo.Utilities
{
internal class Hash
{
internal static byte[] GetHash(byte[] bytes)
{
using (var hashAlgorithm = HashAlgorithm.Create())
{
return hashAlgorithm.ComputeHash(bytes);
}
}

internal static byte[] GetHashFromString(string str)
{
var bytes = Encoding.UTF8.GetBytes(str);
return GetHash(bytes);
}

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).

{
var builder = new StringBuilder();
foreach (var b in bytes)
{
builder.AppendFormat(@"{0:x2}", b);
}
return builder.ToString();
}

internal static string GetHashFilenameFromString(string str)
{
var hash = GetHashFromString(str);
return GetFilenameFromHash(hash);
}
}
}
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 renaming 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, 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", Required = false)]
public string InputMdFile { get; set; }
[Option('d', "directory", HelpText = "Input directory", Required = false)]
public string InputMdDirectory { get; set; }
[Option('m', "maxlength", HelpText = "Max length before renaming to a shorter name", Required = false, Default = 50)]
public int MaxLength { get; set; }
[Usage(ApplicationAlias = "Dynamo docs generator")]
public static IEnumerable<Example> Examples
{
get
{
yield return new Example("Generate docs from package folder", new RenameOptions());
}
}
}
}