Skip to content
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
20 changes: 17 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,25 @@ Files are downloaded to `%temp%MarkdownSnippets` with a maximum of 100 files kep
Will render:

<!-- web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet -->
<a id='snippet-snipPet'></a>
<a id='snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt%23snipPet'></a>
```txt
Some code
```
<sup><a href='#snippet-snipPet' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet' title='Snippet source file'>anchor</a></sup>
<!-- endSnippet -->

You can optionally provide a second URL that will be used for the source link. This is useful when the raw content URL is different from the view URL. For example:

`web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet`

Will render:

<!-- web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet -->
<a id='snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt%23snipPet'></a>
```txt
Some code
```
<sup><a href='https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet#L1-L3' title='Snippet source file'>anchor</a></sup>
<!-- endSnippet -->

### Including a full file
Expand Down Expand Up @@ -347,7 +361,7 @@ switch (linkFormat)
throw new($"Unknown LinkFormat: {linkFormat}");
}
```
<sup><a href='/src/MarkdownSnippets/Processing/SnippetMarkdownHandling.cs#L103-L124' title='Snippet source file'>snippet source</a> | <a href='#snippet-BuildLink' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/MarkdownSnippets/Processing/SnippetMarkdownHandling.cs#L125-L146' title='Snippet source file'>snippet source</a> | <a href='#snippet-BuildLink' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand Down
18 changes: 16 additions & 2 deletions readme.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,25 @@ Files are downloaded to `%temp%MarkdownSnippets` with a maximum of 100 files kep
Will render:

<!-- web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet -->
<a id='snippet-snipPet'></a>
<a id='snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt%23snipPet'></a>
```txt
Some code
```
<sup><a href='#snippet-snipPet' title='Start of snippet'>anchor</a></sup>
<sup><a href='https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet' title='Snippet source file'>anchor</a></sup>
<!-- endSnippet -->

You can optionally provide a second URL that will be used for the source link. This is useful when the raw content URL is different from the view URL. For example:

`web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet`

Will render:

<!-- web-snippet: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet -->
<a id='snippet-https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt%23snipPet'></a>
```txt
Some code
```
<sup><a href='https://github.com/SimonCropp/MarkdownSnippets/blob/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt#snipPet#L1-L3' title='Snippet source file'>anchor</a></sup>
<!-- endSnippet -->

### Including a full file
Expand Down
89 changes: 65 additions & 24 deletions src/MarkdownSnippets/Processing/MarkdownProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,11 @@ void AppendSnippet(string key1)
line.Current = builder.ToString();
}

void AppendWebSnippet(string url, string snippetKey)
void AppendWebSnippet(string url, string snippetKey, string? viewUrl = null)
{
builder.Clear();
var indentedAppendLine = CreateIndentedAppendLine(line.LeadingWhitespace);
ProcessWebSnippetLine(indentedAppendLine, missingSnippets, usedSnippets, url, snippetKey, line);
ProcessWebSnippetLine(indentedAppendLine, missingSnippets, usedSnippets, url, snippetKey, viewUrl, line);
builder.TrimEnd();
line.Current = builder.ToString();
}
Expand All @@ -205,9 +205,9 @@ void AppendWebSnippet(string url, string snippetKey)
continue;
}

if (SnippetKey.ExtractWebSnippet(line, out var url, out var snippetKey))
if (SnippetKey.ExtractWebSnippet(line, out var url, out var snippetKey, out var viewUrl))
{
AppendWebSnippet(url, snippetKey);
AppendWebSnippet(url, snippetKey, viewUrl);
continue;
}

Expand All @@ -230,6 +230,20 @@ void AppendWebSnippet(string url, string snippetKey)
continue;
}

if (SnippetKey.ExtractStartCommentWebSnippet(line, out url, out snippetKey, out viewUrl))
{
AppendWebSnippet(url, snippetKey, viewUrl);

index++;

lines.RemoveUntil(
index,
"<!-- endSnippet -->",
relativePath,
line);
continue;
}

if (line.Current.TrimStart() == "<!-- toc -->")
{
tocLine = line;
Expand Down Expand Up @@ -298,38 +312,65 @@ void ProcessSnippetLine(Action<string> appendLine, List<MissingSnippet> missings
appendLine("<!-- endSnippet -->");
}

void ProcessWebSnippetLine(Action<string> appendLine, List<MissingSnippet> missings, List<Snippet> used, string url, string snippetKey, Line line)
void ProcessWebSnippetLine(Action<string> appendLine, List<MissingSnippet> missings, List<Snippet> used, string url, string snippetKey, string? viewUrl, Line line)
{
appendLine($"<!-- web-snippet: {url}#{snippetKey} -->");
var commentText = viewUrl == null
? $"<!-- web-snippet: {url}#{snippetKey} -->"
: $"<!-- web-snippet: {url}#{snippetKey} {viewUrl} -->";
appendLine(commentText);
// Download file content
var (success, content) = Downloader.DownloadContent(url).GetAwaiter().GetResult();
if (!success || string.IsNullOrWhiteSpace(content))
try
{
var missing = new MissingSnippet($"{url}#{snippetKey}", line.LineNumber, line.Path);
missings.Add(missing);
appendLine("```");
appendLine($"** Could not fetch or parse web-snippet '{url}#{snippetKey}' **");
appendLine("```");
var (success, content) = Downloader.DownloadContent(url).GetAwaiter().GetResult();
if (!success || string.IsNullOrWhiteSpace(content))
{
var missing = new MissingSnippet($"{url}#{snippetKey}", line.LineNumber, line.Path);
missings.Add(missing);
appendLine("```");
appendLine($"** Could not fetch or parse web-snippet '{url}#{snippetKey}' **");
appendLine("```");
appendLine("<!-- endSnippet -->");
return;
}
// Extract snippets from content
using var reader = new StringReader(content);
var snippets = FileSnippetExtractor.Read(reader, url);
var found = snippets.FirstOrDefault(_ => _.Key == snippetKey);
if (found == null)
{
var missing = new MissingSnippet($"{url}#{snippetKey}", line.LineNumber, line.Path);
missings.Add(missing);
appendLine("```");
appendLine($"** Could not find snippet '{snippetKey}' in '{url}' **");
appendLine("```");
appendLine("<!-- endSnippet -->");
return;
}
// Create new snippet with viewUrl if provided
var snippetToAppend = viewUrl == null
? found
: Snippet.Build(
language: found.Language,
startLine: found.StartLine,
endLine: found.EndLine,
value: found.Value,
key: found.Key,
path: found.Path,
expressiveCode: found.ExpressiveCode,
viewUrl: viewUrl);
appendSnippets(snippetKey, [snippetToAppend], appendLine);
appendLine("<!-- endSnippet -->");
return;
used.Add(snippetToAppend);
}
// Extract snippets from content
using var reader = new StringReader(content);
var snippets = FileSnippetExtractor.Read(reader, url);
var found = snippets.FirstOrDefault(_ => _.Key == snippetKey);
if (found == null)
catch
{
var missing = new MissingSnippet($"{url}#{snippetKey}", line.LineNumber, line.Path);
missings.Add(missing);
appendLine("```");
appendLine($"** Could not find snippet '{snippetKey}' in '{url}' **");
appendLine($"** Could not fetch or parse web-snippet '{url}#{snippetKey}' **");
appendLine("```");
appendLine("<!-- endSnippet -->");
return;
}
appendSnippets(snippetKey, [found], appendLine);
appendLine("<!-- endSnippet -->");
used.Add(found);
}

bool TryGetSnippets(
Expand Down
80 changes: 74 additions & 6 deletions src/MarkdownSnippets/Processing/SnippetKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,51 @@ public static bool ExtractStartCommentSnippet(Line line, [NotNullWhen(true)] out
return true;
}

public static bool ExtractStartCommentWebSnippet(Line line, [NotNullWhen(true)] out string? url, [NotNullWhen(true)] out string? snippetKey) =>
ExtractStartCommentWebSnippet(line, out url, out snippetKey, out _);

public static bool ExtractStartCommentWebSnippet(Line line, [NotNullWhen(true)] out string? url, [NotNullWhen(true)] out string? snippetKey, out string? viewUrl)
{
var lineCurrent = line.Current.TrimStart();
if (!IsStartCommentWebSnippetLine(lineCurrent))
{
url = null;
snippetKey = null;
viewUrl = null;
return false;
}

var substring = lineCurrent[18..]; // after "<!-- web-snippet: "
var indexOf = substring.IndexOf("-->");
var value = substring[..indexOf].Trim();

// Check for optional second URL separated by whitespace
var parts = value.Split([' ', '\t'], StringSplitOptions.RemoveEmptyEntries);
string firstPart;
if (parts.Length >= 2)
{
firstPart = parts[0];
viewUrl = parts[1];
}
else
{
firstPart = value;
viewUrl = null;
}

var hashIndex = firstPart.LastIndexOf('#');
if (hashIndex < 0 || hashIndex == firstPart.Length - 1)
{
url = null;
snippetKey = null;
viewUrl = null;
return false;
}
url = firstPart[..hashIndex];
snippetKey = firstPart[(hashIndex + 1)..];
return true;
}

public static bool ExtractSnippet(Line line, [NotNullWhen(true)] out string? key)
{
var lineCurrent = line.Current.TrimStart();
Expand All @@ -35,25 +80,45 @@ public static bool ExtractSnippet(Line line, [NotNullWhen(true)] out string? key
return true;
}

public static bool ExtractWebSnippet(Line line, [NotNullWhen(true)] out string? url, [NotNullWhen(true)] out string? snippetKey)
public static bool ExtractWebSnippet(Line line, [NotNullWhen(true)] out string? url, [NotNullWhen(true)] out string? snippetKey) =>
ExtractWebSnippet(line, out url, out snippetKey, out _);

public static bool ExtractWebSnippet(Line line, [NotNullWhen(true)] out string? url, [NotNullWhen(true)] out string? snippetKey, out string? viewUrl)
{
var lineCurrent = line.Current.TrimStart();
if (!IsWebSnippetLine(lineCurrent))
{
url = null;
snippetKey = null;
viewUrl = null;
return false;
}
var value = lineCurrent[12..].Trim(); // after 'web-snippet:', fixed from 11 to 12
var hashIndex = value.LastIndexOf('#');
if (hashIndex < 0 || hashIndex == value.Length - 1)
var value = lineCurrent[12..].Trim(); // after 'web-snippet:'

// Check for optional second URL separated by whitespace
var parts = value.Split([' ', '\t'], StringSplitOptions.RemoveEmptyEntries);
string firstPart;
if (parts.Length >= 2)
{
firstPart = parts[0];
viewUrl = parts[1];
}
else
{
firstPart = value;
viewUrl = null;
}

var hashIndex = firstPart.LastIndexOf('#');
if (hashIndex < 0 || hashIndex == firstPart.Length - 1)
{
url = null;
snippetKey = null;
viewUrl = null;
return false;
}
url = value[..hashIndex];
snippetKey = value[(hashIndex + 1)..];
url = firstPart[..hashIndex];
snippetKey = firstPart[(hashIndex + 1)..];
return true;
}

Expand All @@ -65,4 +130,7 @@ public static bool IsStartCommentSnippetLine(string line) =>

public static bool IsWebSnippetLine(string line) =>
line.StartsWith("web-snippet:", StringComparison.OrdinalIgnoreCase);

public static bool IsStartCommentWebSnippetLine(string line) =>
line.StartsWith("<!-- web-snippet:", StringComparison.OrdinalIgnoreCase);
}
28 changes: 25 additions & 3 deletions src/MarkdownSnippets/Processing/SnippetMarkdownHandling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,19 @@ void WriteSnippet(Action<string> appendLine, Snippet snippet, uint index)

static string GetAnchorText(Snippet snippet, uint index)
{
var id = $"snippet-{snippet.Key}";
string id;
var pathStr = snippet.Path;
if (pathStr != null && pathStr.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
// For web-snippets, include the full URL and the snippet key, with '#' encoded
var combined = $"snippet-{pathStr}#{snippet.Key}";
id = combined.Replace("#", "%23");
}
else
{
id = $"snippet-{snippet.Key}";
}

if (index == 0)
{
return id;
Expand All @@ -62,12 +74,22 @@ static string GetAnchorText(Snippet snippet, uint index)
string GetSupText(Snippet snippet, string anchor)
{
var linkForAnchor = $"<a href='#{anchor}' title='Start of snippet'>anchor</a>";
if (snippet.Path == null || linkFormat == LinkFormat.None)
var pathLocal = snippet.Path;
if (pathLocal == null || linkFormat == LinkFormat.None)
{
return linkForAnchor;
}

var path = snippet.Path.Replace('\\', '/');
var path = pathLocal.Replace('\\', '/');
if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
// For web-snippets: use ViewUrl if provided, otherwise link back to the original URL with the snippet key as a hash
if (snippet.ViewUrl != null)
{
return $"<a href='{snippet.ViewUrl}#L{snippet.StartLine}-L{snippet.EndLine}' title='Snippet source file'>anchor</a>";
}
return $"<a href='{path}#{snippet.Key}' title='Snippet source file'>anchor</a>";
}
if (!path.StartsWith(targetDirectory))
{
// if file is not in the targetDirectory then the url wont work
Expand Down
Loading