Skip to content

Commit 6d44635

Browse files
committed
dict: add support for read/writing multidictionaries
Add support for reading and writing multi-dictionaries in Git's credential helper I/O format. Multi-dictionaries differ from normal dictionaries by being key:value relation 1:N. Git prepends "[]" to the end of keys that should be treated as lists or having mutliple values. An empty value in the list has the effect of cleaning/resetting the values in the list so far. The first multiple-valued key is expected to be 'wwwauth[]' from this patch series: https://lore.kernel.org/git/[email protected]/
1 parent 4e8674a commit 6d44635

File tree

3 files changed

+388
-25
lines changed

3 files changed

+388
-25
lines changed

src/shared/Core.Tests/StreamExtensionsTests.cs

Lines changed: 212 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public class StreamExtensionsTests
1111
private const string LF = "\n";
1212
private const string CRLF = "\r\n";
1313

14+
#region Dictionary
15+
1416
[Fact]
1517
public void StreamExtensions_ReadDictionary_EmptyString_ReturnsEmptyDictionary()
1618
{
@@ -183,11 +185,204 @@ public void StreamExtensions_WriteDictionary_TextWriterCRLF_EntriesWithSpaces_Wr
183185
Assert.Equal("key a=value 1\r\n key b = value 2 \r\n\tvalue\tc\t=\t3\t\r\n\r\n", output);
184186
}
185187

188+
#endregion
189+
190+
#region MultiDictionary
191+
192+
[Fact]
193+
public void StreamExtensions_ReadMultiDictionary_EmptyString_ReturnsEmptyDictionary()
194+
{
195+
string input = string.Empty;
196+
197+
var output = ReadStringStream(input, StreamExtensions.ReadMultiDictionary);
198+
199+
Assert.NotNull(output);
200+
Assert.Equal(0, output.Count);
201+
}
202+
203+
[Fact]
204+
public void StreamExtensions_ReadMultiDictionary_TerminatedLF_ReturnsDictionary()
205+
{
206+
string input = "a=1\nb=2\nc=3\n\n";
207+
208+
var output = ReadStringStream(input, StreamExtensions.ReadMultiDictionary);
209+
210+
Assert.NotNull(output);
211+
Assert.Equal(3, output.Count);
212+
213+
AssertMultiDictionary(new[] { "1" }, "a", output);
214+
AssertMultiDictionary(new[] { "2" }, "b", output);
215+
AssertMultiDictionary(new[] { "3" }, "c", output);
216+
}
217+
218+
[Fact]
219+
public void StreamExtensions_ReadMultiDictionary_TerminatedCRLF_ReturnsDictionary()
220+
{
221+
string input = "a=1\r\nb=2\r\nc=3\r\n\r\n";
222+
223+
var output = ReadStringStream(input, StreamExtensions.ReadMultiDictionary);
224+
225+
Assert.NotNull(output);
226+
Assert.Equal(3, output.Count);
227+
AssertMultiDictionary(new[] { "1" }, "a", output);
228+
AssertMultiDictionary(new[] { "2" }, "b", output);
229+
AssertMultiDictionary(new[] { "3" }, "c", output);
230+
}
231+
232+
[Fact]
233+
public void StreamExtensions_ReadMultiDictionary_CaseSensitive_ReturnsDictionaryWithMultipleEntries()
234+
{
235+
string input = "a=1\nA=2\n\n";
236+
237+
var output = ReadStringStream(input, x => StreamExtensions.ReadMultiDictionary(x, StringComparer.Ordinal));
238+
239+
Assert.NotNull(output);
240+
Assert.Equal(2, output.Count);
241+
AssertMultiDictionary(new[] { "1" }, "a", output);
242+
AssertMultiDictionary(new[] { "2" }, "A", output);
243+
}
244+
245+
[Fact]
246+
public void StreamExtensions_ReadMultiDictionary_CaseInsensitive_ReturnsDictionaryWithLastValue()
247+
{
248+
string input = "a=1\nA=2\n\n";
249+
250+
var output = ReadStringStream(input, x => StreamExtensions.ReadMultiDictionary(x, StringComparer.OrdinalIgnoreCase));
251+
252+
Assert.NotNull(output);
253+
Assert.Equal(1, output.Count);
254+
AssertMultiDictionary(new[] { "2" }, "a", output);
255+
}
256+
257+
[Fact]
258+
public void StreamExtensions_ReadMultiDictionary_Spaces_ReturnsCorrectKeysAndValues()
259+
{
260+
string input = "key a=value 1\n key b = 2 \nkey\tc\t=\t3\t\n\n";
261+
262+
var output = ReadStringStream(input, StreamExtensions.ReadMultiDictionary);
263+
264+
Assert.NotNull(output);
265+
Assert.Equal(3, output.Count);
266+
267+
AssertMultiDictionary(new[] { "value 1" }, "key a", output);
268+
AssertMultiDictionary(new[] { " 2 " }, " key b ", output);
269+
AssertMultiDictionary(new[] { "\t3\t" }, "key\tc\t", output);
270+
}
271+
272+
[Fact]
273+
public void StreamExtensions_ReadMultiDictionary_EqualsInValues_ReturnsCorrectKeysAndValues()
274+
{
275+
string input = "a=value=1\nb=value=2\nc=value=3\n\n";
276+
277+
var output = ReadStringStream(input, StreamExtensions.ReadMultiDictionary);
278+
279+
Assert.NotNull(output);
280+
Assert.Equal(3, output.Count);
281+
AssertMultiDictionary(new[] { "value=1" }, "a", output);
282+
AssertMultiDictionary(new[] { "value=2" }, "b", output);
283+
AssertMultiDictionary(new[] { "value=3" }, "c", output);
284+
}
285+
286+
[Fact]
287+
public void StreamExtensions_ReadMultiDictionary_MultiValue_ReturnsDictionary()
288+
{
289+
string input = "odd[]=1\neven[]=2\neven[]=4\nodd[]=3\n\n";
290+
291+
var output = ReadStringStream(input, StreamExtensions.ReadMultiDictionary);
292+
293+
Assert.NotNull(output);
294+
Assert.Equal(2, output.Count);
295+
AssertMultiDictionary(new[] { "1", "3" }, "odd", output);
296+
AssertMultiDictionary(new[] { "2", "4" }, "even", output);
297+
}
298+
299+
[Fact]
300+
public void StreamExtensions_WriteDictionary_TextWriterLF_EmptyMultiDictionary_WritesLineLF()
301+
{
302+
var input = new Dictionary<string, IList<string>>();
303+
304+
string output = WriteStringStream(input, StreamExtensions.WriteDictionary, newLine: LF);
305+
306+
Assert.Equal(LF, output);
307+
}
308+
309+
[Fact]
310+
public void StreamExtensions_WriteDictionary_TextWriterCRLF_EmptyMultiDictionary_WritesLineCRLF()
311+
{
312+
var input = new Dictionary<string, IList<string>>();
313+
314+
string output = WriteStringStream(input, StreamExtensions.WriteDictionary, newLine: CRLF);
315+
316+
Assert.Equal(CRLF, output);
317+
}
318+
319+
[Fact]
320+
public void StreamExtensions_WriteDictionary_TextWriterLF_MultiEntries_WritesKVPListsAndLF()
321+
{
322+
var input = new Dictionary<string, IList<string>>
323+
{
324+
["a"] = new[] { "1", "2", "3" },
325+
["b"] = new[] { "4", "5", },
326+
["c"] = new[] { "6" }
327+
};
328+
329+
string output = WriteStringStream(input, StreamExtensions.WriteDictionary, newLine: LF);
330+
331+
Assert.Equal("a[]=1\na[]=2\na[]=3\nb[]=4\nb[]=5\nc=6\n\n", output);
332+
}
333+
334+
[Fact]
335+
public void StreamExtensions_WriteDictionary_TextWriterCRLF_MultiEntries_WritesKVPListsAndCRLF()
336+
{
337+
var input = new Dictionary<string, IList<string>>
338+
{
339+
["a"] = new[] { "1", "2", "3" },
340+
["b"] = new[] { "4", "5", },
341+
["c"] = new[] { "6" }
342+
};
343+
344+
string output = WriteStringStream(input, StreamExtensions.WriteDictionary, newLine: CRLF);
345+
346+
Assert.Equal("a[]=1\r\na[]=2\r\na[]=3\r\nb[]=4\r\nb[]=5\r\nc=6\r\n\r\n", output);
347+
}
348+
349+
[Fact]
350+
public void StreamExtensions_WriteDictionary_NoMultiEntries_WritesKVPsAndLF()
351+
{
352+
var input = new Dictionary<string, IList<string>>
353+
{
354+
["a"] = new[] {"1"},
355+
["b"] = new[] {"2"},
356+
["c"] = new[] {"3"}
357+
};
358+
359+
string output = WriteStringStream(input, StreamExtensions.WriteDictionary, newLine: LF);
360+
361+
Assert.Equal("a=1\nb=2\nc=3\n\n", output);
362+
}
363+
364+
[Fact]
365+
public void StreamExtensions_WriteDictionary_MultiEntriesWithEmpty_WritesKVPListsAndLF()
366+
{
367+
var input = new Dictionary<string, IList<string>>
368+
{
369+
["a"] = new[] {"1", "2", "", "3", "4"},
370+
["b"] = new[] {"5"},
371+
["c"] = new[] {"6", "7", ""}
372+
};
373+
374+
string output = WriteStringStream(input, StreamExtensions.WriteDictionary, newLine: LF);
375+
376+
Assert.Equal("a[]=3\na[]=4\nb=5\n\n", output);
377+
}
378+
379+
#endregion
380+
186381
#region Helpers
187382

188-
private static IDictionary<string, string> ReadStringStream(string input, Func<TextReader, IDictionary<string, string>> func)
383+
private static T ReadStringStream<T>(string input, Func<TextReader, T> func)
189384
{
190-
IDictionary<string, string> output;
385+
T output;
191386
using (var reader = new StringReader(input))
192387
{
193388
output = func(reader);
@@ -196,7 +391,7 @@ private static IDictionary<string, string> ReadStringStream(string input, Func<T
196391
return output;
197392
}
198393

199-
private static string WriteStringStream(IDictionary<string, string> input, Action<TextWriter, IDictionary<string, string>> action, string newLine)
394+
private static string WriteStringStream<T>(T input, Action<TextWriter, T> action, string newLine)
200395
{
201396
var output = new StringBuilder();
202397
using (var writer = new StringWriter(output){NewLine = newLine})
@@ -207,6 +402,20 @@ private static string WriteStringStream(IDictionary<string, string> input, Actio
207402
return output.ToString();
208403
}
209404

405+
private static void AssertDictionary(string expectedValue, string key, IDictionary<string, string> dict)
406+
{
407+
Assert.True(dict.TryGetValue(key, out string actualValue));
408+
Assert.Equal(expectedValue, actualValue);
409+
}
410+
411+
private static void AssertMultiDictionary(IList<string> expectedValues,
412+
string key,
413+
IDictionary<string, IList<string>> dict)
414+
{
415+
Assert.True(dict.TryGetValue(key, out IList<string> actualValues));
416+
Assert.Equal(expectedValues, actualValues);
417+
}
418+
210419
#endregion
211420
}
212421
}

0 commit comments

Comments
 (0)