diff --git a/readme.md b/readme.md
index 8be83bb1..f5b85fae 100644
--- a/readme.md
+++ b/readme.md
@@ -219,11 +219,25 @@ Files are downloaded to `%temp%MarkdownSnippets` with a maximum of 100 files kep
Will render:
-
+
```txt
Some code
```
- anchor
+ anchor
+
+
+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:
+
+
+
+ ```txt
+ Some code
+ ```
+ anchor
### Including a full file
@@ -347,7 +361,7 @@ switch (linkFormat)
throw new($"Unknown LinkFormat: {linkFormat}");
}
```
-snippet source | anchor
+snippet source | anchor
diff --git a/readme.source.md b/readme.source.md
index 5c290083..8023a1e7 100644
--- a/readme.source.md
+++ b/readme.source.md
@@ -212,11 +212,25 @@ Files are downloaded to `%temp%MarkdownSnippets` with a maximum of 100 files kep
Will render:
-
+
```txt
Some code
```
- anchor
+ anchor
+
+
+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:
+
+
+
+ ```txt
+ Some code
+ ```
+ anchor
### Including a full file
diff --git a/src/MarkdownSnippets/Processing/MarkdownProcessor.cs b/src/MarkdownSnippets/Processing/MarkdownProcessor.cs
index 318aac61..34c5c44d 100644
--- a/src/MarkdownSnippets/Processing/MarkdownProcessor.cs
+++ b/src/MarkdownSnippets/Processing/MarkdownProcessor.cs
@@ -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();
}
@@ -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;
}
@@ -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,
+ "",
+ relativePath,
+ line);
+ continue;
+ }
+
if (line.Current.TrimStart() == "")
{
tocLine = line;
@@ -298,38 +312,65 @@ void ProcessSnippetLine(Action appendLine, List missings
appendLine("");
}
- void ProcessWebSnippetLine(Action appendLine, List missings, List used, string url, string snippetKey, Line line)
+ void ProcessWebSnippetLine(Action appendLine, List missings, List used, string url, string snippetKey, string? viewUrl, Line line)
{
- appendLine($"");
+ var commentText = viewUrl == null
+ ? $""
+ : $"";
+ 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("");
+ 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("");
+ 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("");
- 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("");
- return;
}
- appendSnippets(snippetKey, [found], appendLine);
- appendLine("");
- used.Add(found);
}
bool TryGetSnippets(
diff --git a/src/MarkdownSnippets/Processing/SnippetKey.cs b/src/MarkdownSnippets/Processing/SnippetKey.cs
index d067f76e..252b4ae1 100644
--- a/src/MarkdownSnippets/Processing/SnippetKey.cs
+++ b/src/MarkdownSnippets/Processing/SnippetKey.cs
@@ -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 "");
+ 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();
@@ -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;
}
@@ -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("
+```txt
+Some code
+```
+
+
+after
+}
\ No newline at end of file
diff --git a/src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithCommentWebSnippetWithViewUrl.verified.txt b/src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithCommentWebSnippetWithViewUrl.verified.txt
new file mode 100644
index 00000000..a533630c
--- /dev/null
+++ b/src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithCommentWebSnippetWithViewUrl.verified.txt
@@ -0,0 +1,22 @@
+{
+ UsedSnippets: [
+ {
+ Key: snipPet,
+ Language: txt,
+ Value: Some code,
+ Error: ,
+ FileLocation: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt(1-3),
+ IsInError: false
+ }
+ ],
+ result:
+before
+
+
+```txt
+Some code
+```
+
+
+after
+}
diff --git a/src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithInlineWebSnippetWithViewUrl.verified.txt b/src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithInlineWebSnippetWithViewUrl.verified.txt
new file mode 100644
index 00000000..a533630c
--- /dev/null
+++ b/src/Tests/MarkdownProcessor/MarkdownProcessorTests.WithInlineWebSnippetWithViewUrl.verified.txt
@@ -0,0 +1,22 @@
+{
+ UsedSnippets: [
+ {
+ Key: snipPet,
+ Language: txt,
+ Value: Some code,
+ Error: ,
+ FileLocation: https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/main/src/Tests/DirectorySnippetExtractor/Case/code1.txt(1-3),
+ IsInError: false
+ }
+ ],
+ result:
+before
+
+
+```txt
+Some code
+```
+
+
+after
+}
diff --git a/src/Tests/MarkdownProcessor/MarkdownProcessorTests.cs b/src/Tests/MarkdownProcessor/MarkdownProcessorTests.cs
index 25af7d80..07c6f276 100644
--- a/src/Tests/MarkdownProcessor/MarkdownProcessorTests.cs
+++ b/src/Tests/MarkdownProcessor/MarkdownProcessorTests.cs
@@ -792,6 +792,68 @@ public Task WithIndentedMultiLineSnippet()
]);
}
+ [Fact]
+ public Task WithCommentWebSnippetUpdate()
+ {
+ var content = """
+
+ before
+
+
+ OLD CONTENT
+ THAT SHOULD BE
+ REPLACED
+
+
+ after
+
+ """;
+
+ return SnippetVerifier.Verify(
+ DocumentConvention.InPlaceOverwrite,
+ content);
+ }
+
+ [Fact]
+ public Task WithCommentWebSnippetWithViewUrl()
+ {
+ var content = """
+
+ before
+
+
+ OLD CONTENT
+ THAT SHOULD BE
+ REPLACED
+
+
+ after
+
+ """;
+
+ return SnippetVerifier.Verify(
+ DocumentConvention.InPlaceOverwrite,
+ content);
+ }
+
+ [Fact]
+ public Task WithInlineWebSnippetWithViewUrl()
+ {
+ var content = """
+
+ before
+
+ 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
+
+ after
+
+ """;
+
+ return SnippetVerifier.Verify(
+ DocumentConvention.SourceTransform,
+ content);
+ }
+
static Snippet SnippetBuild(string language, string key) =>
Snippet.Build(
language: language,
diff --git a/src/Tests/SnippetMarkdownHandlingTests.AppendWebSnippet.verified.txt b/src/Tests/SnippetMarkdownHandlingTests.AppendWebSnippet.verified.txt
new file mode 100644
index 00000000..e264ecab
--- /dev/null
+++ b/src/Tests/SnippetMarkdownHandlingTests.AppendWebSnippet.verified.txt
@@ -0,0 +1,5 @@
+
+```cs
+theValue
+```
+anchor
diff --git a/src/Tests/SnippetMarkdownHandlingTests.AppendWebSnippetWithViewUrl.verified.txt b/src/Tests/SnippetMarkdownHandlingTests.AppendWebSnippetWithViewUrl.verified.txt
new file mode 100644
index 00000000..bfb85779
--- /dev/null
+++ b/src/Tests/SnippetMarkdownHandlingTests.AppendWebSnippetWithViewUrl.verified.txt
@@ -0,0 +1,5 @@
+
+```cs
+theValue
+```
+anchor
diff --git a/src/Tests/SnippetMarkdownHandlingTests.cs b/src/Tests/SnippetMarkdownHandlingTests.cs
index 1b080f8f..259015c7 100644
--- a/src/Tests/SnippetMarkdownHandlingTests.cs
+++ b/src/Tests/SnippetMarkdownHandlingTests.cs
@@ -70,6 +70,49 @@ public Task AppendHashed()
return Verify(builder.ToString());
}
+ [Fact]
+ public Task AppendWebSnippet()
+ {
+ var builder = new StringBuilder();
+ var webSnippet = Snippet.Build(
+ startLine: 1,
+ endLine: 2,
+ value: "theValue",
+ key: "mysnippet",
+ language: "cs",
+ path: "http://example.com/file.cs",
+ expressiveCode: null);
+ var markdownHandling = new SnippetMarkdownHandling(Environment.CurrentDirectory, LinkFormat.GitHub, false);
+ using (var writer = new StringWriter(builder))
+ {
+ markdownHandling.Append("key1", new List { webSnippet }, writer.WriteLine);
+ }
+
+ return Verify(builder.ToString());
+ }
+
+ [Fact]
+ public Task AppendWebSnippetWithViewUrl()
+ {
+ var builder = new StringBuilder();
+ var webSnippet = Snippet.Build(
+ startLine: 5,
+ endLine: 10,
+ value: "theValue",
+ key: "mysnippet",
+ language: "cs",
+ path: "http://example.com/raw/file.cs",
+ expressiveCode: null,
+ viewUrl: "https://github.com/user/repo/blob/main/file.cs");
+ var markdownHandling = new SnippetMarkdownHandling(Environment.CurrentDirectory, LinkFormat.GitHub, false);
+ using (var writer = new StringWriter(builder))
+ {
+ markdownHandling.Append("key1", new List { webSnippet }, writer.WriteLine);
+ }
+
+ return Verify(builder.ToString());
+ }
+
static List Snippets() =>
[Snippet.Build(1, 2, "theValue", "thekey", "thelanguage", Environment.CurrentDirectory, expressiveCode: null)];
}
\ No newline at end of file