Skip to content

Commit 31744fb

Browse files
authored
Add simple example deduplication logic for Merge-MarkdownModel (#365)
1 parent 8499a2e commit 31744fb

File tree

5 files changed

+232
-7
lines changed

5 files changed

+232
-7
lines changed

.vscode/tasks.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "build",
6+
"command": "dotnet",
7+
"type": "process",
8+
"args": [
9+
"build",
10+
"${workspaceFolder}/test/Markdown.MAML.Test/Markdown.MAML.Test.csproj"
11+
],
12+
"problemMatcher": "$msCompile"
13+
}
14+
]
15+
}

src/Markdown.MAML/Model/MAML/MamlCodeBlock.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,17 @@ public MamlCodeBlock(string text, string languageMoniker = null)
2121
/// The text of the code block.
2222
/// </summary>
2323
public string Text { get; private set; }
24+
25+
/// <summary>
26+
/// Serves as a hash function for a <see cref="T:Markdown.MAML.Model.MAML.MamlCodeBlock"/> object.
27+
/// </summary>
28+
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
29+
/// hash table.</returns>
30+
public override int GetHashCode()
31+
{
32+
return
33+
(Text == null ? 679 : Text.GetHashCode()) ^
34+
(LanguageMoniker == null ? 765679 : LanguageMoniker.GetHashCode());
35+
}
2436
}
2537
}

src/Markdown.MAML/Model/MAML/MamlExample.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,27 @@ public class MamlExample
1313
/// Additional options that determine how the section will be formated when rendering markdown.
1414
/// </summary>
1515
public SectionFormatOption FormatOption { get; set; }
16+
17+
/// <summary>
18+
/// Serves as a hash function for a <see cref="T:Markdown.MAML.Model.MAML.MamlExample"/> object.
19+
/// Ignores the FormatOption field.
20+
/// </summary>
21+
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
22+
/// hash table.</returns>
23+
public override int GetHashCode()
24+
{
25+
int hash = 1;
26+
if (Code != null)
27+
{
28+
foreach (var codeBlock in Code) {
29+
hash ^= codeBlock.GetHashCode();
30+
}
31+
}
32+
return
33+
hash ^
34+
(Title == null ? 123 : Title.GetHashCode()) ^
35+
(Remarks == null ? 12345 : Remarks.GetHashCode()) ^
36+
(Introduction == null ? 123457 : Introduction.GetHashCode());
37+
}
1638
}
1739
}

src/Markdown.MAML/Transformer/MamlMultiModelMerger.cs

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ public MamlCommand Merge(Dictionary<string, MamlCommand> applicableTag2Model)
4646
result = new MamlCommand()
4747
{
4848
Name = referenceModel.Name,
49-
Synopsis = new SectionBody(MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Synopsis.Text))),
50-
Description = new SectionBody(MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Description.Text))),
51-
Notes = new SectionBody(MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Notes.Text))),
49+
Synopsis = new SectionBody(MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Synopsis?.Text))),
50+
Description = new SectionBody(MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Description?.Text))),
51+
Notes = new SectionBody(MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Notes?.Text))),
5252
Extent = referenceModel.Extent
5353
};
5454

@@ -157,13 +157,39 @@ private List<MamlParameter> MergeParameterList(IEnumerable<List<MamlParameter>>
157157
}
158158

159159
/// <summary>
160-
/// Combine examples together. Don't perform any reduction, but group the examples after each other and add tag name to the title.
161-
/// TODO can we combine examples better perhaps?
160+
/// Combine examples together.
161+
/// If examples are identical, we deduplicate them.
162+
/// If example is not found in all the models, add the applicable tags in parentheses.
162163
/// </summary>
163164
/// <param name="result"></param>
164165
/// <param name="applicableTag2Model"></param>
165166
private void MergeExamples(MamlCommand result, Dictionary<string, MamlCommand> applicableTag2Model)
166167
{
168+
// We want to more or less keep the order of the original examples.
169+
// To do it, we use the following algorithm:
170+
// We are adding examples sequentially:
171+
// 1st from every model, 2nd from every model, etc.
172+
// At the same time we are doing deduplication by hashcode.
173+
174+
// this dictonary gives the mapping between example hashes and applicable tags
175+
var hash2tag = new Dictionary<int, List<String>>();
176+
foreach (var pair in applicableTag2Model)
177+
{
178+
foreach (var example in pair.Value.Examples) {
179+
List<String> tagList;
180+
int hash = example.GetHashCode();
181+
if (!hash2tag.TryGetValue(hash, out tagList))
182+
{
183+
tagList = new List<string>();
184+
hash2tag[hash] = tagList;
185+
}
186+
tagList.Add(pair.Key);
187+
}
188+
}
189+
190+
// this hashset contains the example hashes for already used examples
191+
var usedHashes = new HashSet<int>();
192+
167193
int max = applicableTag2Model.Select(pair => pair.Value.Examples.Count).Max();
168194
for (int i = 0; i < max; i++)
169195
{
@@ -172,7 +198,19 @@ private void MergeExamples(MamlCommand result, Dictionary<string, MamlCommand> a
172198
if (pair.Value.Examples.Count > i)
173199
{
174200
var example = pair.Value.Examples.ElementAt(i);
175-
example.Title += string.Format(" ({0})", pair.Key);
201+
int hash = example.GetHashCode();
202+
if (usedHashes.Contains(hash)) {
203+
continue;
204+
}
205+
// if all tags are covered, don't add anything
206+
if (hash2tag[hash].Count < applicableTag2Model.Count)
207+
{
208+
// we will sort the same list few times, but that's fine
209+
hash2tag[hash].Sort();
210+
string listString = string.Join(", ", hash2tag[hash]);
211+
example.Title += string.Format(" ({0})", listString);
212+
}
213+
usedHashes.Add(hash);
176214
result.Examples.Add(example);
177215
}
178216
}

test/Markdown.MAML.Test/Transformer/MamlMultiModelMergerTests.cs

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ public class MamlMultiModelMergerSyntaxTests
301301
[Fact]
302302
public void MergeDefaultSyntaxAndCreateMarkdown()
303303
{
304-
// First merge two models with default syntax names
304+
// First merge two models with default syntax names
305305

306306
var merger = new MamlMultiModelMerger(null, false, "! ");
307307
var input = new Dictionary<string, MamlCommand>();
@@ -400,4 +400,142 @@ private MamlCommand GetModel2()
400400
return command;
401401
}
402402
}
403+
404+
public class MamlMultiModelMergerExampleTest
405+
{
406+
[Fact]
407+
public void Merge2ExamplesInOne()
408+
{
409+
// First merge two models with default syntax names
410+
411+
var merger = new MamlMultiModelMerger(null, false, "! ");
412+
var input = new Dictionary<string, MamlCommand>();
413+
414+
input["First"] = GetModel(
415+
title: "Example 1",
416+
code: "PS:> 1+1",
417+
remarks: "Hello"
418+
);
419+
input["Second"] = GetModel(
420+
title: "Example 1",
421+
code: "PS:> 1+1",
422+
remarks: "Hello"
423+
);
424+
425+
var result = merger.Merge(input);
426+
427+
// Examples
428+
Assert.Single(result.Examples);
429+
Assert.Equal("Example 1", result.Examples.ElementAt(0).Title);
430+
431+
// next render it as markdown and make sure that we don't crash
432+
var renderer = new MarkdownV2Renderer(MAML.Parser.ParserMode.FormattingPreserve);
433+
string markdown = renderer.MamlModelToString(result, true);
434+
}
435+
436+
[Fact]
437+
public void MergeExamplesOrder()
438+
{
439+
// First merge two models with default syntax names
440+
441+
var merger = new MamlMultiModelMerger(null, false, "! ");
442+
var input = new Dictionary<string, MamlCommand>();
443+
444+
input["First"] = GetModel(
445+
new []
446+
{
447+
new MamlExample() { Title = "E1" },
448+
new MamlExample() { Title = "E2" },
449+
}
450+
);
451+
input["Second"] = GetModel(
452+
new []
453+
{
454+
new MamlExample() { Title = "E2" },
455+
new MamlExample() { Title = "E1" },
456+
new MamlExample() { Title = "E3" },
457+
}
458+
);
459+
460+
var result = merger.Merge(input);
461+
462+
// Examples
463+
Assert.Equal(3, result.Examples.Count);
464+
Assert.Equal("E1", result.Examples.ElementAt(0).Title);
465+
Assert.Equal("E2", result.Examples.ElementAt(1).Title);
466+
Assert.Equal("E3 (Second)", result.Examples.ElementAt(2).Title);
467+
468+
// next render it as markdown and make sure that we don't crash
469+
var renderer = new MarkdownV2Renderer(MAML.Parser.ParserMode.FormattingPreserve);
470+
string markdown = renderer.MamlModelToString(result, true);
471+
}
472+
473+
[Fact]
474+
public void Merge3ExamplesIn2()
475+
{
476+
// First merge two models with default syntax names
477+
478+
var merger = new MamlMultiModelMerger(null, false, "! ");
479+
var input = new Dictionary<string, MamlCommand>();
480+
481+
input["First"] = GetModel(
482+
title: "Example 1",
483+
code: "PS:> 1+1",
484+
remarks: "Hello"
485+
);
486+
input["Second"] = GetModel(
487+
title: "Example 1",
488+
code: "PS:> 1+1",
489+
remarks: "Hello"
490+
);
491+
492+
input["Third"] = GetModel(
493+
title: "Example 1",
494+
code: "PS:> 1+1",
495+
remarks: "Hello world"
496+
);
497+
498+
var result = merger.Merge(input);
499+
500+
// Examples
501+
Assert.Equal(2, result.Examples.Count);
502+
Assert.Equal("Example 1 (First, Second)", result.Examples.ElementAt(0).Title);
503+
Assert.Equal("Hello", result.Examples.ElementAt(0).Remarks);
504+
Assert.Equal("Example 1 (Third)", result.Examples.ElementAt(1).Title);
505+
Assert.Equal("Hello world", result.Examples.ElementAt(1).Remarks);
506+
507+
// next render it as markdown and make sure that we don't crash
508+
var renderer = new MarkdownV2Renderer(MAML.Parser.ParserMode.FormattingPreserve);
509+
string markdown = renderer.MamlModelToString(result, true);
510+
}
511+
512+
private MamlCommand GetModel(string title, string code, string remarks)
513+
{
514+
MamlCommand command = new MamlCommand()
515+
{
516+
Name = "Get-Foo",
517+
};
518+
519+
command.Examples.Add(new MamlExample()
520+
{
521+
Title = title,
522+
Code = new[] { new MamlCodeBlock(code) },
523+
Remarks = remarks,
524+
}
525+
);
526+
527+
return command;
528+
}
529+
530+
private MamlCommand GetModel(MamlExample[] examples)
531+
{
532+
MamlCommand command = new MamlCommand()
533+
{
534+
Name = "Get-Foo",
535+
};
536+
537+
command.Examples.AddRange(examples);
538+
return command;
539+
}
540+
}
403541
}

0 commit comments

Comments
 (0)